Допустим, у меня есть функция (написана на Ruby, но должна быть понятна всем):
def am_I_old_enough?(name = 'filip')
person = Person::API.new(name)
if person.male?
return person.age > 21
else
return person.age > 18
end
end
В модульном тестировании я бы создал четыре теста, чтобы охватить все сценарии. Каждый будет использовать макет Person::API
объекта с заглушенными методами male?
и age
.
Теперь речь идет о написании интеграционных тестов. Я предполагаю, что Person :: API больше не должен быть осмеян. Поэтому я бы создал точно такие же четыре тестовых примера, но без насмешливого объекта Person :: API. Это верно?
Если да, то какой смысл писать модульные тесты вообще, если бы я мог просто написать интеграционные тесты, которые дают мне больше уверенности (так как я работаю с реальными объектами, а не с заглушками или издевательствами)?
unit-testing
testing
ruby
integration-tests
Филипп Бартузи
источник
источник
Ответы:
Нет, интеграционные тесты не должны просто дублировать охват модульных тестов. Они могут дублировать какое-то освещение, но это не главное.
Суть модульного тестирования заключается в том, чтобы гарантировать, что конкретный небольшой функционал работает точно и полностью, как задумано. Модульный тест для
am_i_old_enough
проверки данных с различными возрастами, определенно близкими к порогу, возможно, всех человеческих возрастов. После того, как вы написали этот тест, целостностьam_i_old_enough
никогда больше не должна подвергаться сомнению.Смысл интеграционного теста состоит в том, чтобы убедиться, что вся система или комбинация значительного числа компонентов работают правильно, когда используются вместе . Клиента не волнует конкретная служебная функция, которую вы написали, он заботится о том, чтобы его веб-приложение было должным образом защищено от доступа несовершеннолетних, потому что в противном случае у регулирующих органов будут свои оценки.
Проверка возраста пользователя является одна небольшой частью этой функциональности, но интеграционный тест не проверяет , используется ли функция полезности правильного порогового значения. Он проверяет, принимает ли вызывающая сторона правильное решение на основе этого порога, вызывается ли функция полезности вообще, выполняются ли другие условия для доступа и т. Д.
Причина, по которой нам нужны оба типа тестов, заключается в том, что существует комбинаторный взрыв возможных сценариев для пути через кодовую базу, который может пройти выполнение. Если функция полезности имеет около 100 возможных входов и сотни функций полезности, то проверка того, что все происходит правильно во всех случаях, потребует многих, многих миллионов тестовых случаев. Просто проверяя все случаи в очень маленьких областях, а затем проверяя общие, релевантные или вероятные комбинации для этих областей, предполагая, что эти небольшие области уже правильны, как продемонстрировано модульным тестированием , мы можем получить довольно уверенную оценку того, что система делает что следует, не утонув в альтернативных сценариях для тестирования.
источник
The customer doesn't care about a particular utility function you wrote, they care that their web app is properly secured against access by minors
-> Это очень умное мышление, спасибо! Проблема в том, когда вы делаете проект для себя. Трудно разделить ваше мышление между программистом и менеджером продукта в одно и то же времяКороткий ответ - нет". Более интересная часть - почему / как эта ситуация может возникнуть.
Я думаю, что путаница возникает из-за того, что вы пытаетесь придерживаться строгих методов тестирования (модульные тесты или интеграционные тесты, макеты и т. Д.) Для кода, который, кажется, не придерживается строгих правил.
Это не значит, что код «неправильный», или что определенные практики лучше, чем другие. Просто некоторые допущения, сделанные практиками тестирования, могут не применяться в этой ситуации, и это может помочь использовать подобный уровень «строгости» в практиках кодирования и практиках тестирования; или, по крайней мере, признать, что они могут быть несбалансированными, что приведет к неприменимости или избыточности некоторых аспектов.
Наиболее очевидная причина в том, что ваша функция выполняет две разные задачи:
Person
основе их имени. Это требует интеграционного тестирования, чтобы убедиться, что он может найтиPerson
объекты, которые предположительно созданы / хранятся в другом месте.Person
достаточно ли стар, исходя из их пола. Это требует модульного тестирования, чтобы убедиться, что расчет выполняется должным образом.Сгруппировав эти задачи в один блок кода, вы не сможете выполнить одно без другого. Когда вы хотите провести модульное тестирование вычислений, вы вынуждены искать
Person
(из реальной базы данных или из заглушки / макета). Если вы хотите проверить, что поиск интегрируется с остальной частью системы, вы также должны выполнить расчет возраста. Что мы должны делать с этим расчетом? Должны ли мы игнорировать это или проверить? Кажется, именно это затруднительное положение вы описываете в своем вопросе.Если мы представим альтернативу, у нас может быть сам расчет:
Поскольку это чистый расчет, нам не нужно проводить интеграционные тесты на нем.
У нас также может возникнуть желание написать задачу поиска отдельно:
Однако в этом случае функциональность настолько близка,
Person::API.new
что я бы сказал, что вы должны использовать ее вместо этого (если необходимо имя по умолчанию, лучше ли его хранить в другом месте, например, в атрибуте класса?).При написании интеграционных тестов
Person::API.new
(илиperson_from_name
) все, что вам нужно, это заботиться о том, получите ли вы ожидаемоеPerson
; все возрастные расчеты выполняются в других местах, поэтому ваши интеграционные тесты могут их игнорировать.источник
Еще один момент, который я хотел бы добавить к ответу Киллиана, состоит в том, что модульные тесты выполняются очень быстро, поэтому мы можем иметь их тысячи. Интеграционный тест обычно занимает больше времени, потому что он вызывает веб-сервисы, базы данных или некоторые другие внешние зависимости, поэтому мы не можем запустить те же тесты (1000) для сценариев интеграции, поскольку они будут занимать слишком много времени.
Кроме того, модульные тесты обычно запускаются во время сборки (на компьютере сборки), а интеграционные тесты запускаются после развертывания в среде / машине.
Как правило, мы выполняем наши тысячи модульных тестов для каждой сборки, а затем - около 100 или более ценных интеграционных тестов после каждого развертывания. Мы можем не брать каждую сборку для развертывания, но это нормально, потому что сборка, которую мы берем для развертывания, будут запускать интеграционные тесты. Как правило, мы хотим ограничить выполнение этих тестов в течение 10 или 15 минут, потому что мы не хотим задерживать развертывание слишком долго.
Кроме того, по недельному расписанию мы можем запускать регрессионный набор интеграционных тестов, которые охватывают больше сценариев на выходных или в другое время простоя. Это может занять более 15 минут, так как будет рассмотрено больше сценариев, но обычно никто не работает над Sat / Sun, поэтому мы можем потратить больше времени на тесты.
источник