Лет десять назад у меня был коллега из Ульяновска, тоже звали Никитой, кстати. Устроился он тогда в компанию, у которой несколько лет уже работал большой, живой, высоконагруженный продукт, приносивший достаточно денег, чтобы платить команде в России. Продукт активно дорабатывался прямо по живому, а чтобы не сойти с ума, функциональность была покрыта интеграционным тест сьютом. Все бы хорошо, но фичи приоритезировались больше, чем тесты, которые в результате начали подгнивать, перестали отражать реальную ситуацию, и в какой-то момент превратились из инструмента безопасного релиза в театр безопасности. Никто не знал, можно ли им верить, они падали в рандомные моменты времени даже на работающем коде, CI перезапускал тест сьют трижды (!) и считал тест успешным, если он прошел хотя бы один раз из трех. Ситуация быстро кульминировала в пик outages (несколько штук в неделю), невыспавшуюся команду и дерганый менеджмент. Дошло до того, что по общему соглашению разработку вообще всех новых фич остановили на полгода (!), которые посвятили исключительно техдолгу, починке тестов и настройке нормальных процессов релиза. Довольно беспрецендентный шаг, по моему опыту, который тем не менее завершился ощутимым успехом (тоже, кстати, редкость, чтобы такие большие планы ожидаемо сбывались) — работать снова стало возможно. Потом, правда, деньги кончились, но это другая уже история.
Никита тогда занимался как раз починкой тест сьюта и вынес несколько патентованых афоризмов™.
Зачем тесты:
- Тесты помогают двигаться вперед, не оглядываясь назад. - Тесты не делают программу лучше или надежнее. Код самой программы делает программу лучше и надежнее. Акцент всегда на коде! Тесты всегда средство. - Тесты пишутся только из острой необходимости, когда нет других способов обеспечить надежность (простота, гарантии компилятора, БД, ОС). - Смысл тестов в том, чтобы падать. - Прошедший/упавший тест — это сигнал, единица информации. - Тест, падающий всегда, плох. Он ничего не показывает. - Также плох и тест, не падающий никогда. - Мигающий тест не несет информации, это шум.
Что тестировать:
- Ровно то, что программа обеспечивает де факто. - Про каждый (каждый!) тест должно быть понятно, почему он написан, что проверяет, почему это нужно проверять. - Не должно быть тестов «на всякий случай», «просто». - Нет смысла тестировать простые, предсказуемые вещи. - Юнит-тесты тестируют то, что удобно, вместо того, что требует тестирования. - Погоня за 100% покрытием (или любым другим — 90%, 85%) бессмысленное дрочево^W^W формализм.
Как тестировать:
- Тесты не должны отнимать больше усилий, чем код. - Тесты должны быть гибкими и податливыми, не быть обузой, когда надо изменить код. - Не запускать тесты в отдельных, специально создаваемых условиях. Эмуляция окружения, БД, mock-и дают уверенность, что волшебные феечки в волшебной стране работают нормально, а интересно должно быть, как работает реальность. - Асинхронность — не проблема. Надо признать ее существование и тестировать ровно то и ровно так, как оно на самом деле происходит. - Тестирование sleep-ами, фиксированные взятые с потолка задержки, retry тестов — прямая дорога к моргающим тестам, дергающемуся глазу, неврозу, дурке. - Тесты должны быть быстрыми, чтобы запускаться как можно чаще. Duh. - Тесты должны работать локально так же просто, как и на CI. - Кажется, что это сложно или невозможно? Только кажется. Обычно фундаментальных препятствий к этому нет, надо лишь один раз заморочиться. - Править код, архитектуру и даже инфраструктуру (например, взять другую БД!), чтобы написать тесты хорошо — нормально. Тесты тоже часть архитектуры.
От себя добавлю, что главное в тестах — понимание. Когда вы берете в руки отвертку, вы точно знаете, какой именно шуруп вы хотите подкрутить, кто здесь цель, кто средство, какие критерии успеха. Так же должно быть, когда вы садитесь писать тест. Осознанность, епта!