Pull to refresh

Comments 17

Аннотация SpringBootTest - считается, что это интеграционный тест, поднимает все приложение

А что насчет DataJpaTest и WebMvcTest - с одной стороны приложение поднимается, с другой оно урезанное. Это какой тест получается?

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

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

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

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

Разработка продукта под юнит-тесты и написание самих тестов равнозначно увеличению объемов разработки в разы. Если бизнес готов тратить время и материальные ресурсы на такое раздувание сроков (и коллективов программистов, как угодно) - пожалуйста, никаких проблем. Если в приоритете MVP, то о юнит-тестах вообще не может идти и речи.

Глобально, тут два пути: либо проект сразу же строится по принципу TDD, либо внедрение юнит-тестов будет для него слоном в посудной лавке. Вынести отдельные модули (api, взаимодействие с сервисами) в тестируемую часть - пожалуйста. Молиться на "проценты покрытия" - карго-культ.

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

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

По поводу статьи автора никакого негатива, все описано именно так, как есть. С некоторыми моментами можно спорить, но программист и так поймет, а продакт, примеряющий на себя роль маркетолога и желающий внедрить "модно-креативно-популярно-юнит-тестово-kpi-внедряемо", все равно с большой долей вероятности все сделает не так.

Спасибо за развернутый комментарий.

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

Также есть опыт увеличения покрытия тестами уже написанного приложения - это долго и трудно, сопряжено с неизбежным рефакторингом, но вполне реально.

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

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

Не понял противопоставления "Лондонской школы" и "Классической школы". Они отличаются друг от друга только тем, что мы считаем юнитом в данном случае и по идее должны использоваться и те и другие (естественно если это надо в конкретном проекте, а не для галочки) и по сути по мере увеличения размера "юнита" они плавно перерастают в интеграционные тесты. Где провести границу, между юинт и интеграционным тестированием, это большой вопрос, ответ на который зависит от большого числа факторов.

Тут правильнее сказать, что отличаются они подходом к изоляции кода - одна школа изолирует код от изменяемых зависимостей, другая - тесты друг от друга. А размер юнита - это следствие из этого. В начале статьи как раз указаны признаки юнит-теста, один из которых - должен поддерживать изоляцию от другого кода, и если оно не выполняется - это уже не юнит-тест.

Так как мы используем подход Лондонской школы, где юнит == класс, то тест, который проверяет этот класс с замоканными изменяемыми зависимостями - это юнит-тест. Если мы такие зависимости не мокаем, а используем реальные классы - то уже интеграционный. Для классической школы чуть сложнее, но думаю возможно провести границу.

Ок. Пример для понимания моей точки зрения. Допустим есть 2 класса: A - самодостаточный и у него нет никаких внешних зависимостей и B - зависящий только от A.

В вашем подходе, если мы тестируем класс B с использованием реального класса A, вы называете это интеграционным тестом. "Классическая школа" называет это юнит тестом.

Но ни на код самих тестов, ни на необходимость наличия их обоих (если это имеет смысл в рамках проекта) это не никак влияет.

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

Классическая школа: всегда писали компонентные тесты, но сейчас в моде модульное тестирование, поэтому стали просто называть свои тесты модульными.

Лондонская школа: верят, что возможность изолировать отдельный модуль - это признак хорошей архитектуры, поэтому тратят 10х времени на тестирование, рефакторинги и звездолёты.

В какую сам пойдёшь, в какую джуна отправишь?

Вопрос тестирования всегда упирается в две вещи. Testable system desing, и стоимость тестирования. Unit тест отвечает на один простой вопрос: «Как работает компонент X, если его окружение (компоненты Y,Z,C,W,etc) работает идеально ?». В некоторых тривиальных случаях окружение отсутствует, и тут юнит-тесты бесконечно полезны. Например — у меня есть код для расчета и проверки контрольной цифры кода EAN13. Он зависит только от документов GS/1 которые не менялись годами, и поэтому его легко тестировать. И это нужно делать, потому что наш остальной код будет стопятьсот раз вызывать эту штуку в разных местах. И я ДЕЙСТВИТЕЛЬНО хочу быть уверен что мы не лажаем в расчете контрольной цифры штрих-кода.

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

В итоге, если система спроектирована хорошо — в ее основании находятся некоторое количество классов/методов/алгоритмов — которые имеют малое (в идеале, 0) количество внешних зависимостей и не меняют логику своей работы в ходе жизни приложения. Вы их тестируете по юнит-тестами, чтобы точно знать что фундамент не собирается обвалиться. А чем более высокого уровня логику вы тестируете, тем чаще там большие интеграционные тесты, а не мелкие юниты. Если я вижу юнит с большим количеством моков — он скорее всего бесполезен (исключение — юниты которые написаны для поиска каких-то редких и неприятных ошибок, где нужно совершенно определенное состояние окружения чтобы ее стриггерить). Потому что нужно доказывать (и с течением времени — поддерживать!) соответствие идеального окружения созданного моками, и реального окружения в котором будет работать компонент.

А дальше — будет вопрос о рисках и деньгах… Можно сделать идеально протестированную систему с хорошим покрытием, продуть рынок и пойти под сокращение в результате банкротства. Разумеется, есть и обратные примеры. А дальше это уже вопрос к руководству, бизнес-модели и все такое прочее…

Хотел попросить более подробно объяснить разницу между интергационным и и сквозными тестами. На мой взгляд, это две вещи одной природы, интеграционный проверяет взаимодецствие с другими компонентами системы, т. е. их совместную работу, в это же время сквозной тест также использует несколько компонентов. Можно ли сказать, что сквозной тест включает в себя (содержит) интеграционный?

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

Сквозные тесты все-таки более высокоуровневые и ориентируется на копирование поведения реального пользователя системы - для приложения с UI они могут имитировать щелчки мышки и нажатия клавиш. Для систем без UI - это запросы к публичному API. Такие тесты не зависят от внутренней реализации системы, смотрят на тестируемую систему как на черный ящик (в отдельных случаях как на серый ящик - если нужна специфическая подготовка тестовых данных) и используют тот интерфейс, который использует реальный пользователь - графический интерфейс или публичное API (такой интерфейс чаще всего имеет спецификацию). При таких тестах могут быть задействованы несколько систем сразу. Они оценивают, что система работает так, как ожидалось с точки зрения конечного пользователя. Этим тестам не нужен исходный код тестируемого приложения, они могут быть запущены на отдельной машине, для проверки им нужен лишь адрес тестируемой системы. У нас такие тесты пишут QA-специалисты.

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

При подготовке материала очень помогла книга Владимира Хорикова (@vkhorikov ) «Принципы юнит-тестирования».

Статья - краткий пересказ книги. Хотелось бы хоть какие-то оригинальные идеи автора увидеть.

Sign up to leave a comment.