- PVSM.RU - https://www.pvsm.ru -
Эта статья — не теоретическое руководство по написанию тестов и не how-to по использованию инструментария в определенном стеке, а ряд популярных вопросов, иногда даже у многих не сформировавшихся, на которые я постараюсь дать ответы. Источником этих вопросов служат коллеги, люди с обоих сторон в собеседованиях и знакомые, а ответы будут субъективными, краткими и не исчерпывающими, основанными на чужих материалах и своём опыте. Целевая аудитория статьи – разработчики, которые с определенным успехом пишут или хотя бы пытались писать тесты, но испытывают определенные сложности в их написании.
Я старался не привязываться к конкретному языку, чтобы увеличить охват читающих, однако оговорюсь сразу, что работаю в экосистеме PHP с использованием PHPUnit, и поэтому некоторые мои выводы могут быть неподходящими для других экосистем. При подборе вопросов и написанию я ориентировался на множество докладов и статей и пользовался ими как референсом.
Поводом для написания послужила недавняя статья «PHPUnit. Мокаем Doctrine Entity Manager [1]» от trawl [2], некоторые проблемы из которой я тоже разберу.
Список вопросов:
Я видел очень множество мнений на этот счёт, но сам пришел к своему собственному: надо ориентироваться на потребности бизнеса.
Для сделанного за пару часов на коленке куска кода тесты не нужны. С другой стороны, для крупного корпоративного проекта на сотни человеколет обязательны все популярные виды тестов. А все, что находится между этими полюсами, надо рассматривать как частный случай, оценивая стоимость тех или иных видов тестирования: с тестами она должна быть меньше, чем без них. Лично я пишу smoke тесты даже для крошечного CRUD-проекта длинной в пару недель, потому что уже на такой дистанции они приносят пользу и уменьшают стоимость разработки.
Плюсы тестирования, кратко и тезисно:
Так что если ваши проекты не являются одноразовыми скриптами из несколько файлов, то я однозначно рекомендую писать тесты. Поэтому вопрос из заголовка стоит переформулировать: «В каком объеме писать тесты, и какие?» Об этом далее.
Это очень спорный вопрос, поэтому я еще раз оговорюсь, что рассказываю исключительно про своё субъективное мнение, при этом я пытаюсь это сделать максимально деликатно.
Написание тестов – такая же часть работы над задачей, как её продумывание, кодирование или отладка. Тесты – такой же код, как бизнес-логика или контроллеры. Но про них у вас ведь не возникают вопросы? Вы же не ставите отдельную задачу на написание контроллера и не согласуете её с менеджерами?
На написание модульных и интеграционных тестов дополнительное время выделять не нужно. Тесты пишутся в рамках основной задачи. Технический лидер проекта, как владелец репозитория проекта и компетентное лицо, сам решает, как, какие и с каким покрытием тесты писать, ориентируясь на задачи бизнеса, не раздувая, а сокращая стоимость разработки.
Однако может случиться, что заказчик не верит в тесты и считает, что с их помощью вы не экономите, а лишь тратите время. Он платит деньги и ставит условия, с таким же успехом он может запретить вам использовать темную тему в вашем IDE или приказать использовать пять пробелов в качестве отступа. И если условия не обсуждаются или обсуждение зашло в тупик, то ваш выбор – принять такие условия или отказаться. С советующими последствиями.
Оговорюсь, что последнее верно только в том случае, если вы умеете писать тесты, адекватно оцениваете затраты на них и предполагаемую окупаемость. Если вы хотите научиться их писать за счет работодателя на краткосрочном проекте, а он против, то я на его стороне.

Можно посмотреть на пирамиду тестирования Майка Коэна: нас, как разработчиков, интересует два самых низших уровня тестирования. Максимальные усилия стоит отдать именно модульному тестированию, оно дешевле всего (под дешевизной я понимаю время, затраченное на разработку и поддержку), такие тесты очень просто внедрить, очень просто запустить, они быстро работают.
Но всегда ли модульное тестирование применимо или целесообразно? Я считаю, что для примитивного CRUD такие тесты займут слишком много времени при малой отдачи и ничего не гарантируют. Попробуйте протестировать репозиторий (Repository в DataMapper) и потом ответьте на вопрос, что нам это дало. А вот для разнообразных калькуляторов такой подход будет идеальным.
Всегда старайтесь писать модульные тесты там, где это возможно, но не тестируйте то, что тестировать бесполезно.
Как протестировать совместную работу бекенда и фронтенда, если это разные проекты на разных стеках? Так же, как бекенд и мобильное приложение: это системное тестирование, и оно должно интересовать QA и DevOps инженеров, а не разработчиков (ну только если у вас не настоящий беспощадный скрам, где единственная доступная роль – фуллстек разработчик).
Кроме того, системное тестирование самое дорогое и по разработке и поддержке, и по времени, и по инфраструктуре. Решение вопросов, связанных с этим, уже лежит за рамками компетенции «линейных» разработчиков и вопрос о его применении и объемах должен решаться техническим директором и лидерами направлений вместе с бизнесом.
Иногда тесты писать сложно по причине неготовности к ним кода. Нагромождение моков, где одни из них отдают другие, тоже являются следствием.
Не используйте реестры, синглтоны, локаторы, вы сильно усложняете себе этим жизнь. Это и было основной претензией к статье, на которую я ссылался выше. Используйте DI, и внедряйте в сервисы сразу необходимые репозитории. Соблюдайте закон Деметры, чтобы избежать цепочек моков. Попробуйте немного поработать с применением методологии TDD.
Относитесь к тестам, как к коду первого сорта, соблюдая такое же высокое качество кода, как и в бизнес-логике. Низкое качества кода тестов со временем понизит производительность труда.
Никак. Мы тестируем контракт – интерфейс, предоставляемый модулем другим модулям. Нам не нужно тестировать внутреннюю реализацию, она может меняться.
Что делать, если внутри теста какая-то сложная логика, и тестирование контракта превращается в большое количество входных данных, где внутри происходит что-то непонятное? Нужно рефакторить код, разнося его по различным классам, где такие приватные методы станут публичными, на них уже и писать модульные тесты. И если бы тестов не было, такие места было бы сложно обнаружить, что увеличило бы стоимость поддержки подобного кода.
Используйте фикстуры. Изучите популярные инструменты в вашем стеке.
Не пишите большие универсальные паки фикстур, используйте минимальное их количество. Если вам нужно определенное состояние объектов для ваших тестов, потом не переиспользуйте такие фикстуры в других местах, где подобное состояние не требуется, иначе поддержка тестов усложнится: изменение их для одних тестов сломает другие и потребует дополнительного времени.
Существуют интеграционные тесты, которые проходят не полный цикл от запроса до ответа, а проверяется взаимодействие классов лишь внутри компонента. Если в них нет прямой работы с базой, то можно сделать набор данных и/или моков, который войдет на вход. В таких случаях можно избежать использования фикстур и сократить сложность и время выполнения таких интеграционных тестов до уровня модульных.
Интеграционное тестирование – это один из уровней тестирования по степени изоляции. Функциональное тестирование – тестирование на соответствие требованиям. Эти определения лежат в разных плоскостях, и между ними нельзя ставить знак равенства, однако на практике в разговорах между разработчиками под ними понимают одно и то же, хоть это и не корректно.
Внешние зависимости мы заменяем на моки. Не только для модульных, но и для интеграционных тестов.
Например, мы используем HTTPS клиент для обращения к какому-нибудь API через класс Guzzle. Если создавать экземпляр такого класса внутри тестируемого класса, то его будет сложно подменить, но решение будет очень простым: мы внедряем такой клиент в конструктор, а при тестировании заменим его моком.
Современные инструменты разработки способны отследить расположение тестов или тестируемых классов, если вы используете стандарты наименования. Для простоты навигации можно использовать комбинацию клавиш Ctrl + Shift + T в продуктах JetBrains, к тому же если теста не существует, то вам предложат его создать и сделают каркас.
Иногда нужно несколько разных тестовых классов или методов для предмета тестирвоания, в этом случае надо помочь IDE, например добавить аннотацию @covers в случае PHPUnit.
TDD – это одна из методологий ведения разработки, в котором итеративно и обязательно сначала пишут тесты, а потом код. Попробовать её однозначно стоит, она научит писать код, который хорошо тестируется. Однако широкого употребления такой подход не нашел, у него есть проблемы. Одна из таких проблем – вы пишете тесты, которые с классическим подходом писать не стали бы. И такие тесты тоже надо поддерживать, тратя на это время, а польза от них небольшая. Например, тесты на сеттеры или тесты на классы, где ничего не происходит кроме вызова зависимостей. Такие места лучше проверять интеграционным тестированием.
Инструменты статического анализа – это очередной способ улучшить качество вашего кода. Они частично пересекаются с тестами по результату, но зачастую находят и то, что тесты не нашли, особенно при слабом покрытии. Их несомненный плюс – вы видите результат работы сразу же в IDE и получаете обратную связь мгновенно.
Это не значит, что их достаточно оставить только на IDE, я настоятельно рекомендую использовать их и на CI, чтобы видеть потенциальные ошибки и не вмержить такой код.
Также рекомендую добавить в IDE и CI поддержку инструмента для проверки code style. Ошибки часто прячутся среди небрежно написанного кода, а такие инструменты позволяют находить такие участки.
Автор: OnYourLips
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/testirovanie/326131
Ссылки в тексте:
[1] PHPUnit. Мокаем Doctrine Entity Manager: https://habr.com/ru/post/452244/
[2] trawl: https://habr.com/ru/users/trawl/
[3] Фаулер о пирамиде тестирования: https://martinfowler.com/bliki/TestPyramid.html
[4] Тестирование. Фундаментальная теория: https://habr.com/ru/post/279535/
[5] Антипаттерны тестирования ПО: https://habr.com/ru/post/358178/
[6] Простые рецепты хороших юнит тестов (Константин Заикин / Академия Яндекса): https://youtu.be/ZyGZjpxF9Fo
[7] Зачем и как писать качественные Unit-тесты (Алексей Солодкий / Badoo): https://youtu.be/Rz4S0v7K7Ho
[8] F.I.R.S.T. принципы: https://howtodoinjava.com/best-practices/first-principles-for-good-tests/
[9] TDDx2, BDD, DDD, FDD, MDD и PDD, или все, что вы хотите узнать о Driven Development: https://habr.com/ru/post/459620/
[10] Как посчитать ROI от автоматизации тестирования с Selenium?: https://m.habr.com/ru/company/otus/blog/461257/
[11] Источник: https://habr.com/ru/post/462551/?utm_campaign=462551&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.