Пошаговое руководство. Создание и запуск модульных тестов для управляемого кода

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

В этом разделе содержатся следующие подразделы.

Подготовка к выполнению пошагового руководства

Создание проекта модульного теста

Создание тестового класса

Создание первого тестового метода

Построение и запуск теста

Исправьте код и повторно запустите тесты

Использование модульных тестов для улучшения кода

ПримечаниеПримечание

В этом пошаговом руководстве используется платформа модульных тестов Microsoft для управляемого кода.Обозреватель тестов также может запускать тесты из тех платформ модульных тестов стороннего производителя, которые имеют адаптеры для обозревателя тестов.Дополнительные сведения см. в разделе Практическое руководство. Установка платформ модульного тестирования сторонних поставщиков.

ПримечаниеПримечание

Сведения о запуске тестов из командной строки см. в разделе Пошаговое руководство. Использование программы командной строки для тестирования.

Обязательные компоненты

Подготовка к выполнению пошагового руководства

Подготовка к выполнению пошагового руководства

  1. Откройте программу Visual Studio 2012.

  2. В меню Файл выберите пункт Создать, а затем команду Проект.

    Откроется диалоговое окно Новый проект.

  3. В области Установленные шаблоны выберите шаблон Visual C#.

  4. В списке типов приложения выберите пункт Библиотека классов.

  5. В поле Имя введите Bank и нажмите кнопку ОК.

    ПримечаниеПримечание

    Если имя "Bank" уже существует, выберите для проекта другое имя.

    Будет создан новый проект Bank. Этот проект отобразится в обозревателе решений, а его файл Class1.cs откроется в редакторе кода.

    ПримечаниеПримечание

    Если файл Class1.cs не откроется в редакторе кода, дважды щелкните Class1.cs в обозревателе решений, чтобы открыть этот файл.

  6. Скопируйте исходный код из раздела Пример проекта для создания модульных тестов.

  7. Замените исходное содержимое файла Class1.cs кодом из примера Пример проекта для создания модульных тестов.

  8. Сохраните файл как BankAccount.cs

  9. В меню Построение выберите Построить решение.

Будет создан проект с именем "Bank".Он содержит исходный код, подлежащий тестированию, и средства для его тестирования.Пространство имен проекта "Bank", BankAccountNS, содержит открытый класс BankAccount, методы которого будут тестироваться в приведенных ниже процедурах.

В данном кратком руководстве рассматривается метод Debit. Метод Debit вызывается после снятия денег со счета и содержит следующий код:

// method under test
public void Debit(double amount)
{
    if(amount > m_balance)
    {
        throw new ArgumentOutOfRangeException("amount");
    }
    if (amount < 0)
    {
        throw new ArgumentOutOfRangeException("amount");
    }
    m_balance += amount;
}

Создание проекта модульного теста

Предварительное требование: необходимо выполнить действия, приведенные в подразделе Подготовка к выполнению пошагового руководства.

Для создания проекта модульного теста

  1. В меню Файл последовательно выберите Добавить, а затем Новый проект.

  2. В диалоговом окне нового проекта разверните Установленные, разверните Visual C#, а затем выберите Тест.

  3. Из списка шаблонов выберите Проект модульного теста.

  4. В окне Имя введите BankTest, а затем нажмите ОК.

    Проект BankTests добавится к решению Bank.

  5. В проекте BankTests добавьте ссылку на решение Bank.

    В обозревателе решений выберите Ссылки в проекте BankTests, затем выберите Добавить ссылку... из контекстного меню.

  6. В диалоговом окне диспетчера ссылок разверните Решение и проверьте наличие элемента Bank.

Создание тестового класса

Тестовый класс нужен для проверки класса BankAccount.Можно использовать UnitTest1.cs, созданный в шаблоне проекта, но лучше дать файлу и классу более описательные имена.Можно сделать это за один шаг, переименовав файл в обозревателе решений.

Переименование файла класса

В обозревателе решений выберите файл UnitTest1.cs в проекте BankTests.В контекстном меню выберите команду Переименовать, а затем переименуйте файл в BankAccountTests.cs.Выберите Да в диалоговом окне, предлагающем переименовать все ссылки на элемент кода "UnitTest1" в проекте.Данный шаг изменит имя класса на BankAccountTest.

Файл BankAccountTests.cs теперь содержит следующий код:

// unit test code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

Добавление оператора using в тестируемый проект

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

using BankAccountNS

ms182532.collapse_all(ru-ru,VS.110).gifТребования к тестовому классу

Минимальные требования к тестовому классу следующие:

  • Атрибут [TestClass] является обязательным для платформы модульных тестов Microsoft для управляемого кода для любого класса, содержащего методы модульных тестов, которые необходимо выполнить в обозревателе тестов.

  • Каждый метод теста, который требуется обозреватель тестов щелкните должен иметь атрибут [TestMethod].

У других классов в проекте модульного теста, которые не имеют атрибут [TestClass], а также имеет другие методы в тестовых классов, у которых атрибут [TestMethod].Можно использовать эти другие классы и методы в методах теста.

Создание первого тестового метода

В этой процедуре будут написаны методы модульного теста для проверки поведения метода Debit класса BankAccount.Метод был приведен выше.

Путем анализа тестируемого метода можно определить, что необходимо проверить по крайней мере три поведения.

  1. Метод создает исключение [ArgumentOutOfRangeException], если размер кредита превышает баланс.

  2. Он также создает ArgumentOutOfRangeException, если размер кредита меньше нуля.

  3. Если проверка пунктов 1.) и 2.) проходит успешно, то метод вычитает размер кредита из баланса счета.

В первом тесте проверим, что при допустимом размере кредита (со значением, меньшим баланса счета и большим нуля) со счета снимается нужная сумма.

Для создания тестового метода

  1. Добавьте инструкцию using BankAccountNS; к файлу BankAccountTests.cs.

  2. Добавьте следующий метод к классу BankAccountTests:

    // unit test code
    [TestMethod]
    public void Debit_WithValidAmount_UpdatesBalance()
    {
        // arrange
        double beginningBalance = 11.99;
        double debitAmount = 4.55;
        double expected = 7.44;
        BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
    
        // act
        account.Debit(debitAmount);
    
        // assert
        double actual = account.Balance;
        Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
    }
    

Метод достаточно прост.Создаем новый объект BankAccount с начальным балансом, а затем снимаем допустимое значение.Используем платформу модульных тестов Microsoft для метода AreEqual управляемого кода, чтобы проверить соответствие конечного баланса ожидаемому.

ms182532.collapse_all(ru-ru,VS.110).gifТребования к тестовому методу

Тестовый метод должен удовлетворять следующим требованиям:

  • Метод должен быть отмечен атрибутом [TestMethod].

  • Метод должен вернуть void.

  • Метод не должен содержать параметров.

Построение и запуск теста

Для построения и запуска теста

  1. В меню Построение выберите Построить решение.

    При наличии ошибок появляется окно UnitTestExplorer с указанием Debit_WithValidAmount_UpdatesBalance в списке Незапущенные тесты.Если обозреватель тестов не появляется после успешного построения, то выберите в меню пункт Тест, далее Windows, а затем Обозреватель тестов.

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

  3. В описываемом случае тест завершится неудачей.Тестируемый метод будет перемещен в Неудачные тесты.группу.Выберите этот метод в Обозревателе тестов в нижней части окна для просмотра сведений.

Исправьте код и повторно запустите тесты

Анализ результатов теста

Результат теста содержит сообщение, описывающее возникшую ошибку.Для метода AreEquals сообщение отражает ожидаемый результат (параметр Expected<XXX>) и фактически полученный (параметр Actual<YYY> ).Ожидалось, что баланс будет уменьшен по отношению к первоначальному, но вместо этого произошло его увеличение на размер списания.

Повторная проверка кода Debit показывает, что модульный тест позволил успешно найти ошибку.Размер списания добавляется к балансу счета, вместо его вычитания.

Исправление ошибки

Для исправления ошибки просто замените строку

m_balance += amount;

на

m_balance -= amount;

Повторный запуск теста

В Обозревателе тестов выберите Выполнить все для повторного выполнения теста.Красно-зеленая строка состояния станет зеленой и тест будет перемещен в группу Пройденные тесты.

Использование модульных тестов для улучшения кода

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

Анализ проблем

После создания тестового метода, проверяющего корректное списывание нужного значение в методе Debit, вернемся к остальным вариантам исходного анализа:

  1. Метод создает исключение ArgumentOutOfRangeException, если размер кредита превышает баланс.

  2. Он также создает ArgumentOutOfRangeException, если размер кредита меньше нуля.

Создание тестовых методов

Первая попытка создания тестового метода для решения этих проблем кажется перспективной:

//unit test method
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // act
    account.Debit(debitAmount);

    // assert is handled by ExpectedException
}

Атрибут ExpectedExceptionAttribute был использован для подтверждения правильности возникающего исключения.Данный атрибут приводит к тому, что тест завершается неудачей, если не возникает исключения ArgumentOutOfRangeException.Запуск теста как с положительными, так и с отрицательными значениями debitAmount, а также временное изменение тестируемого метода для возвращения универсального исключения ApplicationException при размере списания меньшего нуля, показывает, что тест работает правильно.Чтобы проверить случай, когда размер списания превышает баланс, необходимо:

  1. Создать новый тестовый метод с именем Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.

  2. Скопировать код метода из Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange в новый метод.

  3. Присвоить debitAmount значение, превышающее баланс.

Запуск тестов

Запуск двух методов с разными значениями для debitAmount показывает, что тесты корректно обрабатывают оставшиеся варианты.Запуск всех трех тестов позволяет убедиться в том, что все варианты исходного анализе были правильно покрыты тестами.

Продолжение анализа

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

Повторно просмотрев тестируемый метод можно заметить, что оба условных оператора используют конструктор ArgumentOutOfRangeException, который получает имя аргумента в качестве параметра:

throw new ArgumentOutOfRangeException("amount");

Поиск в библиотеке MSDN обнаруживает существование конструктора, сообщающего более детальную информацию.ArgumentOutOfRangeException(String, Object, String) включает имя аргумента, значения аргументов и определяемое пользователем сообщение.Мы можем изменить тестируемый метод для использования данного конструктора.Даже лучше, можно использовать открытые для общего доступа члены типа для указания ошибок.

Выполнение рефакторинга тестируемого кода

Сначала определим две константы для сообщений об ошибках в области видимости класса:

// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";

Изменим два условных оператора в методе Debit:

// method under test
// ...
    if (amount > m_balance)
    {
        throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
    }

    if (amount < 0)
    {
        throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
    }
// ...

Выполнение рефакторинга тестовых методов

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

  • Сработает предупреждение, что свойство ActualValue исключения (второй параметр конструктора ArgumentOutOfRangeException ) больше начального баланса.В данном случае необходимо сравнить свойство ActualValue исключения со значением переменной beginningBalance тестового метода, а также проверить, что значение ActualValue больше нуля.

  • Сработает предупреждение, что сообщение (третий параметр конструктора) включает указанное DebitAmountExceedsBalanceMessage в классе BankAccount.

Метод StringAssert.Contains платформы модульных тестов Microsoft позволяет проверить второй параметр без вычислений, необходимых в первом случае.

Вторая попытка корректировки Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange будет выглядеть следующим образом:

[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange()
{
    // arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\

    // act
    try
    {
        account.Debit(debitAmount);
    }
    catch (ArgumentOutOfRangeException e)
    {
        // assert
        StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
    }
}

Повторное тестирование, изменение и анализ

При повторной проверке тестируемого метода на различных значениях получаем следующие факты:

  1. При верном обнаружении ошибки в случае, если debitAmount больше баланса, проверка утверждения Contains проходит успешно, исключение игнорируется и тестовый метод завершается успешно.Это соответствует ожидаемому поведению.

  2. Если используется debitAmount, то проверка утверждения завершается неудачей, так как возвращается неверное сообщение об ошибке.Проверка утверждения также завершается неудачей, если ввести временное исключение ArgumentOutOfRange на другом шаге в коде тестируемого метода.Это слишком хорошо.

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

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

Однако повторное тестирование показывает, что тест проходит неуспешно при перехватывании верного исключения.Оператор catch сбрасывает исключение и метод продолжает выполняться, приводя к ошибочному срабатыванию следующего утверждения.Чтобы разрешить эту новую проблему, добавим оператор return после StringAssert.Повторное тестирование подтверждает, что все проблемы были решены.Наша окончательная версия Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange выглядит следующим образом:

[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange()
{
    // arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\

    // act
    try
    {
        account.Debit(debitAmount);
    }
    catch (ArgumentOutOfRangeException e)
    {
        // assert
        StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
        return;
    }
    Assert.Fail("No exception was thrown.")
}

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