В чем заключается решающее преимущество модульного тестирования перед интеграционным?
Это ложная дихотомия.
Модульное тестирование и интеграционное тестирование служат двум схожим, но различным целям. Цель модульного тестирования - убедиться, что ваши методы работают. С практической точки зрения, модульные тесты удостоверяются в том, что код соответствует контракту, указанному в модульных тестах. Это очевидно в том, как создаются модульные тесты: они конкретно указывают, что должен делать код, и утверждают, что код делает это.
Интеграционные тесты разные. Интеграционные тесты осуществляют взаимодействие между программными компонентами. У вас могут быть программные компоненты, которые проходят все свои тесты и по-прежнему не проходят интеграционные тесты, потому что они не взаимодействуют должным образом.
Однако, если есть решающее преимущество для модульных тестов, они заключаются в следующем: настроить модульные тесты гораздо проще, и они требуют гораздо меньше времени и усилий, чем интеграционные тесты. При правильном использовании модульные тесты способствуют разработке «тестируемого» кода, что означает, что конечный результат будет более надежным, более понятным и более легким в обслуживании. Тестируемый код обладает определенными характеристиками, такими как целостный API, повторяемое поведение, и он возвращает результаты, которые легко утверждать.
Интеграционные тесты являются более сложными и более дорогостоящими, потому что вам часто требуются сложные макеты, сложные настройки и сложные утверждения. На самом высоком уровне системной интеграции представьте, что вы пытаетесь смоделировать взаимодействие человека в пользовательском интерфейсе. Целые программные системы предназначены для такого рода автоматизации. И мы стремимся к автоматизации; человеческое тестирование не повторяется и не масштабируется как автоматическое тестирование.
Наконец, интеграционное тестирование не дает никаких гарантий относительно покрытия кода. Сколько комбинаций циклов, условий и веток кода вы тестируете с помощью интеграционных тестов? Вы действительно знаете? Существуют инструменты, которые вы можете использовать с юнит-тестами и тестируемыми методами, которые подскажут вам, какой объем кода у вас есть, и какова цикломатическая сложность вашего кода. Но они действительно хорошо работают только на уровне методов, где живут модульные тесты.
Если ваши тесты меняются каждый раз, когда вы выполняете рефакторинг, это другая проблема. Под модульными тестами подразумевается документирование того, что делает ваше программное обеспечение, подтверждение того, что оно делает это, а затем подтверждение того, что оно делает это снова, когда вы реорганизуете базовую реализацию. Если ваш API меняется, или вам нужно, чтобы ваши методы изменились в соответствии с изменением структуры системы, это то, что должно произойти. Если это часто происходит, подумайте о написании тестов, прежде чем писать код. Это заставит вас задуматься об общей архитектуре и позволит вам писать код с уже установленным API.
Если вы тратите много времени на написание модульных тестов для тривиального кода, такого как
public string SomeProperty { get; set; }
тогда вы должны пересмотреть свой подход. Модульное тестирование должно проверять поведение, и в строке кода выше нет поведения. Однако вы где-то создали зависимость в своем коде, поскольку это свойство почти наверняка будет упоминаться в другом месте вашего кода. Вместо этого рассмотрим написание методов, которые принимают необходимое свойство в качестве параметра:
public string SomeMethod(string someProperty);
Теперь ваш метод не имеет каких-либо зависимостей от чего-то вне его, и теперь он более тестируемый, поскольку он полностью автономен. Конечно, вы не всегда сможете это сделать, но это действительно продвигает ваш код в сторону большей тестируемости, и на этот раз вы пишете модульный тест для реального поведения.