Создание модульных тестов и концепция AAA


На третьем уроке учебника по автоматизированному модульному тестированию C# рассматривается построение модульных тестов с использованием фреймворка тестирования NUnit. В этой статье описывается использование атрибутов, определяющих, как тестировщик выполняет код модульного теста.

Атрибуты NUnit

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

Чтобы примеры в статье были как можно более простыми, мы проведем тесты с использованием существующих классов и структур из .NET framework. Вполне вероятно, что у вас не было бы причин для тестирования .NET классов на самом деле, но это позволит нам сосредоточиться на тестах, а не на тестируемом коде. Для начала создайте новый проект библиотеки классов. Чтобы обеспечить легкий доступ к структуре NUnit, добавьте ссылку на nunit.framework dll и включите следующую директиву using во все файлы исходного кода, содержащие тесты:

using NUnit.Framework;
Простой тест

Все тесты используют одну и ту же базовую структуру. Во-первых, вы должны создать тестовое Fixture. Это контейнер, который может содержать один тест или группу связанных тестов. При использовании NUnit Fixture - это класс, который имеет атрибут TestFixture. Класс должен быть объявлен с общедоступной областью видимости и должен быть конкретным, а не абстрактным. NUnit создает экземпляры классов тестовых приспособлений с использованием конструктора по умолчанию, поэтому он должен присутствовать либо в явном виде, либо в тестовом fixture, не содержащем конструкторов. Тестировщик может создавать экземпляр класса несколько раз, поэтому у конструктора не должно быть побочных эффектов. В большинстве случаев рекомендуется полностью исключить конструкторы из класса.

В классе тест fixture вы создаете один или несколько тестов. Каждый тест является общедоступным методом, который украшен атрибутом Test. Для выполнения теста метод должен иметь правильную подпись. У него не должно быть параметров и возвращаемого значения void.

Каждый модульный тест должен выполнять небольшой раздел тестируемого кода. Обычно тест включает утверждение, которое указывает ожидаемое значение после выполнения кода. Утверждения определяются с помощью статических методов из класса Assert. Различные типы утверждений будут разобраны на следующих уроках.

Чтобы создать простой тест, добавьте в проект следующий класс.

[TestFixture]
public class SimpleTestFixture
{
    [Test]
    public void TwoTimesTwoEqualsFour()
    {
        Assert.AreEqual(4, 2 * 2);
    }
}
Этот класс представляет собой тестовое Fixture с именем SimpleTestFixture. Он содержит один тест, называемый TwoTimesTwoEqualsFour. Название метода описывает цель теста. В данном случае мы используем Assert.AreEqual метод определения утверждения. Первый аргумент метода указывает, что мы ожидаем значение четыре. Второй аргумент предоставляет это фактическое значение для сравнения. В этом случае значением является выражение 2*2. Если значения совпадают и метод завершается в обычном режиме, программа выполнения теста укажет на успешное выполнение теста. Если два значения не совпадают или во время выполнения возникает исключение, тест завершается неудачно.

Arrange, Act, Assert

Приведенный выше пример теста гораздо более прост, чем тесты, которые вы обычно создаете, поскольку он включает только утверждение. Большинство тестов должны следовать шаблону "Организовать, действовать, утверждать" (Arrange, Act, Assert, AAA). Тесты, структурированные с использованием AAA, имеют три четко определенные области. Первая часть теста - это этап организации. Здесь вы подготавливаете объекты и данные, необходимые для теста. Шаг Действия содержит поведение, которое проверяется. Шаг Утверждения предоставляет утверждения, которые докажут, что код работает должным образом, или выявят проблемы.

Структурирование тестов в стиле AAA может упростить чтение и последующее обслуживание тестов. Некоторые разработчики любят добавлять комментарий к каждому шагу, выделяя, к какому этапу относится каждая строка кода, и в качестве напоминания о необходимости придерживаться шаблона. Я добавлю эти комментарии в эту статью, чтобы вы могли увидеть их использование.

В следующем примере показан немного более сложный тест, который проверяет правильность сочетания двух слов в String.Format методе. Часть организации подготавливает две исходные строки. Раздел Act содержит String.Format, которая является тестируемым элементом. Наконец, шаг Assert проверяет, что результат операции соответствует ожиданиям.

[TestFixture]
public class ArrangeActAssert
{
    [Test]
    public void CombiningWordsWorksCorrectly()
    {
        // Arrange
        string word1 = "Hello";
        string word2 = "World";
 
        // Act
        string phrase = string.Format("{0} {1}", word1, word2);
 
        // Assert
        Assert.AreEqual("Hello World", phrase);
    }
}
SetUp и TearDown

Приведенные выше примеры тестов включают в себя по одному утверждению в каждом. В реальных проектах обычно выполняется несколько утверждений для каждой тестируемой операции. Можно включить несколько команд Assert в один метод тестирования, как в примере ниже:

[TestFixture]
public class ArrangeActAssert
{
    [Test]
    public void CombiningWordsWorksCorrectly()
    {
        // Arrange
        string word1 = "Hello";
        string word2 = "World";
 
        // Act
        string phrase = string.Format("{0} {1}", word1, word2);
 
        // Assert
        Assert.AreEqual("Hello World", phrase);
        Assert.AreEqual(11, phrase.Length);
        Assert.AreEqual(" ", phrase.Substring(5, 1));
    }
} 
Добавление нескольких утверждений к одному методу тестирования имеет ключевой недостаток. Если первая команда Assert вызывает сбой, все последующие утверждения игнорируются исполнителем тестирования. Когда вы устраните проблему, вызвавшую первый сбой, вы можете обнаружить, что второе утверждение подчеркивает еще одну проблему. Лучшее решение - иметь только одно утверждение для каждого метода тестирования. Это гарантирует, что каждое утверждение проверяется и все проблемы отображаются немедленно.

Вы можете разделить утверждения предыдущего примера на три метода тестирования, используя только атрибуты TestFixture и Test. Однако это означало бы, что шаги "Организовать" и "Действовать" необходимо будет дублировать для каждого теста. Это дублирование снижает ремонтопригодность тестов, если потребуется изменить любой из этапов. Проблема устраняется с помощью атрибута SetUp.

Каждое устройство для тестирования может включать один метод, используемый для настройки ваших тестов. Метод украшен атрибутом SEtup, чтобы сообщить исполнителю теста NUnit, что он должен выполняться непосредственно перед каждым тестом. Этот метод обычно используется для хранения кода для этапов упорядочивания и действия шаблона AAA. Методы тестирования становятся сосредоточенными на выполнении утверждений. ПРИМЕЧАНИЕ: Метод установки должен быть общедоступным, не иметь параметров и возвращать значение void.

Чтобы показать работу атрибута SetUp, попробуйте выполнить тесты, показанные ниже, которые эквивалентны предыдущему образцу. Чтобы позволить тестам получить доступ к данным, которые необходимо проверить, две переменные слова и переменная фразы были повышены до уровня класса. Шаги Assert и Act были перенесены в метод настройки, который будет выполняться три раза, по одному разу для каждого теста.

[TestFixture]
public class WhenCombiningWordsWithStringFormat
{
    string _word1, _word2, _phrase;
 
    [SetUp]
    public void SetUp()
    {
        _word1 = "Hello";
        _word2 = "World";
 
        _phrase = string.Format("{0} {1}", _word1, _word2);
    }
 
    [Test]
    public void TheTwoWordsAreCombined()
    {
        Assert.AreEqual("Hello World", _phrase);
    }
 
    [Test]
    public void ThePhraseIsTheCorrectLength()
    {
        Assert.AreEqual(11, _phrase.Length);
    }
 
    [Test]
    public void TheSeparatorIsASpace()
    {
        Assert.AreEqual(" ", _phrase.Substring(5, 1));
    }
} 
Для некоторых тестов может потребоваться выполнение действия после каждого теста, независимо от того, было ли оно успешным или нет. Это часто случается с интеграционными тестами, когда необходимо закрыть соединение с базой данных или файл. При модульных тестах также может потребоваться некоторая очистка.

Вы можете создать метод, который выполняется после каждого теста, применив атрибут TearDown. Как и в случае с атрибутом Setup, удаление должно быть применено к общедоступному методу без параметров и возвращаемого значения void.

[TearDown]
public void TearDown()
{
    _someObject.Dispose();
}
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, си шарп, тестирование, NUnit




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.



С++, IE и автоматическая авторизация BASIC Auth
Введение в регулярные выражения
Работа с API