Что необходимо учитывать при юнит-тестировании фронтенда
Привет, Хабр!
Обращаем ваше внимание еще на одну новинку, доступную у нас в предзаказе — книгу о юнит-тестировании.
Автор сегодняшней публикации кратко и доступно рассказывает о достоинствах unit testing и TDD на примере фронтенда.
Приятного чтения!
Юнит-тестирование – один из тех важнейших подходов, пользоваться которыми должен каждый разработчик. Однако я видел много проектов, в которых бывает сложно воплотить юнит-тестирование на практике. Тому есть ряд причин. Например, кто-то может сказать, что необходимо сосредоточиться на разработке фич, а написание юнит-тестов в таком случае — серьезная дополнительная нагрузка. Другие отметят, что тестирование кода, написанного у них – нетривиальная задача, поскольку сам код сложен. В обоих этих случаях упускается суть.
Помню такое выражение: размышляя, есть ли у вас время писать юнит-тесты, подумайте, а найдется ли у вас лишнее время, чтобы обрабатывать один и тот же код дважды, из-за багов и проблем, которые в нем всплывут.
Пишем тестируемый код
Для начала давайте определимся, какие распространенные ошибки серьезно осложняют тестирование кода. Если говорить о тестировании клиентской части, то одна из наиболее сложных проблем, известных мне – это тестирование компонентов пользовательского интерфейса. Основная проблема, с которой мне при этом доводилось сталкиваться, такова: бывает, компоненты оказываются слишком «умными» и обвешиваются зависимостями от иных фрагментов кода, например, от вызовов API, загрузки данных, обработки событий, реализации бизнес-логики. Многим разработчикам не нравится писать тесты для таких «тяжелых» компонентов.
Простейшее решение такой проблемы – разделить компоненты на логику и представление. При таком разделении можно тестировать, насколько в компонентах представления соблюдается логика представления, выполняется обработка событий и визуализация, а в компонентах, отвечающих за логику, отдельно сосредоточиться на тестировании бизнес-логики.
Обеспечить тестируемость своих компонентов – также хороший способ убедиться, что они независимы, их удобно использовать многократно и совместно. Это абсолютно необходимо, особенно при совместном использовании компонентов в разных проектах при помощи таких популярных инструментов и платформ как Bit (Github). Как правило, Bit отобразит и протестирует каждый из ваших компонентов в отдельности, а лишь потом отправит их в разделяемую коллекцию, гарантировав, таким образом, что они в самом деле пригодны для переиспользования – иначе, какой смысл в их разделяемости.
Пример: исследование переиспользуемых компонентов React, предоставляемых для совместного использования на Bit.dev
Порочный круг: что будет, если не писать юнит-тесты
Из моего опыта общения с командами, которые не применяют юнит-тестирования как следует (то есть, не используют покрытие тестами в качестве метрики) либо приступили к юнит-тестированию слишком поздно, либо с самого начала, но не научились его практиковать. На то может быть много причин, но я приведу несколько примеров, чтобы вам было проще определиться, на какой из них наиболее похож ваш случай.
Как мы тестируем наш код при разработке
На мой взгляд, если только не придерживаться разработки через тестирование (TDD), то функционал можно разработать при условии, что фронтенд постоянно загружен в браузере. Необходимо сосредоточиться на том, чтобы визуализировать функционал и взаимодействия, происходящие в пользовательском интерфейсе, так как это – суть разработки для клиентской части.
Лишь после этого, когда весь функционал уже работает, акцент смещается на юнит-тестирование.
Вот основная проблема, с которой приходится сталкиваться: написание юнит-тестов – это дополнительный участок работы, выполняемый после того, как разработка функционала завершена. Такой подход приводит к впечатлению, что юнит-тестирование – это дополнительная задача, выполняемая сверх разработки готового функционала.
Из-за такой постановки вопроса как владельцы продукта, так и разработчики относят юнит-тестирование к разряду издержек.
Но, если придерживаться TDD, то все складывается ровно наоборот. Поскольку тестовые случаи пишутся заранее, нам необязательно все проверять, постоянно визуализируя изменения, ведь у нас есть иной способ проверки кода при разработке. В данном случае основной инструмент проверки – написать код, который пройдет юнит-тестовый кейс.
Поэтому я полагаю, что практика TDD является важнейшим этапом, предваряющим практику юнит-тестов.
Как часто прогоняются юнит-тесты
Возможно, вас интересует, как выполнение юнит-теста влияет на написание юнит-тестов. Допустим, например, у вас есть полный набор тестов. Вы прогоняете его время от времени. Поэтому разработчикам нечасто доводится убедиться в его полезности. Кроме того, даже в случаях, когда обнаруживается, что тест проваливается, уже слишком поздно.
Следовательно, выполнять юнит-тесты важно, как минимум, на каждом этапе сборки пулл-реквеста. Придерживаясь такой практики, взятой из DevOps, мы гарантируем, что каждый новый блок кода, который мы добавляем, проходит набор юнит-тестов. Даже если существует необходимость изменить конкретный кейс, разработчик сделает это до акта слияния кода.
Насколько часто измерять покрытие кода тестами
Как и в случае с выполнением тестов, это измерение также является важным как в психологическом ключе, так и в качестве практики для разработчиков, позволяющей судить, достаточно ли тестовых случаев они готовят, чтобы покрыть ими весь написанный код.
В данном случае я считаю, что недостаточно одного только дашборда, в котором можно просмотреть покрытие юнит-тестами. Но, если бы можно было добавить акт измерения при добавлении нового кода и проверять таким образом уровень покрытия тестами, то такая альтернатива была бы удобнее для получения мгновенной обратной связи. Мы даже могли бы сформулировать правила, требующие, чтобы любой новый код, добавляемый в базу, должен быть покрыт тестами, скажем, на 80%. Уместнее всего добавлять такую проверку в процесс сборки пул-реквеста.
Поскольку разработчики получают мгновенную обратную связь по поводу покрытия своего кода юнит-тестами, они вправе предпринимать меры, чтобы по мере необходимости наверстывать такое покрытие.
Выход из этого круга
Если вы уже вовлечены в этот порочный круг, то наилучший способ из него вырваться – закрепить практики, рассмотренные выше. Поскольку выше речь шла о частом тестировании для проверки нового добавленного кода, а также о тестировании покрытия нового кода, можно выработать процедуру, подходящую для любых операций добавления или изменения кода.
Исключительно маловероятно, что у вас хватит времени покрыть тестами весь старый код (разве что сам проект совсем новый) сразу, до того, как придется писать новый код. Поэтому вообще не рассчитывайте на это, ведь с точки зрения бизнеса это нецелесообразно.Тем временем, можно делать периодические замеры, стратегически покрывая тестами те или иные участки старого кода. Важнее всего, вы должны приучить всех разработчиков придерживаться этой практики. Не менее важно отучиться считать эту работу издержками и донести до всех владельцев проекта, насколько важно писать юнит-тесты. В противном случае многим, особенно нетехнарям, может показаться, что юнит-тестирование – это лишняя работа, вместо которой можно было бы потратить время на разработку нового функционала.
Итак, какова же истинная ценность всей этой работы
Юнит-тесты полезны во многих отношениях. Если грамотно их применять, они помогают сократить количество дефектов в коде, работают в качестве страховки при проверке уже существующего функционала, не повреждаются при рефакторинге кода, а также помогают держать на высоком уровне общую продуктивность.
Посмотрев код, хорошо покрытый юнит-тестами, каждый может убедиться, насколько уверенно он смотрится.Итак, давайте заполнять пробелы и возвращаться в колею, усваивая практики DevOps и приучаясь писать хорошие юнит-тесты, также придерживаясь разработки через тестирование.