За свою карьеру я много раз видел, как команды начинали работу с автоматизированным тестированием. Не все попытки были успешными. В этом посте я поделюсь несколькими советами о том, как создать культуру автоматизированного тестирования в вашей команде — покажу путь от отсутствия тестов к надежному набору тестов на разных уровнях.
Начало внедрения
Обычный способ, которым некоторые команды подходят к автоматизированному тестированию, заключается в том, что они устанавливают цель, например: «В этом квартале мы увеличим покрытие тестами до X процентов».
Я думаю, что это неоптимальный способ добиться более высокого качества. Конечная цель не в том, чтобы определенный процент строк кода был покрыт тестами. Цель состоит в том, чтобы иметь быструю петлю обратной связи для новых изменений, внесенных в код, на протяжении всего срока службы этой кодовой базы.
Я повторю это, так как я буду ссылаться на это на протяжении всего поста:
Цель состоит в том, чтобы иметь быстрый цикл обратной связи для проверки новых изменений, внесенных в код, на протяжении всего срока службы кодовой базы.
Давайте разберем это с помощью аналогии. Цель не должна состоять в том, чтобы тренироваться в этом квартале для следующего пляжного сезона, цель должна состоять в том, чтобы оставаться в хорошей форме до конца жизни. Речь идет не об энергичной чистке зубов в течение одного дня раз в месяц, а о постоянной практике гигиены полости рта.
Точно так же я рассматриваю автоматизированное тестирование как набор привычек, которым следует команда. И важным аспектом сохранения новых привычек является раннее выявление преимуществ нового поведения.
В оставшейся части статьи я поделюсь своими знаниями о том, как быстрее развивать эти привычки.
Самое первое, что вы должны сделать, это…
Поймите ожидаемый срок службы вашей кодовой базы
Каков ожидаемый срок службы кодовой базы, над которой сейчас работает ваша команда? Как долго вы ожидаете, что код будет оставаться в продакшене? Эти вопросы могут показаться не связанными с обсуждением тестирования, но они имеют большое значение. Это связано с тем, что продолжительность жизни кодовой базы будет определять то, где вы ожидаете изменений. Исходя из моего опыта, вполне разумно предположить, что бизнес-приложение проживет 5–7 лет. Иногда даже больше 10 лет. А теперь подумайте, что может измениться через 10 лет:
- Железо, на котором работает ваш код
- Операционная система
- Все технологии, от которых зависит ваше программное обеспечение, получат серьезные обновления
- Все библиотеки и фреймворки будут обновлены
- Версия языка
- Инструменты
- Процесс сборки
- Процесс развертывания
Этот список ни в коем случае не является полным, и он даже не охватывает какие-либо изменения, внесенные в сам код в связи с новыми требованиями.
Вы можете видеть, как популярные проекты с открытым исходным кодом учитывают эти изменения, поэтому они тестируются на разных версиях ОС и на разном оборудовании в рамках конвейера CI. И это вполне разумно для их продолжительности жизни. Другим примером может быть написание тестов для технологий, от которых сильно зависит ваш код. Не потому, что вы хотите протестировать чужой код, а потому, что хотите иметь подстраховку, когда будете обновлять Kafka/Postgres/что угодно до следующей основной версии.
Цель здесь состоит в том, чтобы заставить команду подумать о том, что они на самом деле должны тестировать. Теперь давайте приблизимся к реальному тестированию. Представьте, что у вас уже есть кодовая база, и в ней нет тестов. С чего бы вы начали писать тесты?
Определите горячие точки, которые часто меняются
Переход от отсутствия тестов к хорошо протестированной кодовой базе требует от команды больших усилий. Поэтому важно как можно скорее получить преимущества автоматизированных тестов. Я видел повторяющийся паттерн: команда ставит перед собой цель достичь 70% покрытия линейного кода. Через некоторое время, достигнув этой цели, команда по-прежнему не чувствует никакой ценности написанных тестов — процент ошибок после запуска по-прежнему высок, процент успешных развертываний остается прежним. В конце концов, у команды возникает негодование по поводу автоматизированного тестирования: «мы пробовали это, это не работает для нашей команды».
Я понимаю, почему некоторые люди сдаются. Им пришлось выкроить время для написания тестов, договориться о техническом долге с владельцем продукта и приложить немало усилий. Тем не менее, они не получили много от этих своих вложений.
Альтернативный подход — определить, где изменения происходят чаще, и начать писать тесты для покрытия этих горячих точек. Помните, что конечной целью автоматических тестов является обеспечение быстрой обратной связи для новых изменений. Так что стоит задуматься о том, где происходят изменения. К счастью, для этого есть инструмент — `git effort` из пакета `git-extras`. Вот, как он работает.:
~ git effort **/*.go --above 10 path commits active days assert/assertions.go........................ 223 163 assert/assertions_test.go................... 145 108 mock/mock.go................................ 106 81 mock/mock_test.go........................... 62 54 suite/suite.go.............................. 46 37 require/require.go.......................... 45 39 assert/assertion_forward.go................. 44 38 require/require_forward.go.................. 43 37 assert/assertion_format.go.................. 36 31 suite/suite_test.go......................... 34 26 assert/doc.go............................... 19 17 assert/http_assertions.go................... 18 15 assert/forward_assertions_test.go........... 17 17 require/requirements.go..................... 16 15 assert/forward_assertions.go................ 16 16 assert/http_assertions_test.go.............. 15 9 assert/assertion_order.go................... 11 10 _codegen/main.go............................ 11 10
Это пример из проекта с открытым исходным кодом. Он показывает, сколько коммитов получил каждый файл, и количество дней активной разработки для каждого файла. Из приведенного выше примера видно, что частота изменений распределяется неравномерно. Некоторые файлы меняются почти каждый день, а другие меняются всего несколько раз в год.
И это одна из причин, по которой покрытие кода является вводящей в заблуждение метрикой — она игнорирует частоту изменений. Вы можете добиться высокого тестового покрытия, но это ничего не даст, если горячие точки останутся непокрытыми.
Итак, сначала убедитесь, что вы тестируете код, который часто меняется. Таким образом, ваша команда быстрее заметит преимущества автоматических тестов.
Эта тактика поможет вам выбрать, с чего начать, но для поддержания новых привычек необходимо проделать большую работу. Итак, давайте поговорим о том, как убедиться, что ваши тесты действительно помогают вам проверять входящие изменения.
Не ограничивайте свои тесты только правильным путем
Работая с несколькими командами, от отсутствия тестов до хорошей автоматизации тестирования, я заметил, что вначале люди думают о тестах как о бинарной вещи — «у нас есть тесты для X» или «у нас нет тестов для X». .
Позже люди начинают спрашивать себя, насколько хороши их тесты. Опять же, я думаю, что это плохое влияние метрики тестового покрытия, которая фиксирует, только если данная строка кода покрыта тестовым случаем. Но давайте не путать это с возможными вариациями выполнения кода из-за логического ветвления или входных параметров.
Я буду использовать термин «полнота теста», чтобы описать, насколько хорошо все возможные пути выполнения покрыты тестами. Чтобы показать разницу между покрытием кода и полнотой тестирования, рассмотрим следующий пример.
У меня есть простая функция, которая складывает два числа:
func Sum(a, b int) int {
return a + b
}
Для достижения 100% покрытия кода этой функции достаточно написать тест, вызывающий ее с любыми параметрами. `assert.Equal(Sum(1, 2), 3)`. Покрыто 100% строк кода! Но охватывает ли он все возможные сценарии?
Нет, возможных вариаций в зависимости от входных параметров гораздо больше:
# these are just basic school math
assert.Equal(Sum(1, 2), 3)
assert.Equal(Sum(1, 0), 1)
assert.Equal(Sum(3, -1), 2)
assert.Equal(Sum(-1, -1), -2)
assert.Equal(Sum(-1, 0), -1)
# there are more language specific test-cases
assert.Equal(Sum(math.MaxInt, 1), math.MinInt)
assert.Equal(Sum(math.MinInt, -1), math.MaxInt)
Поэтому вместо того, чтобы спрашивать, есть ли у чего-то тесты, спросите, насколько полны эти тестовые сценарии.
Пример с суммированием двух чисел может показаться искусственным, но согласно исследованию «Простое тестирование может предотвратить большинство критических сбоев: анализ производственных сбоев в распределенных системах с интенсивным использованием данных»:
- Почти все катастрофические сбои (92%) являются результатом неправильной обработки нефатальных ошибок, явно показываемых программным обеспечением.
- Большинство производственных сбоев (77%) можно воспроизвести с помощью модульного теста.
Как инженеры, мы должны развивать соответствующий уровень паранойи, когда дело доходит до размышлений о том, что может пойти не так, и разработки тестовых случаев для покрытия этих сбоев.
Выводы
Может показаться заманчивым установить измеримую цель, чтобы увидеть прогресс во внедрении автоматизированного тестирования. Однако это может привести к тому, что вы в мишень попадете, но цели не достигнете.
Альтернативный подход — не гнаться за метрикой покрытия строк, а вместо этого обеспечить хорошую полноту тестирования кода, который часто меняется.
Затем, когда у команды есть подстраховка для часто меняющегося кода, она может сосредоточиться на получении реальной пользы от автоматизированных тестов:
- Можно ли повысить показатель успешности развертывания, добавив дополнительные тесты?
- Можно ли воспроизвести ошибки с помощью автоматизированного теста, а не вручную?
- Как мы можем обнаружить проблемы раньше, когда в следующий раз мы выполним основное обновление версий для наших зависимостей?
Ответы на эти вопросы могут не привести к легко измеримым целям, но они создадут более значимые шаги на пути к вашей собственной стратегии тестирования.