Pull to refresh

Comments 28

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

Кажется, разработчики как раз понимают. Не понимает начальство. Жду статью, как заставить начальство понять и выделить ресурсы на написание и поддержку тестов...


Именно тут на страже стоят unit тесты, которые фиксируют утвержденные правила, по которым должна работать система!

Систему целиком контролируют IT-тестами, никак не unit. Поэтому если времени в обрез, лучше тратить его на IT-тесты, от них выхлопа больше


“у нас есть метод, который подсчитывает сумму чисел”
“на данный метод можно написать тест”

В статье скатились в тоже самое, только тестирование спринга добавилось.

В статье скатились в тоже самое, только тестирование спринга добавилось.

не совсем, в статье заложен принцип написания тестов для взаимоинтегрируемых систем

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

Покрывать нельзя, вы правы. Насколько я понял выражение "Систему целиком контролируют IT-тестами", то тут главное "систему целиком", а не "целиком контролируют". Мысль, что если ресурсов на тестирование мало, то их лучше пустить на интеграционные тесты — проверять систему в сборе, а не по отдельным компонентам.

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

Посыл статьи в целом поддерживаю, но на мой взгляд проблема даже чуть проще: основная масса текстов по юнит-тестированию не даёт хорошего представления о том, что такое этой самый юнит.
Более того, типичные примеры (типа как в статье про тест сложения чисел) наводят на мысль, что юнит — это вот что-то совсем обособленное от всего. А разжевывание концепции моков — на то, что каждая тестируемая часть кода должна быть полностью отгорожена от всех других частей стенами моков.


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


Меж тем, юнит таки безразмерная единица. Это часть кода, но никто (из вменяемых людей) никогда не утверждает, что это должна быть часть определенного размера, определенной связанности, и тому подобное. Единственное разумное ограничение, которое тут выставляется самим процессом юнит-тестирования — это то, что у юнита должен быть определенный формально фиксируемый контракт поведения (а иначе что тестировать-то?).


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


И вот когда люди не пытаются применить жесткие и часто абсолютно необоснованные требования к тому, что может входить в "юнит" — вот тогда с написанием тестов становится всё ощутимо лучше.

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

Статья начала и закончила за здравие. А в середине те же самые 2+2 с кровавым Enterprise.


Посыл статьи полностью разделяю и поддерживаю. Юнит тесты используются для фиксации ожидаемого поведения наиболее эффективно. Нет бесполезной нагрузки на тестирование геттеров\сеттеров, или мокания целых хранимых процедур в базе данных.


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


Утрированно, пример с 2+2. Тестировать 2+2==4 не имеет смысла в силу отсутствия здесь бизнес требований. Что первично — два аргумента для суммирования, или возврат 4 — не оговорено.
Формализуя бизнес-требование — нам нужно складывать два числа — уже можно написать первый полезный тест, который фиксирует кол-во аргументов.
И при создании такого теста, мы как разработчики задумываемся — сегодня два аргумента, есть ли шанс что завтра будет 1 или 3. Шанс есть. Но требования нет. Значит имеет смысл за дешево вложить в архитектуру точку роста на случай изменений и не затачиваться внутри на два поля в структуре. Но и без фанатизма.
Затем приходит бизнес-требование — хотим вычитание. В этот момент — не надо копировать тесты сложения и изменять. Куда полезнее, задаться вопросом — что общего между двуми требованиями, а в чем разница — и обобщить реализацию, например выделив функтор. Немедленно закрепив бизнес-требование — юнит тестом, которое применяет функтор к паре чисел.
Итого — у нас реализовано два бизнес -требования, у нас два юнит-теста, которые надо поддерживать, и наша система уже готова к расширению кол-ва аргументов, или появлению новых операций. И вот эта готовность архитектуры бизнес-логики к непредсказуемым изменениям и есть основная полезность юниттестирования.

А в середине те же самые 2+2 с кровавым Enterprise

не совсем, не 2+2, а тогда уж x + 2, легко расширяемый до ax2+bx+c… и далее…
пример элементарный, но принцип очень важный — как покрывать тестами функционал содержащий зависимости и интеграции — применим к любой сложности.

Проблема в том, что бизнес-требование редко приходит в виде "нам нужно складывать два числа", чаще в виде "пользователь при регистрации в системе должен указать уникальное входное имя". Функционал оказывается размазанным по слоям (от UI до БД) и юнит-тестов на одно бизнес-требование нужно писать много больше одного.


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

Полностью согласен. Но это уже искусство — разделить бизнес-требование, написанное человеком для человеков, на элементарные части, которые могут быть проверены юнит-тестами. IMHO, такого проверяемого юнит-тестами кода в моих приложениях может быть процентов 10-15 (у других может и 80-90-100%, но у меня вот такой опыт). В основном, сложная логика. Настолько сложная, что нужно применять TDD, чтобы понять, что должно получиться в результате.

Я понимаю, что это сделано для демонстрации принципа, но поднимать в юнит тесте Spring контекст для проверки конкатенации в контроллере это чересчур. Юнит тесты должны быть кроткими и быстрыми. Конкатенацию надо выносить в отдельный сервис, покрывать его тестами, а в юнит тесте контроллера проверять, вызывается ли данный сервис. Причём условие Mockito.atLeastOnce() допускает многократный вызов сервиса, такое поведение надо дополнительно обосновывать.
поднимать в юнит тесте Spring контекст для проверки конкатенации в контроллере это чересчур.

вы про WebMvcTest? но это уже стандарт гонять тесты контроллера через MockMvc, вы поступаете иначе?
условие Mockito.atLeastOnce() допускает многократный вызов сервиса

согласен, нужно указать точнее
вы про WebMvcTest? но это уже стандарт гонять тесты контроллера через MockMvc, вы поступаете иначе?

Мы убираем всю бизнес-логику из контроллеров и в юнит тестах контроллеров проверяем только вызовы сервисов. Проверку поведения самого контроллера, как элемента инфраструктуры, мы выносим в интеграционные тесты. Тем самым экономится время разработчиков — при работе им достаточно регулярно прогонять быстрые юнит тесты, медленные интеграционные тесты выполняет CI-система.
ок. спорный вопрос.
при детальном покрытии быстрыми юнит тестами — долгие и тяжелые интеграционные могут вообще не понадобиться.
юниты дают несравнимо больше профита, и накапливают ценность в перспективе.
Юнит тесты фиксируют поведение бизнес-логики, внутреннего слоя приложения, тогда как интеграционные тесты фиксируют ожидания от внешней инфраструктуры, необходимой для предсказуемой работы бизнес-логики. И то и другое одинаково важно, и то и другое даёт профит. Как быть, если вдруг поменяется таблица в базе или контроллер начнёт отдавать неизвестный клиенту HttpStatus? Но проверять это в юнит тестах не рационально из-за медлительности такого рода тестов. Такое лучше проверять в интеграционных тестах.
Увидев такое, и перечитав для верности дважды, соискатель восклицает: “Что за лютый бред?..” Ведь, у него в коде практически нет методов, которые все необходимое получают через аргументы, а затем отдают однозначный результат по ним. Это типичные утилитарные методы, и они практически не меняются. А как быть со сложными процедурами, с внедренными зависимостями, с методами без возврата значений? Там это подход не применим от слова “совсем”.

Печально, если такая ситуация. По хорошему, все функции с нетривиальной логикой стоит делать такими, чистыми: кроме аргументов ничего не используют, кроме возвращаемого значения ничего не делают. Ну ладно, не все — но большинство точно.
Тогда "грязные" функции почти не будут содержать логику, и покроются общими интеграционными тестами.

не вижу и близко смысла сравнивать тяжелые, долгие и сквозные интеграционные тесты, где на вход даешь A и на выходе проверяешь… G
с быстрыми, легкими и детальными юнит тестами, позволяющими точечно покрыть всю цепочку: A, B, C, D, E, F, G
иногда и дизлайк — залог правоты :)
UFO just landed and posted this here
UFO just landed and posted this here
Как тестирование модуля может зафиксировать ожидаемое поведение системы?

Проблемы проявятся, если сервис будет возвращать json, а не string. Любое добавление поля приведет к тому, что нужно будет переписать каждый подобный тест.


это не проблема, а суть фиксации поведения. Некогда бизнес потребовал string, это реализовали и зафиксировали тестом. Теперь если теперь некий разработчик изменит это поведение — тест упадет, сигнализируя о несовместимости с прежними требованиями.
Если это ошибочно — уф, спасибо тебе, тест!
Если сознательно — меняется тест, фиксируются актуальные требования = ожидаемое поведение системы.
UFO just landed and posted this here
UFO just landed and posted this here
Вот только никакой ошибки там не было.

ошибка все-таки была, точнее недочет, и не по бизнесу, а по коду тестов.
проверять xml как строку, согласитесь спорное решение. оно вам отомстило :)
Батлы ленивых с трудолюбивыми.

Тест кода — военнослужащий отдела технического контроля – готов по тревоге выполнить приказ.
Отказ от тестов выглядит как отказ от мобилизации.

Чтобы получить выгоды (времени, денег, нервов) от тестов:
— код нужен простой и «тупой», чтобы тесты были такими же, чтобы их можно было автоматически создавать.
— повторять себя — создавать сложные сценарии общением уже протестированных простых объектов, методов.
— на новые сложные сценарии придумывать простые, дешёвые тесты.

На каких-то участках кода придётся использовать спецназ — креативных, дорогих.
Но большая часть армии тестов: дешёвые, малообученные наёмники.
да, и при внесении изменений в большой проект, очень сильно обнадеживает, если за каждый юнитом кода следит маленький, узкопрофильный, но зоркий и строгий солдатик :)
Sign up to leave a comment.

Articles