Подтвердить что ты не робот

Программы тестирования модулей, которые в основном взаимодействуют с внешними ресурсами

Я хотел бы начать больше тестирования модулей в своих приложениях, но мне кажется, что большая часть всего, что я делаю, просто не подходит для тестирования модулей. Я знаю, как модульные тесты должны работать в примерах учебников, но в реальных приложениях они мало чем полезны.

Некоторые приложения, которые я пишу, имеют очень простую логику и сложное взаимодействие с вещами, которые находятся вне моего контроля. Например, я хотел бы написать демона, который реагирует на сигналы, посылаемые некоторыми приложениями, и изменяет некоторые пользовательские настройки в ОС. Я вижу три трудности:

  • сначала я должен иметь возможность говорить с приложениями и получать уведомления об их событиях;
  • тогда мне нужно взаимодействовать с ОС всякий раз, когда я получаю сигнал, чтобы изменить соответствующие пользовательские настройки;
  • Наконец, все это должно работать как демон.

Все эти вещи потенциально деликатные: мне придется просматривать, возможно, сложные API-интерфейсы, и я могу ввести ошибки, скажем, неверно истолковывая некоторые параметры. Что может сделать для меня тестирование? Я могу высмеять как внешнее приложение, так и ОС, и проверить, что с учетом сигнала из приложения я вызову соответствующий API-метод в ОС. Это... ну, тривиальная часть приложения.

На самом деле большинство вещей, которые я делаю, связаны с взаимодействием с базами данных, файловой системой или другими приложениями, и это самые деликатные части.

В другом примере посмотрите мой инструмент сборки PHPmake. Я хотел бы реорганизовать его, поскольку он не очень хорошо написан, но я боюсь сделать это, поскольку у меня нет тестов. Поэтому я хотел бы добавить некоторые. Дело в том, что вещи, которые могут быть нарушены рефакторингом, не могут быть пойманы модульными тестами:

  • Один из способов сделать это - решить, какие вещи нужно построить и какой из них уже обновлен, и это зависит от времени последней модификации файлов. Это время фактически изменяется внешними процессами, когда запускается некоторая команда сборки.
  • Я хочу быть уверенным, что вывод внешних процессов отображается правильно. Иногда команды buikd требуют ввода, и это также должно управляться правильно. Но я не знаю априори, какие процессы будут выполняться - это может быть что угодно.
  • Некоторая логика связана с сопоставлением шаблонов, и это может показаться проверяемой частью. Но функции, которые используют сопоставление шаблонов (добавление к их собственной логике), используют функцию PHP glob, которая работает с файловой системой. Если я просто высмеиваю дерево вместо фактической файловой системы, glob не будет работать.

Я мог бы привести больше примеров, но дело в следующем. Если у меня нет деликатных алгоритмов, большинство из того, что я делаю, связано с взаимодействием с внешними ресурсами, и это не подходит для модульного тестирования. Более того, часто это взаимодействие фактически является нетривиальной частью. Тем не менее многие люди рассматривают модульное тестирование как базовый инструмент. Что мне не хватает? Как я могу научиться быть лучшим тестером?

4b9b3361

Ответ 1

Я думаю, вы открываете ряд вопросов в своем вопросе.

Во-первых, когда ваше приложение интегрируется с внешними средами, такими как ОС, другими потоками и т.д., тогда вам нужно отделить (1) логику, связанную с внешней средой, и (2) бизнес-код. это то, что делает ваше приложение. Это ничем не отличается от того, как вы будете разделять GUI и SERVER в приложении (или веб-приложении).

Во-вторых, вы спрашиваете, следует ли тестировать простую логику. Я бы сказал, это зависит. Часто для простой выборки/сохранения функциональности приятно проводить тесты. Это похоже на основу вашего приложения. Следовательно, это важно. Другие деловые вещи, основанные на вашем фундаменте, которые очень просты, вы можете легко почувствовать, что чувствуете, что теряете свое время, и в основном вы: -)

В-третьих, проблема с существующей программой и ее тестирование в существующем состоянии может быть проблемой. Если ваша PHP-программа создает набор файлов на основе некоторого ввода, ну, может быть, это ваша точка входа в тесты. Конечно, тесты могут быть высокоуровневыми, но это простой способ обеспечить, чтобы после рефакторинга ваша программа выдавала тот же результат. Следовательно, для тестирования на более высоком уровне в этой ситуации на начальном этапе ваших усилий по рефакторингу.

Я бы хотел порекомендовать какую-нибудь литературу, но я могу только придумать один заголовок. "Эффективная работа с устаревшим кодом" от Micheal Feathers. Это хорошее начало. Другим может быть "xUnit Test Patterns: Рефакторинг тестового кода" Джерарда Мезароса (хотя эта книга намного более неаккуратная и FULL текста для копирования).

Ответ 2

Что касается вашей проблемы с существующими базами кода, которые в настоящее время не охвачены тестами, в которых вы хотели бы начать рефакторинг, я бы предложил прочитать:

Эффективная работа с устаревшим кодом Автор Micheal Feathers.

В этой книге приведены методы решения проблем, с которыми вам может столкнуться PHPMake. Он предоставляет способы введения швов для тестирования, где ранее их не было.


Кроме того, при использовании кода, который затрагивает файловые системы, вы можете абстрагировать вызовы файловой системы за тонкой оболочкой, используя шаблон адаптера. Единичные тесты были бы против поддельной реализации абстрактного интерфейса, который реализует класс упаковки.

В какой-то момент вы достигнете достаточно низкого уровня, когда единицу кода не может быть изолирована для модульного тестирования, поскольку они зависят от вызовов библиотеки или API (например, в производственной реализации оболочки). Как только это произойдет, интеграционные тесты - это единственные автоматические тесты разработчиков, которые вы можете написать.

Ответ 3

"Единичные тесты" проверяет одну единицу вашего кода. Никакие внешние инструменты не должны быть задействованы. Это кажется сложным для вашего первого приложения (не зная об этом многозначительно), но phpMake тестируется на единицу - я уверен... потому что ant, gradle и maven тоже проверяются на единицу; )

Но, конечно, вы также можете проверить свое первое приложение автоматически. Есть несколько разных уровней, можно протестировать приложение.

Итак, задача для вас - найти автоматизированный способ тестирования вашего приложения - будь то интеграционное тестирование или что-то еще.

например. вы можете написать сценарии оболочки, которые утверждают некоторый вывод! С этим вы убедитесь, что ваше приложение ведет себя правильно...

Ответ 4

Я рекомендую этот google tech-talk при модульном тестировании.

Видео сводится к

  • напишите свой код, чтобы он знал как мало о том, как он будет использоваться, насколько это возможно. Чем меньше предположений ваш код, тем легче тестировать. Избегайте сложной логики в конструкторах, использовании синглетонов, статических членов класса и т.д.
  • изолировать ваш код от внешнего мира (comms, базы данных, в реальном времени) и убедиться, что ваш код только говорит о вашем изолирующем слое. В противном случае, написание тестов будет кошмаром с точки зрения настройки "поддельной среды".
  • модульные тесты должны тестировать истории; это то, что мы действительно понимаем и заботимся; данный класс с методом foo(), testFoo() неинформативен. Они действительно рекомендуют имена тестов, например itShouldCloseConnectionEvenWhenExceptionThrown(). В идеале, ваши истории должны охватывать достаточно функциональности, что вы можете перестроить спецификацию из рассказов.

ПРИМЕЧАНИЕ. В этом примере видео и это сообщение используют Java; тем не менее, основные пункты означают любой язык.

Ответ 5

Тесты взаимодействия с внешними ресурсами - это интеграционные тесты, а не модульные тесты.

Тесты вашего кода, чтобы увидеть, как он будет себя вести, если бы произошли определенные внешние взаимодействия, могут быть модульные тесты. Это нужно сделать, написав свой код для использования инъекции зависимостей, а затем в unit test, вводя макет объектов в качестве зависимостей.

Например, рассмотрим фрагмент кода, который добавляет результаты вызова к одной службе к результатам вызова другой службы:

public int AddResults(IService1 svc1, IService2 svc2, int parameter)
{
    return svc1.Call(parameter) + svc2.Call(parameter);
}

Вы можете проверить это, передав макету объектов для двух служб:

private class Service1Returns1 : IService1
{
    public int Call(int parameter){return 1;}
}

private class Service2Returns1 : IService2
{
    public int Call(int parameter){return 1;}
}

public void Test1And1()
{
    Assert.AreEqual(2, AddResults(new Service1Returns1(), new Service2Returns1(), 0));
}

Ответ 6

Прежде всего, если модульное тестирование не похоже, что это будет очень полезно в ваших приложениях, почему вы даже хотите начать делать больше? Что побуждает вас заботиться об этом? Это определенно пустая трата времени, если: а) вы делаете все идеально в первый раз и ничего не меняется или б) вы решаете, что это пустая трата времени и делаете это плохо.

Если вы действительно думаете, что действительно хотите провести модульное тестирование, ответ на ваши вопросы одинаков: инкапсуляция. В примере вашего демона вы можете создать ApplcationEventObeservationProxy с очень узким интерфейсом, который просто реализует методы pass through. Цель этого класса - ничего не делать, кроме как полностью инкапсулировать остальную часть вашего кода из сторонней библиотеки наблюдений за событиями (ничего не значит - никакой логики здесь). Сделайте то же самое для настроек ОС. Тогда вы можете полностью unit test класс, который выполняет действия на основе событий. Я бы рекомендовал иметь отдельный класс для демона, который просто обертывает ваш основной класс - это облегчит тестирование.

Существует несколько преимуществ этого подхода вне модульного тестирования. Во-первых, если вы инкапсулируете код, который напрямую взаимодействует с ОС, проще его отключить. Этот тип кода особенно подвержен поломке вне вашего контроля (например, MS patchsets). Вероятно, вы также захотите поддержать более одной ОС, и если логика ОС не будет запутана с остальной частью вашей логики, это будет проще. Другое преимущество заключается в том, что вы будете вынуждены понимать, что в вашем приложении больше бизнес-логики, чем вы думаете.:)

Наконец, не забывайте, что модульное тестирование является основой для хорошего продукта, но не единственным ингредиентом. Наличие набора тестов, которые исследуют и проверяют вызовы API OS, которые вы будете использовать, являются хорошей стратегией для "жестких" частей этой проблемы. Вы также должны иметь сквозные тесты, которые гарантируют, что события в ваших приложениях изменят настройки ОС.

Ответ 7

Как и другие ответы, предлагаемые эффективно работать с устаревшим кодом. Поверхность Micheal Feathers хорошо читается. Если вам нужно иметь дело с устаревшим кодом, и вы хотите убедиться, что взаимодействие с системой работает должным образом, попробуйте сначала написать интеграционные тесты. И тогда более целесообразно писать Unit Tests, чтобы проверить поведение методов, которые оцениваются с точки зрения требований. Тестирование Тестирует совершенно другую цель, чем интеграционные тесты. Тесты единиц более вероятно улучшают дизайн вашей системы, чем тестирование того, как все зависает, чтобы собраться.