Неудобная правда, которую никто не хочет слышать
Начну с того, что заставит ваш ленту в Twitter взорваться: когда junior-разработчики пишут тесты, это как если бы кто-то учился водить, управляя школьным автобусом в час пик. Конечно, они, вероятно, выживут и, возможно, даже чему-то научатся. Но действительно ли это лучшее использование времени и сил каждого? Я уже слышу коллективный возглас фанатиков разработки через тестирование, проповедников гибкой разработки и сторонников принципа «каждый должен просматривать код каждого». Но выслушайте меня — это не просто стремление ограничить доступ ради самого ограничения. Речь идёт о признании того, что в разработке программного обеспечения существует фундаментальная иерархия навыков, которую мы отчаянно пытаемся упразднить, и это обходится нам дороже, чем мы думаем.
Неудобная иерархия, о которой никто не говорит
Дело в том, что тестирование сложно. Не «сложно» в том смысле, в каком изучение первого языка программирования сложно. Тестирование сложно в том смысле, что для написания хороших тестовых случаев требуется понимание не только того, как работает код, но и почему он может дать сбой. Это требует распознавания закономерностей, которое приходит только после того, как вы видели сотни сценариев сбоев. Это требует системного мышления, которое junior-разработчики только начинают развивать. Когда мы настаиваем на том, чтобы junior-разработчики писали тесты с первого дня, мы просим их сделать то, что требует:
- глубокого понимания кодовой базы, которую они ещё изучают;
- осознания граничных случаев, с которыми они ещё не сталкивались;
- опыта работы со сценариями сбоев, которые они ещё не видели;
- зрелости, чтобы писать тесты, которые не станут техническим долгом через шесть месяцев. По сути, мы просим человека, который только научился пользоваться молотком, спроектировать мост.
Что на самом деле говорит нам данные (и что мы игнорируем)
Исследования практик тестирования разработчиков показывают нечто неудобное: когда разработчики пишут тесты, они склонны сосредотачиваться на «счастливом пути». Они в четыре раза чаще пишут тесты, демонстрирующие работу функций, чем тесты, пытающиеся спровоцировать сбои. Эта предвзятость существует у опытных разработчиков — представьте, какой ущерб она наносит, когда её усугубляют junior-разработчики, которые ещё не научились мыслить как злоумышленники. Junior-разработчики также чаще:
- пишут поверхностные тестовые случаи, которые дают ложное чувство уверенности;
- создают хрупкие тесты, которые ломаются при рефакторинге;
- упускают граничные случаи и сценарии обработки ошибок;
- создают наборы тестов, которые сами по себе ошибочны (да, тесты могут содержать ошибки);
- плохо документируют свои тестовые случаи, усложняя обслуживание. Это не моральные проступки. Это этапы развития. Но мы решили их игнорировать.
Реальная стоимость принципа «Все тестируют»
Давайте поговорим о вменённых издержках, потому что именно это волнует руководство (даже если они этого не говорят). Когда вы поручаете junior-разработчику написание тестов, происходит следующее: Распределение времени junior-разработчика:
- 40 % — написание самого тестового кода;
- 30 % — отладка, почему тест не работает;
- 20 % — попытки понять, что они должны тестировать;
- 10 % — фактическое изучение кодовой базы. Старший инженер, пишущий тот же набор тестов:
- 50 % — написание тестового кода (потому что они знают, что делают);
- 20 % — отладка;
- 20 % — архитектурное мышление;
- 10 % — документирование паттернов для будущего. Старший инженер создаёт более качественные тесты за меньшее время. Но вместо этого junior-разработчики тратят три часа на написание тестов, которые старший инженер написал бы за 45 минут.
Настоящая проблема, маскирующаяся под решение
Философия «каждый должен писать тесты, потому что тестирование важно» похожа на утверждение «каждый должен готовить ужин, потому что питание важно». Технически верно, но практически нелепо. На самом деле мы создали систему, в которой:
- Junior-разработчики пишут слабые тесты, которые создают ложное чувство уверенности.
- Эти слабые тесты становятся институциональными, потому что у nobody нет времени их переписывать.
- Кодовая база становится сложнее для рефакторинга, потому что вы не можете изменить код, не нарушив десять хрупких тестовых утверждений.
- Старшие инженеры тратят время на исправление тестов, а не на наставничество или проектирование архитектуры.
- Тестирование становится тем, что junior-разработчики терпят, а не тем, что они понимают. Так вы превращаете «тестирование важно» в «тестирование — это обуза». А как только тестирование становится обузой, вы проиграли. Вы проиграли войну.
Что должно происходить на самом деле (радикальное предложение)
Тут я потеряю половину своей аудитории: junior-разработчики должны сосредоточиться на написании хорошего кода, а не на написании тестов. Знаю. Дико. Противоречиво. Возможно, это предложение может стоить карьеры. Но подумайте об этом по-другому. Если junior-разработчик создаёт код, который:
- понятен и читаем;
- хорошо структурирован;
- правильно документирован;
- следует установленным паттернам;
- имеет очевидные границы и обязанности. Тогда тестирование этого кода просто. Middle или senior-инженер может написать исчерпывающие тесты за считанные минуты. И наоборот, если код запутанный, плохо документированный и нарушает шаблоны проектирования? Никакое тестирование junior-разработчика это не исправит. Они просто напишут запутанный тест. Иерархия должна выглядеть так:
Качество кода (Основная ответственность: Junior-разработчики)
↓
Проектирование тестов (Основная ответственность: Middle-разработчики)
↓
Архитектура тестирования (Основная ответственность: Senior-разработчики)
А не этот беспорядок, который у нас есть сейчас:
Качество кода (Совместное)
Написание тестов (Совместное)
Обслуживание тестов (Совместное)
Все в замешательстве (Все)
Ложное противопоставление QA и тестирования разработчиков
Мы также создали странную ситуацию, когда команды QA существуют, чтобы «поймать то, что упускают разработчики», но мы одновременно настаиваем, чтобы разработчики писали тесты. Так чем же занимается QA? И если QA занимается тестированием, зачем разработчикам также тестировать? Эта путаница существует, потому что мы никогда не были ясны в отношении уровней квалификации. Квалифицированный специалист по QA, особенно младший специалист по QA, проводит весь рабочий день, изучая:
- как разрабатывать тестовые случаи на основе требований;
- как систематически выявлять ошибки;
- как выполнять регрессионное тестирование;
- как правильно документировать дефекты. Они развивают экспертизу в области тестирования. Тем временем от junior-разработчиков ожидается, что они станут экспертами в тестировании в дополнение к тому, чтобы стать экспертами в разработке. Это когнитивная перегрузка. Вот неудобная правда: тестирование — это отдельная дисциплина. Мы делали вид, что это не так, и качество наших тестов (и, следовательно, нашего кода) пострадало.
Когда junior-разработчики всё же касаются тестов (правильный путь)
Сейчас я не говорю, что junior-разработчики никогда не должны писать тесты. Это было бы абсурдно. Они должны, но в контролируемых условиях:
1. Только после совместной работы
Junior-разработчик должен писать тесты только после того, как посидел с кем-то опытным и понял:
- почему важны именно эти утверждения;
- какие существуют граничные случаи;
- какие паттерны следует соблюдать;
- почему определённые подходы терпят неудачу. Это занимает время. В этом смысл.
2. В рамках ограничений
Перед тем как junior-разработчик напишет тест, должны быть:
- документированный шаблон теста, которому они следуют;
- чёткие примеры, на которые они могут ссылаться;
- явные критерии приёмки, определяющие, что делает тест «хорошим»;
- чек-лист, который они заполняют перед отправкой.
3. Всегда проверяется специалистами
Тесты должны проверяться людьми, которые глубоко разбираются в тестировании, а не просто «любым старшим инженером, у которого есть время». Это могут быть либо:
- специализированные QA-специалисты, проверяющие тестовый код;
- старшие инженеры с опытом в тестировании, проводящие проверку;
- архитекторы тестирования, устанавливающие стандарты. А не: «любой старший разработчик, который окажется поблизости».
Практический пример: правильный путь против неправильного пути
Допустим, у вас есть функция обработки платежей. Вот что обычно происходит: Неправильный путь (текущая реальность): Junior-разработчик пишет:
test('payment should work', () => {
const result = processPayment(100, 'VISA');
expect(result.success).toBe(true);
});
Готово. Отправлено. Ложное чувство уверенности обеспечено. Правильный путь: Старший инженер сначала описывает, что должно быть протестировано: «Эта функция нуждается в тестах для:
- допустимого платежа с действительной картой;
- допустимого платежа с просроченной картой;
- недопустимой суммы платежа;
- сбоя сети во время обработки;
- крайних случаев конвертации валюты;
- одновременных попыток оплаты. Вот шаблон, который мы используем для асинхронных тестов в этой кодовой базе…» Затем junior-разработчик, руководствуясь, может написать:
describe('processPayment', () => {
describe('valid payments', () => {
test('should succeed
