<style>.lazy{display:none}</style>Юнит-тестирование: полное руководство
юнит тестирование. unit тестирование.

Юнит-тестирование: полное руководство

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

Содержание

Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.

Что такое юнит-тестирование?

Модульное или юнит-тестирование (англ. unit testing) – это вид тестирования программного обеспечения, который предполагает проверку отдельных модулей (юнитов) или компонентов приложения. Компонент – это наименьшая тестируемая часть приложения, например, функция, метод, процедура, модуль или класс. 

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

Когда следует проводить юнит-тестирование?

Юнит-тестирование обычно является самым первым уровнем тестирования, выполняемым перед интеграционным тестированием. В каждом цикле разработки необходимо провести много юнит-тестов. Они выполняются быстро, так как тестируемые блоки кода достаточно просты.

Пирамида тестирования. Юнит-тестирование - в самом низу.

Кто выполняет юнит-тестирование?

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

Какова цель юнит-тестирования?

График зависимости стоимости правок от времени нахождения дефекта. Чем раньше найден - тем дешевле исправить

Юнит-тестирование является ключевым этапом процесса тестирования ПО по нескольким причинам:

  • Раннее обнаружение ошибок. Юнит-тестирование выявляет ошибки на ранних стадиях разработки и предотвращает усложнение зависимостей между частями программы. Одна ошибка в коде может затронуть множество других компонентов программы. Если не проводить модульное тестирование и не вылавливать ошибки на ранних стадиях, исправление связанных ошибок потребует много времени и усилий.
  • Более качественное написание кода. Если разработчики берут на себя модульное тестирование, то они должны применять лучшие практики написания кода и гарантировать, что код будет легко поддерживаем. При модульном тестировании необходимо, чтобы каждый компонент кода выполнял определенную функцию, которую можно проверить независимо от других компонентов.
  • Отладка. Если модульный тест завершается неудачно, то можно легко понять, в каком месте программы возникла проблема. Это делает отладку более быстрой и эффективной.
  • Документация. Модульные тесты выступают в качестве примеров, показывающих, как работают отдельные блоки кода и какое поведение от них ожидается. Они предоставляют идеальную документацию для всей логики программного обеспечения.

Структура юнит-теста

1. Тестовые фикстуры

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

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

  • Подключение к базе данных
  • Образец поста с заголовком, содержанием, информацией об авторе и т. д.
  • Временное хранилище для обработки почтовых вложений
  • Настройки конфигурации (видимость сообщений по умолчанию, параметры форматирования и т. д.)
  • Тестовую учетную запись пользователя
  • Песочницу (Sandbox environment) – среду для изоляции тестирования от продакшен среды и предотвращения вмешательства в реальные данные блога.

2. Тест-кейс

Тест-кейс – это часть кода, которая проверяет поведение другого участка кода. При помощи тест-кейсов мы можем убедиться, что тестируемый модуль работает так, как ожидается, и дает желаемые результаты. Разработчики также должны писать asserts, чтобы конкретно определить, какие именно результаты ожидаются. Например, ниже приведен пример тестирования функции, которая вычисляет сумму двух чисел,  a и b:

use PHPUnit\Framework\TestCase;
 

class MathTest extends TestCase

{

    public function testSum()

    {

        // Arrange

        $a = 5;

        $b = 7;

        $expectedResult = 12;
 

        // Act

        $result = Math::sum($a, $b);
 

        // Assert

        $this->assertEquals($expectedResult, $result);

    }

}

В этом коде используется assert $this->assertEquals($expectedResult, $result), который проверяет, что a + b действительно равно ожидаемому результату 12.

3. Test Runner

Test Runner – это программа, которая управляет запуском модульных тестов и предоставляет отчеты о результатах тестирования. Очень важно, что test runner может запускать тесты по приоритету, а также управлять тестовой средой и настройками для выполнения тестов. Он также помогает изолировать тестируемый модуль от внешних зависимостей.

4. Тестовые данные

Тестовые данные должны быть тщательно подобраны, чтобы охватить как можно больше сценариев для выбранного блока кода и обеспечить высокое тестовое покрытие. Как правило, предполагается подготовить данные для:

  • Обычных случаев: ожидаемые входные данные для тестируемого блока кода.
  • Граничных случаев:  входные значения находятся на границе допустимого предела.
  • Ошибочных случаев:  недействительные входные данные, чтобы увидеть, как блок кода реагирует на ошибки (посредством сообщений об ошибках или определенного поведения).
  • Угловых случаев: входные данные представляют собой крайние случаи, которые оказывают значительное влияние на блок кода или систему.

5. Моки и стабы

Моки и стабы – это, по сути, заменители реальных зависимостей тестируемого модуля. В модульном тестировании разработчики стараются проверить работу отдельных частей кода независимо друг от друга. Однако иногда для проверки одной части кода может понадобиться использовать другие компоненты программы.

Например, у нас есть класс User, который зависит от класса EmailSender для отправки уведомлений по электронной почте. В классе User есть метод sendWelcomeEmail(), который вызывает EmailSender для отправки приветственного письма только что зарегистрированному пользователю. Чтобы протестировать метод sendWelcomeEmail() без фактической отправки электронных писем, мы можем создать мок-объект класса EmailSender.

Характеристики хороших модульных тестов

Как правило, хорошие модульные тесты:

  • Быстрые.  Эти тесты проверяют очень простые блоки кода, поэтому их можно выполнить за миллисекунды. В большом проекте может быть до тысячи юнит-тестов.
  • Изолированные. Для получения наиболее точных результатов тестируемые блоки кода следует изолировать от внешних зависимостей.
  • Легко автоматизируемые. Поскольку юнит-тесты очень просты, их легко автоматизировать. Для этого разработчики могут использовать специальные инструменты.

Как проводить юнит-тестирование?

  1. Определение модуля. Определите конкретный модуль, который нужно протестировать. Это может быть функция, метод, класс или любой другой компонент программы. Прочтите код и придумайте логику, необходимую для его тестирования. На этом этапе разработчики также должны иметь представление о случаях, которые им нужно протестировать для этого модуля, чтобы обеспечить высокий уровень тестового покрытия.
  2. Выбор подхода. Как и во многих других видах тестирования, к юнит-тестированию есть два основных подхода:
    1. Ручное тестирование. Разработчики вручную тестируют код, чтобы убедиться, что он работает хорошо. 
    2. Автоматизированное тестирование.  Разработчики пишут скрипт, который автоматизирует взаимодействие с кодом.
  3. Подготовка тестовой среды. Перед началом тестирования нужно подготовить тестовые данные и настроить зависимости. Также нужно изолировать тестируемый модуль для более тщательного тестирования.
  4. Запуск тестов. Выберите подходящие инструменты для выполнения написанных тестовых сценариев. Эти инструменты проверяют код на соответствие ожидаемому поведению и выводят результаты тестирования, сообщая о том, прошел ли тест успешно или возникли ошибки.
  5. Отладка, исправление и подтверждение. Если тест не проходит, проведите отладку, чтобы найти причину. Исправьте проблемы, а затем повторно запустите тесты, чтобы убедиться, что ошибки действительно были исправлены.

Техники юнит-тестирования

Модульное тестирование проводится как методом “черного ящика”, так и методом “белого ящика”.

  • Тестирование “черного ящика”: внутренняя структура и детали реализации тестируемого модуля не рассматриваются. Тесты фокусируются на внешнем поведении и функциональности блока. Тест-кейсы разрабатываются на основе ожидаемых входных и выходных данных.
  • Тестирование “белого ящика”:  учитывается внутренняя структура, логика и реализация модуля. Тест-кейсы разрабатываются таким образом, чтобы проверить различные пути внутри модуля, гарантируя, что все ветви и сегменты кода будут протестированы.

Топ-4 инструмента для юнит-тестирования

1. JUnit

JUnit – это инструмент с открытым исходным кодом для модульного тестирования на языке Java. Он обычно используется для запуска автоматизированных наборов тестов с несколькими тест-кейсами.

Ключевые особенности:

  • Поддерживает TDD (Test-Driven Development)
  • Интегрируется с Maven и Gradle
  • Выполняет тесты в группах
  • Совместим с такими популярными IDE, как NetBeans, Eclipse, IntelliJ и др.
  • С помощью аннотаций @RunWith и @Suite можно запускать тест-кейсы как наборы тестов.

2. NUnit

NUnit – фреймворк для модульного тестирования с открытым исходным кодом на базе .NET. Как и JUnit, NUnit обеспечивает надежную поддержку разработки через тестирование (TDD), и имеет схожие функциональные возможности. NUnit позволяет запускать несколько автоматизированных тестов одновременно.

Ключевые особенности:

  • NUnit обеспечивает параллельное выполнение тестов
  • Поддерживаются несколько сборок
  • Различные атрибуты позволяют запускать тесты с разными параметрами
  • Поддерживается тестирование, управляемое данными (DDT – Data Driven Testing)
  • Поддерживаются языки семейства Microsoft, такие как .NET Core и Xamarin forms

3. TestNG

TestNG – это надежный фреймворк, обеспечивающий полный контроль над тестированием и выполнением модульных тестов. Он включает в себя функции JUnit и NUnit и поддерживает различные виды тестов (модульные, функциональные, интеграционные). TestNG является одним из самых мощных инструментов для модульного тестирования благодаря своим удобным функциональным возможностям.

Ключевые особенности:

  • Возможность параллельного выполнения тест-кейсов
  • Встроенный механизм обработки исключений
  • Создание HTML-отчетов и логов
  • Возможность извлечения ключевых слов/данных из логов
  • Поддержка многопоточности
  • Настройка всех параметров тестирования в формате XML

4. PHPUnit

PHPUnit – это фреймворк для модульного тестирования, созданный специально для PHP-разработчиков. Он следует архитектуре xUnit, такой же, как у NUnit и JUnit. Этот фреймворк работает только через командную строку и не поддерживает непосредственную работу с веб-браузерами.

Ключевые особенности:

  • Анализ покрытия кода
  • Поддержка TDD
  • Поддержка мокинга
  • Добавление поддержки обработчика ошибок
  • Гибкость в расширении тест-кейсов
  • Создание тест-репортов

TDD и юнит-тестирование

Разработка через тестирование (Test-Driven Development, TDD) тесно связана с модульным тестированием. TDD предполагает создание автоматизированных юнит-тестов до написания кода. Эти тесты обязательно проваливаются, поскольку код еще не написан. Затем, на основе результатов этих тестов, разработчики пишут свой код. После этого они повторно запускают проваленные тесты, чтобы убедиться, что их код действительно обеспечивает требуемый функционал приложения. 

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

С помощью TDD вы можете эффективно предотвратить подобные инциденты. Как правило, TDD-подход предполагает прохождение трех этапов:

  1. Неудача. Напишите модульные тесты, которые обязательно провалятся, потому что код еще не написан.
  2. Успех. Напишите код и убедитесь, что тесты проходят успешно.
  3. Рефакторинг. Улучшите код, а затем продолжите выполнять юнит-тесты для следующих функций.
Закольцованная схема шагов TDD: fail- pass-refactoring

Лучшие практики юнит-тестирования

  • Высокая скорость тестирования. Цель модульных тестов – повысить уверенность в существующем коде, чтобы разработчики смогли приступить к работе над новыми функциями. Но обычно юнит-тестов много, и если их выполнение будет долгим, то разработчики не захотят браться за эту работу.
  • Простота тестов. Каждый юнит-тест должен проверять отдельный компонент приложения. Структурируйте тесты по шаблону AAA, чтобы сохранить ясность и читабельность ваших модульных тестов.
  • Изолированное выполнение. Изоляция кода – крайне рекомендуемая практика. Контролируйте данные, которые вы используете для тестов, и старайтесь не использовать динамически создаваемые данные, которые могут изменить результаты тестирования. Также перед каждым тестом убедитесь, что состояние программы сброшено, чтобы предыдущие тесты не повлияли на текущий тест.
  • Согласованность результатов тестов. Чем более предсказуемыми будут ваши модульные тесты, тем лучше. Другими словами, их результаты всегда должны быть последовательными, независимо от того, какие изменения были внесены в код или в каком порядке вы запускаете тесты.
  • Непрерывная интеграция и автоматизация тестирования. Включите модульные тесты в ваш конвейер непрерывной интеграции (CI) и автоматизируйте их выполнение. Это гарантирует, что тесты будут запускаться регулярно, обеспечивая своевременную обратную связь о состоянии вашего кода.

Трудности модульного тестирования

При проведении юнит-тестирования разработчики сталкиваются с рядом проблем:

  • Управление тысячами модульных тестов без специального инструмента требует много ресурсов
  • Написание и поддержка тестовых скриптов при обновлении кода занимает много времени
  • Настройка тестовой среды для различных тестов требует значительных усилий

Перевод статьи «What is Unit Testing? A Complete Guide».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *