Для начала, мне стоит дать определение, которого я придерживаюсь, когда говорю о unit-тесте.
Unit-тест - это автоматизированный код, который вызывает исполнение тестируемого модуля, и проверяет один из результатов его работы.
Этот код надежный, читаемый, поддерживаемый. Вместе с тем, что очень важно, этот код не имеет внешних зависимостей, и имеет полный контроль над объектом тестирования. Именно это отличает unit-тесты от интеграционных.
Если рассмотреть свойства unit-теста, то можно прийти к следующему набору качеств, которые такой тест должен иметь:
Лучше расширить список поводов для запуска тестов, и включить в него: ежедневные (ночные) запуски; запуск перед поставкой; запуск перед выгрузкой кода в репозиторий (пушем).
Легкий во внедрении. Подобный тест должно быть легко (быстро) разработать и добавить к тестовому набору. Если вы видите, что тест разрабатывать долго, то это признак того, что или вы пишете не unit-тест, или вы неправильно выбрали "размеры" объекта тестирования. Да, тестируемые модули могут быть разными по размеру. Это могут быть и отдельные методы, и несколько классов.
Актуален (релевантен) в любое время. Это значит, что тест не теряет актуальность до тех пор, пока объект тестирования актуален (не подвергся изменениям или не удален). И не должно быть никаких других условий релевантности.
Легко исполняемый. Каждый участник команды разработки должен иметь возможность запустить тест. Как локально, так и на CI-сервере. Это позволит быть уверенным любому, что он не сломал чужой код.
Быстрый. Unit-тесты исполняются если не за доли секунды, то за секунды. Это является гарантией того, что они будут исполняться часто. Никому не хочется долго ждать завершения тестов. И, зачастую, продолжительные тесты просто не выгодно запускать, если у нас есть ограничения (к примеру, небольшое количество сборщиков на CI; или нам срочно нужно сделать поставку кода).
Консистентный. Всегда должен быть один и тот же результат при каждом исполнении теста. Это одно из главных условий стабильности тестов.
Имеет полный контроль над объектом тестирования (модулем). Это значит, что тест исключает и подменяет "общение" модуля с любыми внешними источниками: БД, файловая система, системное время, различные генераторы и т.д. В противном случае, тест не может быть консистентным.
Прост в анализе. Для облегчения анализа причин неуспешной проверки, как минимум, тест должен иметь понятную архитектуру, и сообщать какой результат актуальный, а какой ожидаемый.
Начну с самого очевидного – разрабатывайте unit-тесты в специализированном фреймворке. Независимо от языка программирования, который вы используете, возможно найти подходящий xUnit фреймворк, который предоставит возможность:
Разрабатывать структурированные тесты. Как минимум, у вас будут атрибуты, которые будут помечать методы как "тесты".
Использовать готовые методы для разных типов проверок. Использование таких методов увеличит читаемость кода и упростит анализ исполненных тестов.
Формировать тестовые наборы. Вы сможете логически объединять тесты в группы, основываясь на каком-нибудь признаке. Например, относительно функционала.
Видеть успешность проверок в момент их исполнения.
Проводить анализ исполненных тестов: сколько тестов исполнилось, сколько не исполнялось, какие результаты проверок, причины провалов, и т.д.
Разрабатывать параметризованные тесты. А значит драматично уменьшить количество необходимых строк кода при сохранении уровня тестового покрытия.