Как стать автором
Обновить

Комментарии 31

Антипатерн X
Называют тест сценарии — юнит тестами.

Антипатерн X+1
Используют тесты в широком смысле один раз — во время создания функциональности.

Антипатерн X+2
Пытаются покрыть юнит тестами все возможные сценарии (если сценарий не существует — надо его выдумать).

и т.д.

Увы в плане адекватного использования технологии юнит и шире автоматическое тестирование осталось непаханым полем.
Не статья, а целый гайдбук, почему тесты — это хорошо, и как делать их правильно.
Большое спасибо.
Спасибо за перевод!
Работаю на проекте, в котором явно видны большинство перечисленных антипаттернов, и хочу искренне пожать автору руку — он явно пролил не один литр своей крови в похожих местах за годы опыта!

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

И все же из статьи не понятно куда относить тесты из разряда:
Тестируемый объект: SerivceA(ServiceB, ServiceC)
Тесты: мокаем ServiceB, ServiceC, тестируем публичный интерфейс SerivceA. В моках имитируем все известные поведения ServiceB, ServiceC. Отдельно таким же способом тестируем ServiceB и ServiceC.

А таких тестов подавляющее большинство.

Из личного опыта, подобные тесты + юнит тесты покрывают 99% всех возможных проблем с функционалом. Если будут какие-то зашкварные проблемы с интеграцией, конфигурацией или деплойментом они будут выявлены во время UAT так ли иначе. Можно попытаться это протестировать, но:
1. Полноценный интеграционный тест это сложно, очень сложно
2. Это медленно
3. Это false-positive срабатывания (что-то отвалилось во время тестирование, то что ты не контролируешь)
4. Это никакой гарантии что в продакшине все будет нормально

Я конечно пишу парочку интеграционных тестов чтобы удостоверится что глобально все работает и что все сервисы/компоненты поднимаются и взаимодействуют, что dependency injectction нигде не тупит. Но не более того — не стоит оно потраченного времени, поэтому у меня:
99% — юнит тесты и тесты описаны выше
1% — интеграционные тесты
Интеграционный тест с блекбоксом и мокингом? Вообще говоря писать для маломальски сложного объекта что-нибудь проще интеграционного теста с мокингом — очень затратное занятие. Если сосредотачиваться на обязательных модульных тестах всех новых функций, то код, готовый к модульному тестированию, будет выглядеть просто ужасно. Это наверное самое неприятное в модульном тестировании ооп кода.

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

Таких функций не бывает, они по-любому будут дергать вспомогательные функции, функции нижнего уровня. Вопрос только как — через интерфейсы или напрямую. Первое легко и просто тестируется, второе — 80% времени будете тесты писать.
Посмотрите в сторону CDC (Pact)
Похоже на поведенческие юнит тесты на моках, т.н. mockist подход — один из двух подходов (школ) в юнит тестировании описанных в статье Фаулера Mocks Aren't Stubs. Я в основном использую моки. Классические тесты использую в основном для сервисов хранения (запись/чтение в память, файл).
Моя «пирамида» примерно такая-же 90% юнит + 10% «интеграционных» тестов. При этом мои интеграционные тесты (в отличие от классических) проверяют исключительно сборку объектов — возврат результата из фабрики + assert'ы для зависимостей внутри классов; что делает их очень быстрыми и непротухающими.

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


Начнём с терминологии. Тесты не делятся на "юнит", "интеграционные" и "gui". Интеграционными он называет компонентные тесты, что является довольно распространённой ошибкой. Интеграционный тест проверяет взаимодействие модулей. Компонентный просто проверяет интерфейс компонента и ему плевать использует ли тот какие-либо другие компоненты код капотом или нет. На картинках у него правильно написано UI (ещё правильнее — e2e), что несколько шире, чем GUI. Например, CLI или Speech UI — тоже относятся к UI. Тестируются они по одним и тем же принциам — поднимается приложение и эмулируются действия клиента (это может быть как пользователь в случае UI так программа в случае API). В общем, из-за бардака в классификации далее он делает кучу неверных выводов. Для упорядочивания терминов рекомендую статью: Концепции автоматического тестирования. Там же можно увидеть и правильную пирамиду тестирования:



Заметьте, что тут ни слова про e2e тестирование, так как эмуляция пользовательских событий происходит в самых нижних компонентах и высокоуровневые тесты не зависят от деталей реализации низкоуровневых. Это делает тесты менее хрупкими и более простыми и быстрыми, чем e2e, при сравнимом качестве покрытия. А юнит тесты — не более чем вырожденные случаи компонентных, так что противопоставлять их нет никакого смысла.


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


Теперь пройдёмся по "антипаттернам"...


Антипаттерн 1. Модульные тесты без интеграционных
Антипаттерн 2. Интеграционные тесты без модульных

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


Антипаттерн 3. Неправильный тип тестов

Довольно странные выводы о нужности/ненужности e2e тестов. CLI — такой же UI как любой другой и может быть протестирован e2e тестами. Я бы сказал, что многие CLI утилиты вполне можно покрывать только e2e тестами, так как они в этом случае дадут хорошее покрытие минимумом усилий. Подготовили пачку CSV и JSON, прогнали их все через утилиту и всё. Вот с GUI всё сложнее, там много входов, выходов, да ещё и динамическое состояние имеется — тут одними только e2e не обойтись, иначе будет комбинаторный взрыв. Ну а пресловутая "бизнес-логика" — это вообще не понятно что. Что это за мифическое приложение такое без "бизнесс-логики"?


Дальше у меня остались только мелкие придирки, но не осталось времени, так что всем зелёных билдов.)

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

Ну а пресловутая «бизнес-логика» — это вообще не понятно что. Что это за мифическое приложение такое без «бизнесс-логики»?
Есть довольно широкий класс малопродуманных полуполезных говноприложений.

Простите, как это не выработали терминологии, если есть, например, тот же ISTQB ?

Тут другая проблема, с легкой руки появляются такие вот синонимы общепринятых терминов.

К сожалению habr многими потом воспринимается как первоисточник и можно услышать вопросы на собеседовании чем отличается компонентное тестирование от модульного или что такое операционное тестирование. why?

ps статья полезная.

Что статья полезная — это бесспорно! :)


Ну, и насчет упомянутых вами вопросов — тоже согласен.


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


Но это не отменяет того факта, чтт всё это следует обсуждать и вычленять полезное. :)

В этом плане сдача ISTQB CTFL очень полезна в плане того, что позволяет расположить по полочкам всю основную терминологию. P.S.: в этом году должно выйти его обновление, там будут некоторые изменения и дополнения.
Скажите, а если приложение использует full view от Tier1-карриеров (нескольких) для своей работы, то интеграционный тест подразумевает поднятие своих Tier-1 карриеров со своим интернетом, или можно обойтись меньшей кровью? Какой?

Очень странно написано про TDD


Написание тестов перед кодом подразумевает, что вы уверены в окончательном API, а это не всегда так.

Это утверждение попросту ложно. API можно менять на ходу, прямо в процессе написания кода и TDD этого не запрещает.


С практической точки зрения тому же стартапу рано слепо следовать TDD. Если вы работаете в стартапе, то ваш код может меняться так быстро, что TDD мало поможет.

Почему TDD не поможет? Если добавляются новые классы, а старые удаляются — просто написать новый код по методике TDD, а старый код со старыми тестами удалить. Если речь об изменениях в старых классах — поправить тесты, а потом поправить код, тоже согласно TDD. Создаётся впечатление, что автор понимает TDD как-то по своему.


Вы можете даже отбросить код и начинать сначала, пока не напишете «правильный» вариант.

И TDD поможет это сделать.


Написание тестов после кода реализации — совершенно правильная стратегия в таком случае.

Написание тестов — вообще правильная стратегия. Но непонятно, почему не применить тут TDD.


В общем неприменимость TDD в стартапах это как раз миф, порождённый непониманием того, что такое TDD и зачем эта методика нужна.

Представьте, что Вы хотите съездить… ну, скажем, в Париж. Из Москвы. Идете и покупаете билет на поезд — ведь до Парижа он вас довезет, верно? Отлично. Начинаете изучать сайты путешествий и выясняете, что самолет стоит столько же, а летит куда быстрее — чорт, Вы сдаете билет на поезд и покупаете на самолет. Продолжаете изучать сайты и выясняете, что с пересадкой не сильно дольше, но сильно дешевле — чорт побьери, Вы сдаете прямой билет и покупаете с пересадкой. Тут оказывается, что жена боится самолетов — проклятье, снова появляется билет на поезд. А детям-то неплохо бы по ходу поездки показать Минск, Брест, Варшаву и Берлин — три тысячи чертей, Вам нужен не прямой билет, а с пересадками. Так вот автор и говорит, что TDD хорош, когда «вы уверены в окончательном API», тогда можно писать тесты на этот API и они меняться не будут. В противном случае по ходу изменения требований тесты будут неоднократно переписываться, и масса сил уйдет впустую. «миф, порождённый непониманием» — что такое TDD, как раз все понимают. Не все понимают, что (как и у всякого инструмента) у TDD есть границы применимости.

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


И уже во вторую очередь хочется сказать, что если в ваших рассуждениях TDD заменить на программирование — логическая цепочка останется прежней. При изменении требований код точно так же будет постоянно меняться и пользуясь той же логикой можно сказать, что код можно писать только тогда, когда требования меняться не будут.


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

TDD применяется для того, чтобы получить тесты для конечного API
Нет, ни я ни автор такого не говорили.
При изменении требований код точно так же будет постоянно меняться и пользуясь той же логикой можно сказать, что код можно писать только тогда, когда требования меняться не будут.
Хорошо бы, но и это утопия :) Я бы сказал, что разработка при недоопределенных требованиях — процесс сходящийся, а сходиться ему помогают как написание кода (что обязательно) и фидбеки и пр., так и написание тестов, но писать тесты с самого начала (как предлагают апологеты и консультанты) является нехорошим излишеством, не приносящим никакой пользы, окромя вреда. Я, кстати, предпочитаю BDD, несмотря на мизерную разницу.
Нет, ни я ни автор такого не говорили.

Не говорили, но это следует из вашего утверждения, что TDD не применимо в кейсах с меняющимися требованиями потому, что тесты будут многократно переписаны, а значит масса сил уйдёт впустую. Если силы, потраченные на написание тестов ушли впустую, значит в написании этих тестов не было смысла. Тем не менее вы считаете, что применять TDD, когда требования есть — имеет смысл. А раз TDD теряет смысл, когда нельзя сразу написать тесты к финальному варианту требований, то смысл применения TDD в том, чтобы получить тесты для финального варианта требований.


Хорошо бы, но и это утопия :)

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


писать тесты с самого начала (как предлагают апологеты и консультанты) является нехорошим излишеством, не приносящим никакой пользы, окромя вреда

По какой причине? Как это вредит разработке?

TDD не применимо в кейсах с меняющимися требованиями
Я такого не утверждал.
тесты будут многократно переписаны, а значит масса сил уйдёт впустую
А вот это верно. И именно этим вредит. Но между состояниями «вообще нет требований» и «финальный вариант требований» есть много промежуточных вариантов, когда TDD/BDD может принести пользу. Речь о том, что не нужно относится к этому инструменту как к священной корове или серебряной пуле — «есть границы применимости».
Я такого не утверждал. (что TDD не применимо в кейсах с меняющимися требованиями)

Тогда что вы имели в виду, когда писали во это?


Так вот автор и говорит, что TDD хорош, когда «вы уверены в окончательном API», тогда можно писать тесты на этот API и они меняться не будут.

А вот это верно. И именно этим вредит.

Вредит то, что тесты будут многократно переписаны? То, что код будет многократно переписан — ведь тоже вредит?


Но между состояниями «вообще нет требований» и «финальный вариант требований» есть много промежуточных вариантов, когда TDD/BDD может принести пользу. Речь о том, что не нужно относится к этому инструменту как к священной корове или серебряной пуле — «есть границы применимости».

Вы только что писали, что TDD хорош, когда есть уверенность в окончательном API, а в другом случае — не хорош. А теперь уже есть много промежуточных вариантов. Как в таком случае понять когда надо применять TDD, а когда нет? Как определить границы применимости метода?

Код писать обязательно, без кода нет продукта, а вот тесты — нет, поэтому многократное переписывание кода совсем не равно тому же для тестов. Тесты верифицируют код, а верифицировать откровенно промежуточные варианты кода смысла нет. А вот что такое «откровенно промежуточные» — это решать каждому, так что границы применимости метода (как и любого инструмента) вещь субъективная. Я согласен с автором, что TDD/BDD можно применять, начиная с некоторой степени стабильности кода/контрактов/требований и пр. Вы несогласны — Ваше право.
Код писать обязательно, без кода нет продукта, а вот тесты — нет

Тут я хотел бы процитировать Страуструпа.


Программы, которые не тестировали, НЕ РАБОТАЮТ.

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


Тесты верифицируют код, а верифицировать откровенно промежуточные варианты кода смысла нет.

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


Я согласен с автором, что TDD/BDD можно применять, начиная с некоторой степени стабильности кода/контрактов/требований и пр. Вы несогласны — Ваше право.

Дело не в согласии или несогласии, дело в аргументах, которые автор приводит в защиту своей позиции.

Тут уже надо разделять:
  • Правильность методологии. «приятный побочный эффект» требует заметных ресурсов, так что максимализм в применении TDD не оправдан.
  • Практическое применение методологии. Например, к нам на собеседования приходит много людей, которые не пишут тестов (от «совсем» до «местами есть»). И основная причина — бизнес не дает денег/времени на тестирование. Но ПО-то РАБОТАЕТ, несмотря на Страуструпа. Конечно, не всегда ожидаемо, рефакторинг затруднен и т.д. — последствия всем понятны, но для бизнеса приемлемы. Вот такая загогулина.

«приятный побочный эффект» требует заметных ресурсов, так что максимализм в применении TDD не оправдан.

Если TDD применяется для получения "приятного побочного эффекта", то ваше утверждение верно.


Например, к нам на собеседования приходит много людей, которые не пишут тестов (от «совсем» до «местами есть»).

Да, такое часто бывает. Но тут поднимается не вопрос применимости TDD в стартапах, а вопрос применимости TDD вообще.


И основная причина — бизнес не дает денег/времени на тестирование.

Насколько я понимаю в данном случае под тестированием имеется в виду применение юнит тестов. И вот это, конечно, странно. С таким же успехом бизнес мог бы не давать денег и времени на применение ООП или систем сборки. Бизнес как раз и нанимает разработчиков для того, делегировать им вопросы разработки продукта. Бизнесу нужен продукт, как он сделан бизнесу не очень важно.


Но ПО-то РАБОТАЕТ, несмотря на Страуструпа.

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


последствия всем понятны, но для бизнеса приемлемы. Вот такая загогулина.

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

вопрос применимости TDD в стартапах
имеется в виду применение юнит тестов
И приходят не только (да и не столько) из стартапов, и тесты не только юнит, а вообще всякие. Вопросы разработки бизнесом обычно делегируются, а вот деньги — не очень. Тестировщиков иногда не бывает вовсе, как правило один-два. Все держится силами службы поддержки и самими разработчиками. Но это мы уже уклонились в сторону правильного построения процессов разработки в целом — как правило, ресурсы на «правильность» находятся только у крупных компаний, остальные живут как могут (что говорить — до сих пор в вакансиях пишут знание VCS, хотя это уже должно быть по умолчанию).
сори за некропостинг, очень хочется ответить
Если представителю бизнеса принести меморандум с описанием последствий и попросить поставить подпись, что он с ними ознакомлен, и что его всё устраивает, то он этот меморандум не подпишет никогда. Скорее всего он будет в шоке от того, что весь цирк, который он наблюдает годами — не является результатом какой-то цепочки нелепых случайностей и ошибок, а запланирован с самого начала.

а) как говорится «если не видно разницы, зачем платить больше?», иными словами зачастую (в моем опыте) бизнес видит что код написан (а если разработчик не очень квалифицированный — то код написан будет быстро, но не протестирован совсем, и уж тем более без юнит-тестов) то можно сказать что это «все готово», вот приходит Вася и пишет быстро-быстро код, и говорит что «все готово» заказчику, конечно если в другой раз к нему придет Петя и скажет что на аналогичный функционал потребуется времени = сколько в тот раз потратил Вася плюс еще время на напсиание юнит-тестов, то заказчику будет резонно не понятно почему вдруг Петя больше времени потратит, а то что у Васи код не протестированный — заказчик разумеется успел забыть (и сколько времени он потом фиксил баги и перетестивал — он не суммирует, он только срвнивает с первоначальной оценкой, не всегда — но бывало дело).
Поэтому 1й смысл не написания авто-тестов — сэкономить время на их написание, вмысле на тесты вообще и авто-тесты в частности.
Да я (Петя) как опытный разработчик могу заказчику сказать «времени на тесты выделяем, зато будет надежней» но заказчик может не согласиться и будет требовать как раз таки побыстрей хоть что-то, а если баги — «ну потом пофиксим» (реальные случаи)

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

А уж стартапы, где профит не известен — экономят на всем — это точно, и на юнит-тестах и на ручных. И их точку зрения можно понять, и как разработчик — незачем идти против них, мол говорить «я не буду без юнит-тестов» — ну не дадут вам времени на них — увильняться сразу чтоли?

Если просуммировать
Вот реальная логика заказчика, когда ему говоришь «на тесты нужно доп время»
заказчик думает:
а) не пишем тесты, не тестим руками — сэкономим время
б) не пишем тесты, тестим руками (кто тестит? иногда сам заказчик, иногда юзеры, иногда бета-тестеры) — сэкономим время
в) пишем авто-тесты — теряем время, и Особенно если требования поменялись буквально до написания кода (проблема не выяснения требований, а изменения хотелок, ну там баланс другой, алгоритм нужен другой, старое уже не нужно… бывает не сразу — бывает после 1й итерации, и там важные не баги частных случаев, а работа пары обычных кейзов чтобы пощупать и выбрать то или не то)

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

А еще откровенность: несколько заказчиков говорили даже на баги «вот баги редкие — значит можно не чинить», т.е. не просто времени не тратить на продумывание вариантов и на тестирование — заранее… а вообще наплевать, даже когда очевидно что баги есть, потому что касаются они неочевидных кейзов, а главное чтобы работали 1-2 обычных случая чтобы условнеы 99% юзеров видели «работоспособность»

Да и сами эти заказчики вполне успешны вцелом, т.е. сосредотачиваться на частных случаях — можно закопаться, главное чтоб была видимость что все работает, когда работает только в 99% случаев, 1% можно не чинить (или никогда, или потом когда-нибудь, если будут упускаться деньги)

Я разумеется не поддерживаю такую философию, но и не так уж опровергаю, если заказчик ставит требования (а баг или не баг — это требование заказчика) — я как программист его исполню, ну в рамках разумного буду разубеждать пилить баги откровенные, и сам буду тестить если дают время, и писать юнит-тесты если дают на них время, но просто такая откровенность от меня — контр-аргумент тому что «заказчик не подпишется» — очень даже подписываются, иногда явно даже

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

Третий абзац с конца: первое предложение —


… я действительно вам чувствую.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации