CTO Vetmanager, PHP Developer, Ironman 70.3

Часть 3. Реальная жизнь

Тезисы

  1. Юнит тестам нужен код с “особенностями”
  2. Что делать с легаси, эффективно ли писать на него юнит тесты?

Навигация по серии статей:

Как интро для статьи я использую цитату Сергея Капралова в одном из чатов.

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

Юнит тестам нужен код с “особенностями”

Хорошо писать тесты для нашей функции:

    function usageLevel(float $usagePower): string

А напишите unit тесты вот на такое:

<body>
    <?php 
        $results = mysql_query(
            'SELECT * FROM customers ORDER BY name'
        );
    ?>
    <h2>Customers</h2>
    <ul>
    <?php while ($customer = mysql_fetch_assoc($results)): ?>
        <li><?= $customer['name'] ?></li>
    <?php endwhile; ?>
    </ul>
</body>

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

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

Это отдельная и очень большая тема как писать так, чтобы код можно было тестировать. Конечно же это про SOLID. Особенно цените SRP и DIP. Это не только мое мнение.

Есть отличная книга про тестирование “Unit Testing Principles, Practices, and Patterns / Vladimir Khorikov”. В ней можно найти не только советы по написанию тестов, но и хорошие советы по рефакторингу кода.

Для php в этом репозитории собрали много советов по тестированию, обязательно посетите его.

Что делать с легаси, эффективно ли писать на него тесты?

Ситуация: вы попали на проект с плохой архитектурой, как на сниппете выше. Вы должны обеспечить дальнейшее развитие проекта. В проекте нет никаких автотестов.

Скажете что таких проектов не существует?

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

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

Приведу цитату автора из комментариев к статье:

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

(с) Sergei Buvaka(Android Developer at Tinkoff Bank)

Попасть на такой проект это не наказание, а даже очень наоборот. Классный видос на эту тему. Иметь живых пользователей очень важно(вспомним Карла Маркса) и таких проектов, обычно с этим проблем нет.

Поехали!

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

Допустим, вам выделили 20% бюджета(или часть каждого спринта) на устранение техдолга. Если вы будете пописывать немного юнит тесты, какой-либо эффект вы почувствуете еще очень не скоро.

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

В книге Владимир Хориков приводил пример, когда команде поставили условие 100% покрытия кода тестами. Команда запускала все методы классов в блоках try catch и выполнила задачу.

Учитывайте интересы бизнеса. Это самый важный пункт. Возможно вашему бизнесу тесты нужны максимально быстро, тогда мои советы вам будут вредны. Сначала интересы бизнеса, потом отдельно процесса разработки.

Начните с интеграционных или end-to-ent тестов. Так советует и та самая книга про тестирование и мой опыт. Выберите 1 длинный позитивный сценарий. Например, покупка товара в магазине вместе с оплатой. Напишите тест на него. Один такой тест может вам дать больше чем сотни юнит тестов для обеспечения качества. Быстро пишутся тесты апи методов. В нашем проекте силами практикантов мы написали простую дергалку всех end поинтов проекта с проверкой на статус ответа. Она часто нас выручала и мы усложняли тесты некоторых end поинтов.

Автоматизируйте запуск тестов. Настройте Jenkins, gitlab ci, github action для запуска ваших тестов после каждого коммита. Встраивайте тесты в процесс разработки.

Запуск тестов - одна команда. Это очень важно, команда будет сопротивляться новым веяниями, если будут какие-то сложности с запуском тестов, вам будет еще сложнее. Используйте Makefile, Docker, selenoid, удобные фреймворки для тестирования.

Общайтесь с командой о тестировании. Написание тестов - это настоящее искусство. Этому можно учиться бесконечно.

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

Я писал про такие ситуации в статье

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

А вот мнение Владимира Хорикова:

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

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

Типичный комментарий под статьей про тесты с хабра:

Расскажите как, потому что сколько я не пытался заставить юнит тесты работать всегда получается одно и то же: — Любой рефакторинг превращается в борьбу с тестами, время на рефакторинг увеличивается в разы. — Тесты ломаются от чего угодно, только не от багов. Это из-за моков, но как писать юниты без них непонятно.

Это очень большая тема, конечно, еще есть о чем писать. Но давайте закончим на этом.