Рецепт быстрого кодирования, быстрой доставки и бесперебойных производственных выпусков

Покрытие кода тестовыми примерами и предоставление тех же функций на самом деле быстрее, чем без тестов? Как такое возможно? Это так, и я бы сказал, что понимание этого было лучшим опытом в моей карьере инженера-программиста.

Моя команда и я недавно создали PoC за относительно короткое время, и его использовали на живом мероприятии. Круто, но на самом деле в этом нет ничего особенного. Теперь самое интересное - во время этого живого мероприятия, в то же время, когда клиенты использовали нашу систему, мы выполнили дюжину производственных релизов с новыми изменениями, без какого-либо контроля качества - и каждый раз мы были уверены, что ничего не сломаем. .

Как это было возможно? Все наши функции были покрыты соответствующими тестовыми примерами. Итак, у вас есть отличные тестировщики, которые раньше подготовили правильные тесты? У нас действительно есть отличные тестировщики, но на этот раз они не участвовали в подготовке этих тестовых примеров. О, так у вас было достаточно времени и вы могли поработать над тестовым покрытием перед мероприятием? Нет, мы торопились, нам пришлось многое отрезать от нашего решения, и на самом деле теперь нам нужно потратить еще несколько дней после мероприятия, чтобы исправить какой-то код, который не соответствует нашим стандартам качества.

Как уже упоминалось, написание тестов не требует от нас дополнительного времени. Главное - писать правильные тесты и не тестировать код вручную.

Распространенные заблуждения о тестировании

  1. Мы торопимся; у нас нет времени писать тестовые примеры:
    Итак, у вас нет времени писать тестовые примеры, но у вас есть время, чтобы вручную протестировать свое приложение после каждого изменения, воспроизвести ошибки для их отладки , а дым-тест после каждого релиза? На мой взгляд, если вы можете делать эти вещи вручную снова и снова, похоже, у вас много времени. Вместо этих ручных процессов я предпочитаю писать автоматизированные тестовые примеры один раз при внесении изменений.
  2. Тестирование и реализация - это разные вещи. Я могу подготовить тесты позже:
    В таком случае, как вы в первую очередь убедитесь, что ваш код работает? Проверяя вручную? Если вы сначала подготовили тестовые примеры, вам не нужно этого делать - так зачем тем временем заниматься ненужной ручной работой?
    Более того, по моему опыту, гораздо проще разработать тестовые примеры до того, как появится код. Вы просто пишете то, что хотели бы проверить дальше, вместо того, чтобы думать о том, что следует проверить, когда все уже готово.

Хорошо, что мне делать?

«Пишите тесты. Не так много. В основном интеграция ». - Гильермо Раух

Это одна из моих любимых цитат, связанных с разработкой программного обеспечения.

Зачем мне писать тесты? Это базовый вопрос, и я надеюсь, вы его уже знаете.

Почему не так много? Потому что чем больше тестов вы напишете, тем больше времени вам понадобится для их поддержки в дальнейшем. Не говоря уже о реализации в первую очередь. Лень - добродетель!

Почему в основном интеграция? Этот вопрос менее очевиден, и не все согласятся. Он исходит из идеи The Test Diamond, альтернативы знаменитой The Test Pyramid. У нас есть три основных уровня тестов:

  1. E2E-тесты - обеспечивают максимальную уверенность в том, что все работает, но их выполнение занимает гораздо больше времени, и они уязвимы для любых изменений.
  2. Интеграционные тесты - дает меньше уверенности (мы тестируем некоторые части, но не весь стек). Их намного быстрее и проще обновлять при внесении изменений.
  3. Модульные тесты - самые быстрые, наиболее устойчивые к изменениям, но также дают наименьшую уверенность (даже если все модули работают нормально, они могут быть неправильно объединены)

Интеграционные тесты - это, по сути, компромисс между надежностью и скоростью выполнения. Кроме того, обычно гораздо проще разработать интеграционные тесты, чем модульные тесты, поскольку вам не нужно разрабатывать структуру кода с указанием некоторых модулей.

Если вы хотите узнать об этом больше, вы можете ознакомиться с замечательным сообщением Кента С. Доддса, в котором это описано более подробно (из контекста внешнего интерфейса).

Каковы затраты?

  1. Вам необходимо изменить свое мышление - пока вы относитесь к ручным проверкам как к удобному способу проверки кода, вы не сможете в полной мере воспользоваться преимуществами автоматизированного тестирования. Одна ручная проверка может быть быстрее, но одной проверки никогда не бывает. Вот почему начало автоматизации может сэкономить ваше время.
    Лично я провожу ручные проверки только в двух случаях: для проверки внешнего вида и для тестирования решений с точки зрения конечного пользователя, особенно при проверке возможности улучшения общего пользовательского опыта.
  2. Практика - как всегда, мастер делает. Для меня самым сложным было перестать думать о некоторых деталях реализации и сосредоточиться на желаемых результатах. С точки зрения времени могу сказать, что детали реализации тестирования не только неактуальны, но, как правило, намного сложнее. Я потерял много времени, прежде чем получил его.
  3. Этот подход должен быть последовательным. По моему опыту, писать регрессионные тесты намного сложнее, когда значительная часть вашего приложения не покрыта тестами. Вам по-прежнему нужны дорогостоящие ручные процессы в конвейере выпуска. В этом случае хорошей отправной точкой будет сначала покрытие критического пути с помощью тестов E2E.

Мой процесс: пример

Весь пример кода здесь написан на NodeJS, но, пожалуйста, не расстраивайтесь, если это не ваша технология. Этот подход универсален для всех технологий - как фронтенд, так и бэкэнд - вам просто нужно использовать другие инструменты, чем у меня.

1. Разработайте и подтвердите клиентский интерфейс.

Для серверных приложений, которые обычно используют формат API. Любая документация будет работать, просто убедитесь, что она работает как на стороне клиента, так и на стороне сервера.

2. Опишите тестовые примеры

После подтверждения API я пытаюсь выяснить все (или большинство) вариантов использования и написать для них описания тестов.

3. Напишите первый тестовый пример.

Напомню, что это интеграционные тесты. В этом случае я не пытаюсь имитировать все, и это упрощает многие вещи - например, я могу использовать базу данных. Для тестов уровня интеграции / API NodeJS я рекомендую супертест.
Также должна быть некоторая подготовка, но не волнуйтесь - это обычно характерно для многих тестовых случаев, поэтому написание этого кода требует только времени. первый раз.

4. Напишите код для прохождения первого теста.

Детали реализации здесь не актуальны, поэтому пример кода я бы пропустил.

Иногда, когда бизнес-логика требует от вас прохождения определенных более сложных интеграционных тестов, было бы неплохо написать несколько модульных тестов на этом этапе. Мой подход здесь тот же: сначала нужно разработать общедоступный API модуля, описать варианты использования, реализовать первый тестовый пример и, наконец, написать код. Чистый TDD.

Если ваша логика довольно стандартна и вы можете реализовать некоторый код, чтобы пройти интеграционный тест без каких-либо проблем - просто сделайте это, пропустите модульное тестирование. Вы заметите, когда они вам действительно понадобятся.

5. Зафиксируйте и вставьте код.

Это может произойти немного раньше / позже, но в целом атомарные коммиты - действительно хорошая практика. Push запускает некоторые автоматические развертывания в промежуточной среде, и мои коллеги могут начать использовать ту часть, которая уже была сделана.

Если ваш код по какой-либо причине не работает даже в тестовой среде, скройте его флажком функции.

6. Повторяйте, пока все не будет готово.

Когда функция готова, я обычно готовлю дымовые тесты и тесты производительности, которые выполняются в уже развернутом приложении. Это может быть ответственность QA в вашей организации. Если это не так, и вы не пишете никаких дымовых тестов и тестов производительности, я рекомендую вам попробовать это.

В настоящее время есть несколько действительно удобных для разработчиков инструментов, которые делают эти вещи, например k6.io - это инструмент для тестирования производительности, а тестовые примеры написаны на JS. На самом деле я использую его для дымового тестирования своих API-интерфейсов после развертывания - или Cypress для тестов E2E, включая интерфейсное приложение.

7. Ждите отзывов.

Отзывы могут поступать от ваших коллег (другого разработчика, использующего ваш API, QA, PM), вашего клиента или некоторых инструментов автоматической отчетности. Независимо от того, откуда он исходит, если есть какая-либо проблема, я продолжаю тот же процесс и проверяю, что и мои тестовые примеры, и код охватывают случай, отсутствующий ранее.

Если с технической точки зрения все в порядке, можно начинать жить!

Резюме

Невероятно, насколько мощным и надежным может стать ваш рабочий процесс, если вы сосредоточитесь на тестировании, чтобы убедиться, что ваше приложение действительно работает, а не только на «некоторых» тестах или x% покрытия тестами. Для меня самым большим открытием стало то, что это может стать действительно простым (и если вы все делаете правильно, так и должно быть!). Все, что вам нужно, - это правильные инструменты и правильный образ мышления.

Спасибо за прочтение. Если вы никогда не пробовали работать таким образом, я надеюсь, что вы почувствуете воодушевление попробовать. Не стесняйтесь оставлять любые комментарии или вопросы!