Сегодня будем говорить про надежность наших тестов. И в связи с этим хочу спросить: А вас вообще устраивают ваши тесты и надежность покрытия вашей системы?
Собственно, что и требовалось доказать. Результаты опроса абсолютно совпадают с моей картиной мира, поэтому встречайте тред про надежность 👇 twitter.com/jsunderhood/st…
Для начала давайте разберемся, что же такое та самая надежность в контексте тестирования? Ваша система на 100% защищена тестами, если:
Вы можете внести любое изменение в бизнес логику и ваши тесты упадут.
Или по-другому: изменения в бизнес-логике требуют изменения тестов.
Вы можете произвести ЛЮБОЙ рефакторинг и хотя бы не переписывать тесты заново, а в идеале вообще не менять тесты
И конечно когда тесты пройдут – быть уверены что приложение действительно работает.
Вы можете обновить любую зависимость в вашей системе, даже на мажорную версию, и если тесты прошли – быть уверены что система работает.
Ваши тесты не падают на каждый чих, так что красный билд превращается в белый шум (привет jest -u на каждом коммите).
На самом деле крайне важный пункт. Если фиксить тесты после любого изменения входит в привычку, то даже действительно серьезная ошибка может быть пропущена.
Возможно, звучит как что-то невозможное. Если бы можно было легко писать такие тесты, то а каких багах и тестировщиках может идти речь?
Но мы хотя бы избегать того, что напрочь гробит надежность и делает тесты бесполезными:
И первое, самое очевидное – это недостаточное покрытие приложения тестами. Даже внимание уделять этому не буду.
> Но хочу отметить, что нужно опираться не на CODE coverage, а на USE CASE coverage. Если захотите можем сделать отдельный тред о покрытиях.
Итак продолжаем. Поговорим за use case coverage: автоматически высчитать его невозможно, потому что 1 строкой кода может обрабатываться несколько сценариев.
Это хорошо описано в статье kentcdodds.com/blog/how-to-kn…
Именно по причине отсутствия метрики тут можно полагаться только на сознательность разработчика, который не поленился и описал все возможные сценарии ручками.
В принципе, такие вещи возможно поймать на код ревью, но только при условии реально хорошего погружения в ПР.
Но и code coverage выбрасывать не стоит. Просто важно понимать что написать 100% coverage можно с абсолютно отвратительными и бесполезными тестами.
То есть, если коверейджа мало – это однозначно плохо, но если его много – это не значит что все хорошо.
Собственно дальше о том что гробит надежность.
И первое что приходит на ум — неправильное и слишком частое использование моков.
Например, часто вижу проблему когда начинается замокивание частей бизнес логики внутри тестов, а интеграция модулей остается непокрытой.
В принципе использование юнит-тестов ведет к проблемам надежности. Чем выше мы взбираемся по пирамиде, тем более надежные у нас тесты. На этой неделе мы к этому еще вернемся, но в данном контексте:
Чем меньше у вас юнит-тестов, тем надежнее и устойчивее к изменениям ваша система
Моки в тестах это вообще довольно опасная вещь, потому что мы сознательно вырезаем кусок кода нашего приложения в тестах.
Это примерно тоже самое что писать any в ts. Если можно без них обойтись - лучше обойтись.
И вообще! Хватит мокать функции только потому что они находится в другом файле или потому что это внешняя зависимость.
Кто вам сказал что эта зависимость или этот код будет работать правильно через 2/3/10 лет?
Вот собственно классическая проблема юнит-тестов и overmocking-а.
С таким тестом мы не можем ни изменить название роута, ни способ которым мы делаем запрос (например на axios) – в любом случае придется переписывать тест.

Собственно, а что мы вообще проверяем в этом тесте?
То, что наш код дергает
fetch
и возвращает промис? Зачем нам это тестировать? Что нам это дает? - нипанятна!! Вообще эту проблему можно описать одной фразой – “Тестирование реализации”.
Тесты должны проверять не то как наш код НАПИСАН, а то как наш код РАБОТАЕТ - и будет вам счастье :)
Для всех – будет отдельный тред про пирамиду тестирования. Затравочка:
Кто вам сказал что e2e тесты сломать проще чем юниты?
Кто вам сказал что писать e2e тесты сложнее и дольше чем юниты?
Продолжаем эппопею.
Не только моки делают тесты ненадежными. Зачастую просто лень разработчиков, которым лень писать чуть-более низкоуровневые условия assert-ов.
Это очень хорошо видно именно во фронтовых тестах.
К примеру тесты на компоненты можно сделать практически независимыми от фреймворка. Звучит странно, согласен.
И самое главное – абсолютно независимыми от реализации компонента/фреймворка/ui kit-a/дизайна.
Все наши тесты состоят из 3 частей: Preparation, action & assertion.
И если можете взять любой тест на компонент, сделать его точную копию на другом фреймворке и изменить только первую preparation часть и тесты будут работать — мое почтение!
Вот собственно пример такого теста, который абсолютно никак не дергает ни React-specific ручки, ни там названия css-классов.
Этот тест придётся менять только при изменении бизнес логики или при изменении внешнего API — собственно чего мы и ожидаем от тестов.

А вот пример не очень хорошего теста:
Завязываемся на названии компонента Day
Почему-то дергаем именно 10 день 🤦♂️
Завязываемся на название Popover-a и название пропса вместо того чтобы просто проверить что 'div[role="dialog"]' отрисован в html

И последний пример:
Недавно смотрел доклад про рефакторинг. Ссылки не будет, бо вы меня загрызете 🙃
И дело зашло за тесты, спикер рассказывал как он целый месяц рефакторил 1 компонент, потому что ему пришлось переписать все тесты в системе, которые были завязаны на него.
Причина банальная. Все тесты были завязаны на классах нашего компонента и в итоге при попытке заизолировать стили внутри компонента – все пошло по одному месту.
И автор решил переписать тесты:

И действительно – стало лучше. Вот только никто не подумал о том, что избавляясь от одних деталей реализации (названия классов) тесты теперь знают о других (названиях компонентов и пропсов).
Автор сам говорил о том, что проект изначально был написан на jquery.
Тогда никто не мог подумать о том что кому-то когда-то понадобится изолироваться от классов.
И опять не подумали, что например через 5 лет веб-компоненты захватят мир и опять надо будет все переписать.

Приближаемся к выводу.
Тесты, даже банальные юнит тесты имеют свои подводные камни, свои "бед" и "бест" практики.
Помочь писать хорошие тесты могут утилиты для тестов с правильным и строгим API которое запрещает делать всякую грязь.
Например testing-library.com
Итак вывод:
Если вы уважаете себя и ваш продукт, хотите чтобы он цвел и пах (и например уволить тестировщиков), а вы не чинили тесты на каждый чих – вам заранее необходимо думать о том как обеспечить “Антихрупкость” тестам и надежное покрытие пользовательских сценариев.