Слово «тест» первоначально означало «небольшой сосуд, используемый для пробы драгоценных металлов». Это означало, что тестирование — это метод определения качества золота или серебра. Он также использовался в процессе рафинирования ценных сплавов, таких как олово.
Позже этот термин был принят в других областях, и в наши дни его часто можно встретить в таких контекстах, как образование, медицина или разработка программного обеспечения. Суть его, однако, не изменилась: тестирование используется для улучшения конечной ценности.
Мы используем тесты при разработке программного обеспечения, чтобы убедиться, что код работает так, как ожидается. Тесты могут быть ручными или автоматизированными. Ручное тестирование похоже на то, как автопроизводители разбивают автомобили, чтобы убедиться в их безопасности на дороге. Это работает, но это слишком дорого, чтобы делать это часто, поэтому обычно это делается в конце производственного цикла. Проблема этого метода заключается в том, что проблемы, обнаруженные на этом этапе, могут задержать запуск продукта на месяцы.
Автоматизированное тестирование программного обеспечения имеет совершенно другую структуру затрат. Это первоначальные затраты плюс периодическое обслуживание, но после внедрения автоматизации мы можем запускать тесты так часто, как нам нужно — за копейки.
Благодаря автоматизации разработчики получают непрерывную обратную связь, что позволяет им выявлять проблемы на самых ранних этапах производственного цикла. Быстрые итерации приводят к улучшению дизайна, повышению качества и более безопасному запуску.
Принципы автоматизации тестирования
Целые книги написаны исключительно на тему автоматизации тестирования. Каждый разработчик должен овладеть этим навыком в какой-то момент, и лучше сделать это раньше, чем позже.
Вот шесть принципов, которые облегчат процесс обучения:
- Тесты должны повышать качество.
- Тесты должны снижать риск возникновения сбоев.
- Тестирование помогает понять код.
- Тесты должны быть просты в написании.
- Набор тестов должен легко запускаться.
- Набор тестов должен нуждаться в минимальном обслуживании.
Принцип 1: автоматизация тестирования повышает качество
Качество — это неуловимое понятие. Как бы мы ни старались, его невозможно определить численно. Тем не менее, мы узнаем его, когда видим. В индустрии программного обеспечения придумано множество показателей для измерения качества: количество дефектов, покрытие кода, частота ошибок CI, частота отказов при тестировании и так далее. Каждая из них отражает какой-то аспект идеи качества.
Автоматизированные тесты улучшают показатели качества, постоянно выполняя сотни или тысячи тестов; они находят дефекты до того, как они попадут в производство, информируют разработчиков о потенциальных проблемах и проверяют, не отклоняется ли система от ожиданий пользователей.
Отчеты о тестировании в Semaphore показывают высокоуровневый взгляд на состояние проекта
Не говоря уже о метриках, мы знаем, что надежный дизайн является предпосылкой качества. Когда тесты управляют разработкой, разработчики могут легко опробовать различные идеи и определить, какая из них работает лучше. Эта особенность с большим успехом используется в таких практиках, как разработка, управляемая тестами (TDD) и разработка, управляемая поведением (BDD).
Принцип 2: автоматизация тестирования снижает риск
Обзор кода и коллегиальное программирование, хотя и являются необходимыми и продуктивными, не могут быть основаны на поиске ошибок. Опыт показывает, что большее количество глазных яблок не означает меньшее количество ошибок.
Единственный способ надежно найти ошибки — это создать всеобъемлющий набор автоматизированных тестов. Тесты могут проверить все приложение сверху донизу. Они отлавливают ошибки до того, как они успевают причинить вред, находят регрессии и запускают приложение на различных устройствах и в различных средах в таких масштабах, что ручная проверка становится непомерно дорогой.
Даже если все в команде были исключительно умными разработчиками, которые никогда не допускали ошибок, сторонние зависимости все равно могут вносить ошибки и создавать риски. Автоматические тесты могут просканировать каждую строчку кода в проекте на предмет ошибок и проблем безопасности.
Trivy сканирует проект на наличие проблем безопасности.
Принцип 3: тесты помогают понять систему
Слишком часто разработчики возвращаются к коду, написанному всего несколько дней назад, и понимают, что совершенно забыли, как он работает. Еще хуже, когда разработчикам приходится иметь дело с кодом, написанным другими людьми.
Часто чтение тестов — лучшее место для понимания системы, поскольку они показывают, как все работает, на собственном примере. Поэтому, когда у разработчиков возникают сомнения, они могут обратиться к набору тестов.
Тесты, например, могут показать другому разработчику, как должен реагировать API, что позволит им пропустить просмотр документации.
ctx := context.Background()
result, _, err := env.Client.Server.Create(ctx, ServerCreateOpts{
Name: "test",
ServerType: &ServerType{ID: 1},
Image: &Image{ID: 2},
SSHKeys: []*SSHKey{
{ID: 1},
{ID: 2},
},
})
if err != nil {
t.Fatalf("Server.Create failed: %s", err)
}
if result.Server == nil {
t.Fatal("no server")
}
if result.Server.ID != 1 {
t.Errorf("unexpected server ID: %v", result.Server.ID)
}
if result.RootPassword != "" {
t.Errorf("expected no root password, got: %v", result.RootPassword)
}
if len(result.NextActions) != 1 || result.NextActions[0].ID != 2 {
t.Errorf("unexpected next actions: %v", result.NextActions)
}
Не уверены в необходимости той или иной строки кода? Закомментируйте ее и посмотрите, какой тест не пройдет. Есть идея, как улучшить функцию? Нужно рефакторить часть кода? Попробуйте и запустите автоматизированные тесты. Вы будете удивлены, как много можно узнать о системе из ее тестов.
Принцип 4: автоматизированные тесты должны быть просты в написании
Некоторые тесты начинают свою жизнь как ручные тесты, а затем автоматизируются. Но чаще всего это приводит к созданию слишком сложных, медленных и неудобных тестов. Лучшие результаты достигаются тогда, когда тесты и код имеют определенную синергию. Написание тестов подталкивает разработчиков к созданию более модульного кода, что, в свою очередь, делает тесты более простыми и гранулированными.
Простота тестов важна, потому что писать тесты ради тестов непрактично. Код также должен быть простым для чтения и написания. В противном случае мы рискуем внести сбои в сами тесты, что приведет к ложным срабатываниям и расслабленности.
Многие механизмы тестирования используют языки домена (Domain Specific Languages, DSL) для определения тестов на простом английском языке. Возможно, самым ярким примером является Gherkin, язык, используемый в системе тестирования Cucumber:
Feature: Is it Friday yet?
Everybody wants to know when it's Friday
Scenario: Sunday isn't Friday
Given today is Sunday
When I ask whether it's Friday yet
Then I should be told "Nope"
Подводя итог, можно сказать, что при написании тестов следует придерживаться нескольких основных принципов:
- Пишите только одно утверждение в каждом тесте.
- Держите код отдельно от тестов, т.е. производственный код не должен включать тесты.
- Тесты должны быть независимы друг от друга, так как зависимости могут быстро превратиться в снежный ком, вызывающий головную боль.
- Сведите дублирование тестов к минимуму, т.е. не тестируйте один и тот же код дважды.
- Не нарушайте инкапсуляцию тестируемого кода. Тестируйте только внешние интерфейсы.
Принцип 5: тесты должны быть просты в выполнении
Если разработчику необходимо открыть контрольный список, чтобы запустить тест, ваши тесты будут запускаться не так часто, как следовало бы.
В идеале тесты должны запускаться при каждом изменении кода без какого-либо вмешательства. Здесь нам повезло, поскольку инструменты разработчика довольно сложны. Большинство современных IDE могут обнаруживать изменения в файлах и запускать набор тестов автоматически, того же можно добиться с помощью программ командной строки, таких как nodemon, live reload, fswatch или testmon.
VS Code запускает тесты в фоновом режиме
Чтобы тесты было легко запускать, должны выполняться некоторые условия:
- Идемпотентность: тесты не должны иметь побочных эффектов. Побочные эффекты включают запись в файлы, сохранение в базе данных или общее изменение данных. Разработчики должны иметь возможность безопасно запускать одни и те же тесты любое количество раз.
- Детерминированность: тесты должны всегда давать один и тот же результат при одних и тех же входных данных. Если тестам требуются внешние данные, не зависящие от разработчика, например, дата/время или ответ от API, их следует имитировать с помощью макетов или заглушек.
- Независимость: тесты должны быть независимы друг от друга, и разработчики должны иметь возможность запускать их в любом порядке.
- Легкость: тесты должны быть достаточно легкими, чтобы выполняться на машине разработчика за разумное время.
- Гранулярность: разработчики должны иметь возможность запускать набор тестов по частям.
Запуск тестов на машине разработчика — это только часть уравнения. Тестирование также должно происходить в рамках конвейера непрерывной интеграции. Ваш конвейер CI/CD действует как ворота качества; он запускает набор тестов при каждом коммите, обеспечивая мгновенную обратную связь и позволяя разработчикам обнаружить, когда возникла ошибка.
Принцип 6: автоматизированный набор тестов должен требовать минимального обслуживания
Последний принцип является следствием предыдущих пяти. То есть вы получаете его бесплатно, если хорошо выполняете остальные. Тем не менее, он важен, поэтому не лишним будет обратить на него внимание.
Разработчики хотят заниматься творческой и полезной работой. Автоматизация позволяет машинам взять на себя всю рутинную работу по тестированию. Когда тесты легко писать и они часто выполняются, возникает положительная обратная связь. Разработчики склонны ценить то, как автоматизация облегчает им жизнь, и, таким образом, у них появляется стимул писать и поддерживать тесты.
Конечно, для поддержания тестов в хорошем состоянии необходимо периодическое обслуживание. Вот четыре рекомендации по написанию и поддержке набора тестов:
- Пишите достаточное количество тестов, чтобы быть эффективным (но не больше). Если ошибки проскальзывают мимо, вам нужно больше тестов. И наоборот, если вы обнаружите, что тесты ломаются при небольших изменениях, вам нужно удалить некоторые тесты.
- Выберите лучший тип тестов для конкретной ситуации. Юнит-тесты быстрые и сфокусированные, в то время как сквозные тесты охватывают пользовательский интерфейс и являются тяжелыми и более комплексными. Набор тестов, который следует пирамиде тестов, имеет здоровое разнообразие тестов.
- Поддерживайте надежность тестов. Тест, который не срабатывает, когда код корректен, называется ложным срабатыванием. Тесты, которые иногда не срабатывают без видимых причин, называются ненадежными. И те, и другие создают проблемы в наборе тестов, потому что они отнимают много времени и являются источником разочарования.
- Делайте тесты быстро. Медленный набор тестов будет тормозить разработку.
Заключение
Те, кто считает, что тестирование — это дорого, не в полной мере осознают цену низкого качества. Влияние ошибок и дефектов на ценность продукта по отдельности трудно измерить, но оно может быстро выйти из-под контроля, если не принять меры. К счастью, вы можете предотвратить это, создавая и совершенствуя свой автоматизированный набор тестов, который станет основой для отличного опыта разработчиков и выдающегося, качественного программного обеспечения.