Модульные тесты и автоматические тесты обычно предназначены как для лучшего проектирования, так и для проверенного кода.
Модульное тестирование должно проверять некоторый путь выполнения в очень маленьком модуле. Этот модуль обычно является публичным или внутренним методом, доступным для вашего объекта. Сам метод все еще может использовать много других защищенных или частных методов из того же экземпляра объекта. Вы можете иметь один метод и несколько модульных тестов для этого метода, чтобы проверить разные пути выполнения. (Под путем выполнения я имел в виду что-то, контролируемое if
, switch
и т. Д.). Написание модульных тестов таким образом подтвердит, что ваш код действительно выполняет то, что вы ожидаете. Это может быть особенно важно в некоторых угловых случаях, когда вы ожидаете выбросить исключение в некоторых редких сценариях и т. Д. Вы также можете проверить поведение метода, если передаете разные параметры - например, null
вместо экземпляра объекта, отрицательное значение для целого числа, используемого для индексация и т. д. Это особенно полезно для публичного API.
Теперь предположим, что ваш протестированный метод также использует экземпляры других классов. Как с этим бороться? Стоит ли вам проверять свой единственный метод и верить, что класс работает? Что если класс еще не реализован? Что если в классе есть какая-то сложная логика внутри? Вы должны проверить эти пути выполнения также на вашем текущем методе? Есть два подхода для решения этой проблемы:
- В некоторых случаях вы просто позволяете тестировать экземпляр реального класса вместе с вашим методом. Это, например, очень распространено в случае регистрации (неплохо иметь журналы, доступные и для тестирования).
- Для других сценариев вы хотели бы взять эту зависимость из вашего метода, но как это сделать? Решение - внедрение зависимости и реализация против абстракции вместо реализации. Что это значит? Это означает, что ваш метод / класс не будет создавать экземпляры этих зависимостей, но вместо этого он будет получать их через параметры метода, конструктор класса или свойства класса. Это также означает, что вы не будете ожидать конкретной реализации, но будете использовать абстрактный базовый класс или интерфейс. Это позволит вам передать ложную, фиктивную или фиктивную реализацию вашему тестируемому объекту. Эти специальные типы реализаций просто не выполняют никакой обработки, они получают некоторые данные и возвращают ожидаемый результат. Это позволит вам протестировать свой метод без зависимостей и привести к гораздо лучшему и более расширяемому дизайну.
В чем недостаток? Как только вы начинаете использовать fakes / mocks, вы тестируете один метод / класс, но у вас нет теста, который собрал бы все реальные реализации и собрал их вместе, чтобы проверить, действительно ли работает вся система = Вы можете иметь тысячи модульных тестов и проверить что каждый ваш метод работает, но это не значит, что они будут работать вместе. Это сценарий для более сложных тестов - интеграционных или сквозных.
Модульные тесты, как правило, должны быть очень простыми для написания - если их нет, это означает, что ваш дизайн, вероятно, сложен, и вам следует подумать о рефакторинге. Они также должны быть очень быстрыми, чтобы выполнять их очень часто. Другие виды тестирования могут быть более сложными и очень медленными, и они должны выполняться в основном на сервере сборки.
Как это согласуется с процессом разработки ПО? Худшая часть процесса разработки - это стабилизация и исправление ошибок, потому что эту часть очень трудно оценить. Чтобы оценить, сколько времени занимает исправление ошибки, вы должны знать, что является причиной ошибки. Но это расследование не может быть оценено. Вы можете исправить ошибку, которая займет один час, но вы потратите две недели на отладку приложения и поиск этой ошибки. При использовании хорошего покрытия кода, скорее всего, вы найдете такую ошибку на ранних стадиях разработки.
Автоматизированное тестирование не говорит о том, что ПО не содержит ошибок. Это лишь говорит о том, что вы сделали все возможное, чтобы найти и решить их во время разработки, и из-за этого ваша стабилизация может быть гораздо менее болезненной и намного короче. Это также не говорит о том, что ваше ПО делает то, что должно, - это больше о самой логике приложения, которая должна тестироваться отдельными тестами, проходящими через каждый сценарий использования / историю пользователя - приемочные тесты (они также могут быть автоматизированы). *
Как это сочетается с TDD? TDD доводит это до крайности, потому что в TDD вы сначала напишите свой тест, чтобы определить качество, охват кода и дизайн.