Пошаговое руководство. Создание и запуск модульных тестов для управляемого кода
В этой статье описывается создание, выполнение и настройка ряда модульных тестов с помощью платформы модульных тестов Майкрософт для управляемого кода и обозревателя тестов Visual Studio. Начните с проекта C#, который находится в процессе разработки, создайте тесты для проверки его кода, запустите тесты и изучите результаты. Затем вы измените код проекта и повторно запустите тесты. Если вы хотите получить концептуальный обзор этих задач перед выполнением этих действий, ознакомьтесь с основами модульных тестов. Если вы хотите автоматически генерировать тесты из существующего кода, ознакомьтесь с Создание заглушек методов модульного тестирования из кода.
Создание проекта для тестирования
Откройте Visual Studio.
В окне запуска выберите Создать проект.
Найдите и выберите шаблон проекта консольного приложения C# для .NET, а затем нажмите кнопку Далее.
Заметка
Если шаблон консольного приложения не отображается, его можно установить в окне Создание нового проекта. В сообщении Не можете найти то, что вы ищете? выберите ссылку Установить больше инструментов и функций. Затем в установщике Visual Studio выберите рабочую нагрузку разработки классических приложений .NET.
Назовите проект Bank, а затем нажмите кнопку Далее.
Либо выберите рекомендуемую целевую платформу, либо .NET 8, а затем выберите Создать.
Проект Bank создается и отображается в обозревателе решений с открытым файлом Program.cs в редакторе кода.
Заметка
Если Program.cs не открыт в редакторе, дважды щелкните файл Program.cs в обозревателе решений , чтобы открыть его.
Замените содержимое Program.cs следующим кодом C#, определяющим класс, BankAccount:
using System; namespace BankAccountNS { /// <summary> /// Bank account demo class. /// </summary> public class BankAccount { private readonly string m_customerName; private double m_balance; private BankAccount() { } public BankAccount(string customerName, double balance) { m_customerName = customerName; m_balance = balance; } public string CustomerName { get { return m_customerName; } } public double Balance { get { return m_balance; } } public void Debit(double amount) { if (amount > m_balance) { throw new ArgumentOutOfRangeException("amount"); } if (amount < 0) { throw new ArgumentOutOfRangeException("amount"); } m_balance += amount; // intentionally incorrect code } public void Credit(double amount) { if (amount < 0) { throw new ArgumentOutOfRangeException("amount"); } m_balance += amount; } public static void Main() { BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99); ba.Credit(5.77); ba.Debit(11.22); Console.WriteLine("Current balance is ${0}", ba.Balance); } } }
Переименуйте файл в BankAccount.cs, щелкнув правой кнопкой мыши и выбрав Переименовать в обозревателе решений .
В меню Сборка щелкните Сборка решения (или нажмите клавиши CTRL + SHIFT + B).
Теперь у вас есть проект с методами, которые можно протестировать. В этой статье тесты сосредоточены на методе Debit
. Метод Debit
вызывается при выводе денег из счета.
Создание проекта модульного теста
В меню файла выберите Добавить>Новый проект.
Совет
Вы также можете щелкнуть правой кнопкой мыши на решении в обозревателе решений и выбрать Добавить>новый проект.
Введите test в поле поиска, выберите C# в качестве языка, а затем выберите шаблон C# MSTest Test Project для .NET и нажмите кнопку Далее.
Заметка
В Visual Studio 2019 версии 16.9 шаблон проекта MSTest проект модульного теста.
Назовите проект BankTests и нажмите Далее.
Либо выберите рекомендуемую целевую платформу, либо .NET 8, а затем выберите Создать.
Начиная с Visual Studio 2022 версии 17.10, можно также выбрать тестовый прогонщик. Для средства выполнения тестов можно выбрать VSTest или MSTest. Для получения дополнительной информации о различиях между средствами запуска тестов см. раздел сравнения Microsoft.Testing.Platform и VSTest.
Проект BankTests добавляется в решение Bank.
В проекте BankTests добавьте ссылку на проект Bank.
В обозревателе решений выберите зависимости в проекте BankTests, а затем выберите Добавить ссылку (или Добавить ссылку на проект) в меню правой кнопкой мыши.
В диалоговом окне Диспетчера ссылок разверните узел Проекты, выберите Решение, а затем проверьте элемент Bank.
Нажмите кнопку ОК.
Создание тестового класса
Создайте тестовый класс для проверки класса BankAccount
. Вы можете использовать файл UnitTest1.cs, созданный шаблоном проекта, но присвойте файлу и классу более описательные имена.
Переименование файла и класса
Чтобы переименовать файл, в обозревателе решенийвыберите файл UnitTest1.cs в проекте BankTests. В контекстном меню выберите Переименовать (или нажмите клавишу F2), а затем переименуйте файл в BankAccountTests.cs.
Чтобы переименовать класс, поместите курсор на
UnitTest1
в редакторе кода, щелкните правой кнопкой мыши и выберите пункт Переименовать (или нажмите клавишу F2). Введите BankAccountTests и затем нажмите Enter.
Теперь файл BankAccountTests.cs содержит следующий код:
// The 'using' statement for Test Tools is in GlobalUsings.cs
// using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BankTests
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
Добавьте инструкцию using
Добавьте инструкцию using
в тестовый класс, чтобы иметь возможность обращаться к проекту, который проверяется, без использования полных имен. В верхней части файла класса добавьте:
using BankAccountNS;
Требования к классу тестирования
Минимальные требования для тестового класса:
Атрибут
[TestClass]
требуется для любого класса, содержащего методы модульного теста, которые необходимо запустить в обозревателе тестов.Каждый метод теста, который требуется распознать обозревателе тестов, должен иметь атрибут
[TestMethod]
.
В проекте модульного теста можно использовать другие классы, которые не имеют атрибута [TestClass]
, а другие методы можно использовать в классах тестов, которые не имеют атрибута [TestMethod]
. Вы можете обращаться к другим классам и методам из ваших методов тестирования.
Создание первого метода теста
В этой процедуре необходимо написать методы модульного теста, чтобы проверить поведение метода Debit
класса BankAccount
.
Есть по крайней мере три поведения, которые необходимо проверить:
Метод выдает ArgumentOutOfRangeException, если сумма дебета больше, чем баланс.
Метод создает ArgumentOutOfRangeException, если сумма дебетовой суммы меньше нуля.
Если сумма дебета действительна, метод вычитает сумму дебетовой суммы из баланса счета.
Совет
Вы можете удалить метод TestMethod1
по умолчанию, так как он не будет использоваться в этом пошаговом руководстве.
Создание метода тестирования
Первый тест проверяет, что допустимая сумма (т. е. меньше баланса учетной записи и больше нуля) выводит правильную сумму из учетной записи. Добавьте следующий метод в этот класс BankAccountTests
:
[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
с начальным балансом, а затем выводит допустимую сумму. Он использует метод Assert.AreEqual для проверки правильности конечного баланса. Такие методы, как Assert.AreEqual
, Assert.IsTrueи другие часто используются в модульном тестировании. Дополнительные концептуальные сведения о написании юнит-теста см. в написании тестов.
Требования к методу тестирования
Метод теста должен соответствовать следующим требованиям:
Он украшен атрибутом
[TestMethod]
.Возвращается
void
.Он не может иметь параметры.
Сборка и запуск теста
В меню Сборка выберите Сборка решения (или нажмите CTRL + SHIFT + B).
Если обозреватель тестов не открыт, откройте его, выбрав обозреватель тестов> (или обозреватель тестов>Windows>) в верхней строке меню (или нажмите Ctrl + E, T).
Выберите Запустить все, чтобы запустить тест (или нажмите клавиши CTRL + R, V).
Во время выполнения теста строка состояния в верхней части окна обозревателя тестов анимирована. В конце тестового запуска полоса становится зеленой, если все методы тестирования проходят, или становится красной, если хотя бы один из тестов не пройден.
В этом случае тест завершается ошибкой.
Выберите метод в обозревателе тестов , чтобы просмотреть сведения в нижней части окна.
Исправление кода и повторное выполнение тестов
Результат теста содержит сообщение, описывающее сбой. Чтобы увидеть это сообщение, возможно, потребуется углубиться. Для метода AreEqual
в сообщении отображается ожидаемое и полученное на самом деле сообщение. Вы ожидали, что баланс уменьшится, но вместо этого он увеличился на сумму вывода.
Модульный тест обнаружил ошибку: сумма снятия была добавлена к балансу счета, когда она должна была быть вычтена .
Исправлена ошибка
Чтобы исправить ошибку, в файле BankAccount.cs замените строку:
m_balance += amount;
с:
m_balance -= amount;
Повторное выполнение теста
В обозревателя тестоввыберите выполнить все для повторного запуска теста (или нажмите клавиши CTRL + R, V). Красная или зеленая полоса становится зеленой, чтобы указать, что тест прошел.
обозреватель тестов
обозреватель тестов
Использование модульных тестов для улучшения кода
В этом разделе описывается, как итеративный процесс анализа, разработки модульных тестов и рефакторинг может помочь сделать рабочий код более надежным и эффективным.
Анализ проблем
Вы создали тестовый метод, чтобы убедиться, что допустимая сумма правильно вычитается в методе Debit
. Теперь убедитесь, что метод выбрасывает исключение ArgumentOutOfRangeException, если дебетовая сумма:
- больше баланса или
- меньше нуля.
Создание и запуск новых методов тестирования
Создайте метод теста для проверки правильности поведения, если сумма дебетовой суммы меньше нуля:
[TestMethod]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
// Arrange
double beginningBalance = 11.99;
double debitAmount = -100.00;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// Act and assert
Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => account.Debit(debitAmount));
}
Используйте метод ThrowsException для утверждения о том, что возникло правильное исключение. Этот метод приводит к сбою теста, если не выбрасывается ArgumentOutOfRangeException. Если вы временно изменяете метод, который тестируется, чтобы создать более универсальный ApplicationException, если сумма дебетовой суммы меньше нуля, тест работает правильно, то есть завершается сбоем.
Чтобы проверить случай, когда сумма снята больше баланса, сделайте следующее:
Создайте новый метод тестирования с именем
Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
.Скопируйте текст метода из
Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange
в новый метод.Задайте для
debitAmount
число больше, чем баланс.
Выполните два теста и убедитесь, что они проходят.
Продолжить анализ
Тестируемый метод можно улучшить дальше. При текущей реализации у нас нет способа узнать, какое условие (amount > m_balance
или amount < 0
) привело к возникновению исключения во время теста. Мы просто знаем, что ArgumentOutOfRangeException
был брошен где-то в методе. Было бы лучше, если бы мы могли определить, какое условие в BankAccount.Debit
вызвало исключение (amount > m_balance
или amount < 0
), чтобы мы могли быть уверены, что наш метод правильно выполняет проверку аргументов.
Просмотрите тестируемый метод (BankAccount.Debit
) еще раз и обратите внимание, что оба условных оператора используют конструктор ArgumentOutOfRangeException
, который просто принимает имя аргумента в качестве параметра:
throw new ArgumentOutOfRangeException("amount");
Существует конструктор, который можно использовать для получения более богатой информации: ArgumentOutOfRangeException(String, Object, String) включает имя аргумента, значение аргумента и определяемое пользователем сообщение. Вы можете переписать тестируемый метод, чтобы использовать этот конструктор. Более того, вы можете использовать общедоступные члены типа, чтобы указать ошибки.
Рефакторинг кода при тестировании
Сначала определите две константы для сообщений об ошибках в пределах класса. Поместите определения в тестируемый класс, BankAccount
:
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";
Затем измените две условные инструкции в методе Debit
:
if (amount > m_balance)
{
throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}
if (amount < 0)
{
throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}
Рефакторинг методов тестирования
Перефакторите методы тестирования, удалив вызов Assert.ThrowsException. Оберните вызов Debit()
в блок try/catch
, перехватите ожидаемое исключение и проверьте связанное с ним сообщение. Метод Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert.Contains предоставляет возможность сравнения двух строк.
Теперь Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
может выглядеть вот так:
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// Arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// Act
try
{
account.Debit(debitAmount);
}
catch (System.ArgumentOutOfRangeException e)
{
// Assert
StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
}
}
Повторное тестирование, перезапись и повторный анализ
В настоящее время метод тестирования не обрабатывает все случаи, которые он должен. Если метод Debit
не вызвал ArgumentOutOfRangeException, когда debitAmount
был больше баланса (или меньше нуля), тест будет считаться успешным. Этот сценарий не подходит, так как вы хотите, чтобы метод теста завершился ошибкой, если исключение не возникает.
Этот результат является ошибкой в методе тестирования. Чтобы устранить проблему, добавьте утверждение Assert.Fail в конце метода теста, чтобы обработать ситуацию, когда исключение не возникает.
Повторное выполнение теста показывает, что тест теперь завершается ошибкой, если правильное исключение поймано. Блок catch
перехватывает исключение, но метод продолжает выполняться, и дает сбой на новом утверждении Assert.Fail. Чтобы устранить эту проблему, добавьте инструкцию return
после StringAssert
в блоке catch
. Повторное выполнение теста подтверждает, что устранена эта проблема. Последняя версия Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange
выглядит следующим образом:
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// Arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// Act
try
{
account.Debit(debitAmount);
}
catch (System.ArgumentOutOfRangeException e)
{
// Assert
StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
return;
}
Assert.Fail("The expected exception was not thrown.");
}
Заключение
Улучшения кода теста привели к более надежным и информативным методам тестирования. Но, что еще более важно, они также улучшили тестируемый код.
Совет
В этом пошаговом руководстве используется платформа модульного тестирования Майкрософт для управляемого кода. Test Explorer также может выполнять тесты из сторонних фреймворков модульных тестов, имеющих адаптеры для Test Explorer. Дополнительные сведения см. в установке сторонних фреймворков модульного тестирования.
Связанное содержимое
Сведения о том, как выполнять тесты из командной строки, см. в VSTest.Console.exe параметрах командной строки.