Unit-тесты на C#
А вы когда-нибудь задумывались о необходимости тестирования разрабатываемых приложений? Сегодня я попробую показать важность применения unit-тестов, которые призваны помочь в обнаружении ошибок на ранних этапах работы, что в последующем приводит к экономии ваших средств и ресурсов.
В процессе написания ПО у меня возникло понимание о целесообразности применения unit-тестов.
В моей практике появилось несколько проектов, в которых мне довелось писать unit-тесты, каждый из которых выполнял определенную роль — поиск ошибок в основных алгоритмах кода, нагрузочное тестирование и отладка бэкенда веб-приложения.
В каждой из поставленных задач unit-тесты оказались эффективны, позволив существенно сократить время работы и обеспечить своевременное обнаружение ошибок кода.
Согласно данным[1] исследований, цена ошибки в ходе разработки и поддержании ПО экспоненциально возрастает при несвоевременном их обнаружении.
На представленном рисунке видно, что при выявлении ошибки на этапе формирования требований мы получим экономию средств в соотношении 200:1 по сравнению с их обнаружением на этапе поддержки.
Среди всех тестов львиную долю занимают именно unit-тесты. В классическом понимании unit-тесты позволяют быстро и автоматически протестировать отдельные части ПО независимо от остальных.
Рассмотрим простой пример создания unit-тестов. Для этого создадим консольное приложение Calc, которое умеет делить и суммировать числа.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calc < class Program < static void Main(string[] args) < >> >
Добавляем класс, в котором будут производиться математические операции.
using System; namespace Calc < ///
public class Calculator < ///
/// Первое число /// Второе число ///
/// /// /// public double AddWithInc(double n1, double n2) < return n1 + n2 + 1; >> >
Так, в методе Div производится операция деления числа n1 на число n2. Если передаваемое число n2 будет равняться нулю, то такая ситуация приведет к исключению. Для этого знаменатель этой операции проверяется на равенство нулю.
Метод AddWithInc производит сложение двух передаваемых чисел и инкрементацию полученного результата суммирования на единицу.
На следующем шаге добавим в решение проект тестов.
Пустой проект unit-тестов:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CalcTests < [TestClass] public class UnitTest1 < [TestMethod] public void TestMethod1() < >> >
Переименуем наш проект: «SimpleCalculatorTests». Добавляем ссылку на проект Calc.
В проекте Calc содержатся 2 метода, которые надо протестировать на корректность работы. Для этого создадим 3 теста, которые будут проверять операцию деления двух чисел, операцию деления на нуль и операцию сложения двух чисел и инкрементацию полученной суммы.
Добавляем в проект тест для проверки метода AddWithInc.
В тесте создаются 3 переменные — это аргументы, передаваемые в метод AddWithInc, и ожидаемый результат, возвращаемый этим методом. Результат выполнения метода будет записан в переменную result.
На следующем шаге происходит сравнение ожидаемого результата с реальным числом метода AddWithInc. При совпадении результата с ожидаемым числом, то есть числом 6, тест будет считаться положительным и пройденным. Если полученный результат будет отличаться от числа 6, то тест считается проваленным.
Следующим тестом мы будем проверять метод Div
[TestMethod] public void Div_4Div2_Returned2() < // arrange var calc = new Calculator(); double arg1 = 4; double arg2 = 2; double expected = 2; // act double result = calc.Div(arg1, arg2); // assert Assert.AreEqual(expected, result); >
Аналогичным образом создаются два аргумента и ожидаемый результат выполнения метода Div. Если результат деления 4/2 в методе равен 2, то тест считается пройдённым. В противном случае — не пройденным.
Следующий тест будет проверять операцию деления на нуль в методе Div.
[TestMethod] [ExpectedException(typeof(DivideByZeroException), «Oh my god, we can’t divison on zero»)] public void Div_4Div0_ZeroDivException() < // arrange var calc = new Calculator(); double arg1 = 4; double arg2 = 0; // act double result = calc.Div(arg1, arg2); // assert >
Тест будет считаться пройденным в случае возникновения исключения DivideByZeroException — деление на нуль. В отличии от двух предыдущих тестов, в этом тесте нет оператора Assert. Здесь обработка ожидаемого результата производится с помощью атрибута «ExpectedException».
Если аргумент 2 равен нулю, то в методе Divвозникнет исключение — деление на нуль. В таком случае тест считается пройденным. В случае, когда аргумент 2 будет отличен от нуля, тест считается проваленным.
Для запуска теста необходимо открыть окно Test Explorer. Для этого нажмите Test -> Windows -> Test Explorer (Ctrl+, T). В появившемся окне можно увидеть 3 добавленных теста:
Для запуска всех тестов нажмите Test -> Run -> All tests (Ctrl+, A).
Если тесты выполнятся успешно, в окне Test Explorer отобразятся зеленые пиктограммы, обозначающие успешность выполнения.
В противном случае пиктограммы будут красными.
Unit-тесты имеют обширную, строго не регламентированную область применения — зачастую фантазия самого автора кода подсказывает решение нестандартных задач с помощью этого инструмента.
Случай написания тестов для бэкенда веб-приложения в моей практике является не совсем стандартным вариантом применения unit-тестов. В данной ситуации unit-тесты вызывали методы контроллера MVC-приложения, в то же время передавая тестовые данные в контроллеры.
Далее в режиме отладки шаг за шагом выполнялись все действия алгоритма. В этом случае применение тестов позволило произвести быструю отладку бэкенда веб-приложения.
Существуют случаи, когда модульные тесты применять нецелесообразно. Например, если вы веб-разработчик, который делает сайты, где мало логики. В таких случаях имеются только представления, как, например, для сайтов-визиток, рекламных сайтов, или, когда вам поставлена задача реализовать пилотный проект «на посмотреть, что получится». У вас ограниченные ресурсы и время. А ПО будет работать только один день — для показа руководству.
Сжатые сроки, малый бюджет, размытые цели или довольно несложные требования — случаи, в которых вы не получите пользы от написания тестов.
Для определения целесообразности использования unit-тестов можно воспользоваться следующим методом: возьмите лист бумаги и ручку и проведите оси X и Y. X — алгоритмическая сложность, а Y — количество зависимостей. Ваш код поделим на 4 группы.
- Простой код (без каких-либо зависимостей)
- Сложный код (содержащий много зависимостей)
- Сложный код (без каких-либо зависимостей)
- Не очень сложный код (но с зависимостями)
Первое — это случай, когда все просто и тестировать здесь ничего не нужно.
Второе — случай, когда код состоит только из плотно переплетенных в один клубок реализаций, перекрестно вызывающих друг друга. Тут неплохо было бы провести рефакторинг. Именно поэтому тесты писать в этом случае не стоит, так как код все равно будет переписан.
Третье — случай алгоритмов, бизнес-логики и т.п. Важный код, поэтому его нужно покрыть тестами.
Четвертый случай — код объединяет различные компоненты системы. Не менее важный случай.
Последние два случая — это ответственная логика. Особенно важно писать тесты для ПО, которые влияют на жизни людей, экономическую безопасность, государственную безопасность и т.п.
Подводя итог всего описанного выше хочется отметить, что тестирование делает код стабильным и предсказуемым. Поэтому код, покрытый тестами, гораздо проще масштабировать и поддерживать, т.к. появляется большая доля уверенности, что в случае добавления нового функционала нигде ничего не сломается. И что не менее важно — такой код легче рефакторить.
[1] Данные взяты из книги «Технология разработки программного обеспечения» автора Ларисы Геннадьевны Гагариной
Модуль unittest: тестируем свои программы

Представьте, что вы написали какую-либо программу, а теперь хотите проверить, правильно ли она работает. Что вы для этого сделаете? Скорее всего, вы запустите её несколько раз с различными входными данными, и убедитесь в правильности выдаваемого ответа.
А теперь вы что-то поменяли и снова хотите проверить корректность программы. Запускать ещё несколько раз? А если потом снова что-то поменяется? Нельзя ли как-то автоматизировать это дело?
Оказывается, можно. В Python встроен модуль unittest, который поддерживает автоматизацию тестов, использование общего кода для настройки и завершения тестов, объединение тестов в группы, а также позволяет отделять тесты от фреймворка для вывода информации.
Для автоматизации тестов, unittest поддерживает некоторые важные концепции:
- Испытательный стенд (test fixture) — выполняется подготовка, необходимая для выполнения тестов и все необходимые действия для очистки после выполнения тестов. Это может включать, например, создание временных баз данных или запуск серверного процесса.
- Тестовый случай (test case) — минимальный блок тестирования. Он проверяет ответы для разных наборов данных. Модуль unittest предоставляет базовый класс TestCase, который можно использовать для создания новых тестовых случаев.
- Набор тестов (test suite) — несколько тестовых случаев, наборов тестов или и того и другого. Он используется для объединения тестов, которые должны быть выполнены вместе.
- Исполнитель тестов (test runner) — компонент, который управляет выполнением тестов и предоставляет пользователю результат. Исполнитель может использовать графический или текстовый интерфейс или возвращать специальное значение, которое сообщает о результатах выполнения тестов.
Модуль unittest предоставляет богатый набор инструментов для написания и запуска тестов. Однако достаточно лишь некоторых из них, чтобы удовлетворить потребности большинства пользователей.
Вот короткий скрипт для тестирования трех методов строк:
Тестовый случай создаётся путём наследования от unittest.TestCase. 3 отдельных теста определяются с помощью методов, имя которых начинается на test. Это соглашение говорит исполнителю тестов о том, какие методы являются тестами.
Суть каждого теста — вызов assertEqual() для проверки ожидаемого результата; assertTrue() или assertFalse() для проверки условия; assertRaises() для проверки, что метод порождает исключение. Эти методы используются вместо обычного assert для того, чтобы исполнитель тестов смог взять все результаты и оформить отчёт.
Методы setUp() и tearDown() (которые в данном простом случае не нужны) позволяют определять инструкции, выполняемые перед и после каждого теста, соответственно.
Последние 2 строки показывают простой способ запуска тестов. unittest.main() предоставляет интерфейс командной строки для тестирования программы. Будучи запущенным из командной строки, этот скрипт выводит отчёт, подобный этому:
unittest может быть использован из командной строки для запуска модулей с тестами, классов или даже отдельных методов:
python -m unittest test_module1 test_module2 -m unittest test_module.TestClass -m unittest test_module.TestClass.test_method
Можно также указывать путь к файлу:
python -m unittest tests/test_something.py
С помощью флага -v можно получить более детальный отчёт:
python -m unittest -v test_module
Для нашего примера подробный отчёт будет таким:
-b ( --buffer ) - вывод программы при провале теста будет показан, а не скрыт, как обычно.
-c ( --catch ) — Ctrl+C во время выполнения теста ожидает завершения текущего теста и затем сообщает результаты на данный момент. Второе нажатие Ctrl+C вызывает обычное исключение KeyboardInterrupt.
-f ( --failfast ) — выход после первого же неудачного теста.
--locals (начиная с Python 3.5) — показывать локальные переменные для провалившихся тестов.
Обнаружение тестов
unittest поддерживает простое обнаружение тестов. Для совместимости с обнаружением тестов, все файлы тестов должны быть модулями или пакетами, импортируемыми из директории верхнего уровня проекта (см. подробнее о правилах наименования модулей ).
Обнаружение тестов реализовано в TestLoader.discover(), но может быть использовано из командной строки:
project_directory -m unittest discover
-v ( --verbose ) — подробный вывод.
-s ( --start-directory ) directory_name — директория начала обнаружения тестов (текущая по умолчанию).
-p ( --pattern ) pattern — шаблон названия файлов с тестами (по умолчанию test*.py).
-t ( --top-level-directory ) directory_name — директория верхнего уровня проекта (по умолчанию равна start-directory ).
Организация тестового кода
Базовые блоки тестирования это тестовые случаи — простые случаи, которые должны быть проверены на корректность.
Тестовый случай создаётся путём наследования от unittest.TestCase.
Тестирующий код должен быть самостоятельным, то есть никак не зависеть от других тестов.
Простейший подкласс TestCase может просто реализовывать тестовый метод (метод, начинающийся с test). Вымышленный пример:
Заметьте, что для того, чтобы проверить что-то, мы используем один из assert\*() методов.
Тестов может быть много, и часть кода настройки может повторяться. К счастью, мы можем определить код настройки путём реализации метода setUp(), который будет запускаться перед каждым тестом:
Мы также можем определить метод tearDown(), который будет запускаться после каждого теста:
Можно разместить все тесты в том же файле, что и сама программа (таком как widgets.py), но размещение тестов в отдельном файле (таком как test_widget.py) имеет много преимуществ:
- Модуль с тестом может быть запущен автономно из командной строки.
- Тестовый код может быть легко отделён от программы.
- Меньше искушения изменить тесты для соответствия коду программы без видимой причины.
- Тестовый код должен изменяться гораздо реже, чем программа.
- Протестированный код может быть легче переработан.
- Тесты для модулей на C должны быть в отдельных модулях, так почему же не быть последовательным?
- Если стратегия тестирования изменяется, нет необходимости изменения кода программы.
Пропуск тестов и ожидаемые ошибки
unittest поддерживает пропуск отдельных тестов, а также классов тестов. Вдобавок, поддерживается пометка теста как «не работает, но так и надо».
Пропуск теста осуществляется использованием декоратора skip() или одного из его условных вариантов.
Классы также могут быть пропущены:
Ожидаемые ошибки используют декоратор expectedFailure():
Очень просто сделать свой декоратор. Например, следующий декоратор пропускает тест, если переданный объект не имеет указанного атрибута:
Декораторы, пропускающие тесты или говорящие об ожидаемых ошибках:
@unittest.skip(reason) — пропустить тест. reason описывает причину пропуска.
@unittest.skipIf(condition, reason) — пропустить тест, если condition истинно.
@unittest.skipUnless(condition, reason) — пропустить тест, если condition ложно.
@unittest.expectedFailure — пометить тест как ожидаемая ошибка.
Для пропущенных тестов не запускаются setUp() и tearDown(). Для пропущенных классов не запускаются setUpClass() и tearDownClass(). Для пропущенных модулей не запускаются setUpModule() и tearDownModule().
Различение итераций теста с помощью подтестов
Когда некоторые тесты имеют лишь незначительные отличия, например некоторые параметры, unittest позволяет различать их внутри одного тестового метода, используя менеджер контекста subTest().
Например, следующий тест:
даст следующий отчёт:
Без использования подтестов, выполнение будет остановлено после первой ошибки, и ошибку будет сложнее диагностировать, потому что значение i не будет показано:
Модуль unittest предоставляет множество функций для самых различных проверок:
assertTrue(x) — bool(x) is True
assertFalse(x) — bool(x) is False
assertIsNot(a, b) — a is not b
assertIsNone(x) — x is None
assertIsNotNone(x) — x is not None
assertNotIn(a, b) — a not in b
assertIsInstance(a, b) — isinstance(a, b)
assertNotIsInstance(a, b) — not isinstance(a, b)
assertRaises(exc, fun, *args, **kwds) — fun(*args, **kwds) порождает исключение exc
assertRaisesRegex(exc, r, fun, *args, **kwds) — fun(*args, **kwds) порождает исключение exc и сообщение соответствует регулярному выражению r
assertWarns(warn, fun, *args, **kwds) — fun(*args, **kwds) порождает предупреждение
assertWarnsRegex(warn, r, fun, *args, **kwds) — fun(*args, **kwds) порождает предупреждение и сообщение соответствует регулярному выражению r
assertAlmostEqual(a, b) — round(a-b, 7) == 0
assertNotAlmostEqual(a, b) — round(a-b, 7) != 0
assertGreaterEqual(a, b) — a >= b
assertNotRegex(s, r) — not r.search(s)
assertCountEqual(a, b) — a и b содержат те же элементы в одинаковых количествах, но порядок не важен
Для вставки кода на Python в комментарий заключайте его в теги
- Модуль csv - чтение и запись CSV файлов
- Создаём сайт на Django, используя хорошие практики. Часть 1: создаём проект
- Онлайн-обучение Python: сравнение популярных программ
- Книги о Python
- GUI (графический интерфейс пользователя)
- Курсы Python
- Модули
- Новости мира Python
- NumPy
- Обработка данных
- Основы программирования
- Примеры программ
- Типы данных в Python
- Видео
- Python для Web
- Работа для Python-программистов
- Сделай свой вклад в развитие сайта!
- Самоучитель Python
- Карта сайта
- Отзывы на книги по Python
- Реклама на сайте
Начало работы с модульным тестированием
Область применения:
Visual Studio Visual Studio для Mac
Visual Studio Code ![]()
Используйте Visual Studio, чтобы определить и запустить модульные тесты для обеспечения работоспособности кода, нужного объема протестированного кода, а также для обнаружения ошибок и сбоев, прежде чем с ними столкнутся клиенты. Выполняйте модульные тесты регулярно, чтобы обеспечить правильную работу вашего кода.
В этой статье код использует C# и C++, иллюстрации представлены на C#, однако концепции и функции применимы к языкам .NET, C++, Python, JavaScript и TypeScript.
Создание модульных тестов.
В этом разделе описывается создание проекта модульного теста.
-
Откройте проект, который хотите протестировать в Visual Studio. В целях демонстрации примера модульного теста в этой статье тестируется простой консольный проект C# или C++ "Hello World" с именем HelloWorld. Пример кода для такого проекта выглядит следующим образом:
namespace HelloWorld < public class Program < public static void Main() < Console.WriteLine("Hello World!"); >> >
#include int main()
Совет Только для C#: вы можете создавать проекты модульных тестов из кода, используя более быстрый метод. Дополнительные сведения см. в разделе Создание проектов модульных тестов и методов теста. Чтобы использовать этот метод для .NET Core или .NET Standard, требуется Visual Studio 2019 или более поздней версии.
На следующем рисунке показано модульный тест MSTest, поддерживаемый в .NET.

Нажмите Далее, выберите имя для тестового проекта и нажмите Создать.

Нажмите Далее, выберите имя для тестового проекта и нажмите Создать.
Проект добавляется в решение.




- MSTest
- NUnit
- xUnit
- Платформа Microsoft Native Unit Test Framework
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using System; namespace HelloWorldTests < [TestClass] public class UnitTest1 < private const string Expected = "Hello World!"; [TestMethod] public void TestMethod1() < using (var sw = new StringWriter()) < Console.SetOut(sw); HelloWorld.Program.Main(); var result = sw.ToString().Trim(); Assert.AreEqual(Expected, result); >> > >
using NUnit.Framework; using System.IO; using System; namespace HelloWorldTests < public class Tests < private const string Expected = "Hello World!"; [SetUp] public void Setup() < >[Test] public void TestMethod1() < using (var sw = new StringWriter()) < Console.SetOut(sw); HelloWorld.Program.Main(); var result = sw.ToString().Trim(); Assert.AreEqual(Expected, result); >> > >
using System; using Xunit; using System.IO; namespace HelloWorldTests < public class UnitTest1 < private const string Expected = "Hello World!"; [Fact] public void Test1() < using (var sw = new StringWriter()) < Console.SetOut(sw); HelloWorld.Program.Main(); var result = sw.ToString().Trim(); Assert.Equal(Expected, result); >> > >
#include "pch.h" #include "CppUnitTest.h" #include "../HelloWorldUnitTestCPP/HelloWorldUnitTestCPP.cpp" // Update using your project name using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace HelloWorldTests < TEST_CLASS(HelloWorldTests) < public: TEST_METHOD(TestMethod) < std::string expected = "Hello World!\n"; std::stringstream buffer; std::streambuf* sbuf = std::cout.rdbuf(); // Save cout's buffer std::cout.rdbuf(buffer.rdbuf()); // Redirect cout to the stringstream buffer // Call main() in your test int result = main(); // When finished, redirect cout to the original buffer std::cout.rdbuf(sbuf); std::cout >; >
Выполнение модульных тестов
- Откройте обозреватель тестов. Откройте обозреватель тестов, выбрав Тест>Обозреватель тестов в верхней строке меню (или нажмите клавиши CTRL + E, T).
- Запустите модульные тесты, нажав Запустить все (или нажмите клавиши CTRL + R, V).


После завершения зеленый флажок указывает, что тест пройден. Красный значок "x" указывает на сбой теста.


Используйте обозреватель тестов для запуска модульных тестов из встроенной платформы тестирования (MSTest) или сторонней платформы тестирования. Вы можете группировать тесты по категориям, фильтровать список тестов, а также создавать, сохранять и запускать списки воспроизведения тестов. Кроме того, с его помощью можно выполнять отладку тестов и анализировать производительность тестов и покрытие кода.
Просмотр результатов динамического модульного тестирования (Visual Studio Enterprise)
Если вы используете платформу тестирования MSTest, xUnit или NUnit в Visual Studio 2017 или более поздней версии, можно просмотреть динамические результаты модульных тестов.
Для выполнения этих шагов требуется Visual Studio Enterprise вместе с кодом .NET и одной из следующих платформ тестирования: MSTest, xUnit или NUnit.
- Включите Live Unit Testing в меню Тест, выбрав Тест>Live Unit Testing>Запустить.






Дополнительные сведения о Live Unit Testing см. в разделе Live Unit Testing.
Использование сторонней платформы тестирования
Вы можете запускать модульные тесты в Visual Studio с помощью сторонних тестовых платформ, таких как NUnit, Boost или Google C++ Testing Framework, в зависимости от вашего языка программирования. Чтобы использовать стороннюю платформу тестирования, выполните следующие действия.
- Используйте диспетчер пакетов NuGet, чтобы установить пакет NuGet для выбранной платформы.
- (.NET) Начиная с Visual Studio 2017 версии 14.6, Visual Studio включает в себя предварительно настроенные шаблоны тестовых проектов для платформ тестирования NUnit и xUnit. Шаблоны также содержат необходимые пакеты NuGet, чтобы обеспечить поддержку.
- (C++) В Visual Studio 2017 и более поздних версиях уже включены некоторые платформы, такие как Google C++ Testing Framework. Дополнительные сведения см. в статье Написание модульных тестов для C/C++ в Visual Studio.
Добавление проекта модульного тестирования:
- Откройте решение, содержащее код, который нужно протестировать.
- В обозревателе решений щелкните решение правой кнопкой мыши и выберите Добавить>Создать проект.
- Выберите шаблон проекта модульного теста. Для того примера — NUnit


Щелкните Далее, назовите проект и нажмите кнопку Создать.
Пошаговое руководство. Создание и запуск модульных тестов для управляемого кода
Область применения:
Visual Studio Visual Studio для Mac
Visual Studio Code ![]()
В этой статье приводится подробное описание процесса создания, запуска и настройки набора модульных тестов с помощью платформы модульных тестов Майкрософт для управляемого кода и обозревателя тестов Visual Studio. В руководстве производится создание проекта C#, находящегося в стадии разработки, создание тестов для проверки его кода, запуск тестов и изучение результатов. После этого производится изменение кода проекта и повторный запуск тестов. Если вы хотите получить концептуальный обзор этих задач перед выполнением этих шагов, см. Основы модульного тестирования.
Создайте проект для тестирования
- Откройте Visual Studio.
- На начальном экране выберите Создать проект.
- Найдите и выберите шаблон проекта консольного приложения C# для .NET и нажмите кнопку "Далее".
Примечание. Если шаблон Консольное приложение отсутствует, его можно установить из окна Создание проекта. В сообщении Не нашли то, что искали? выберите ссылку Установка других средств и компонентов. После этого в Visual Studio Installer выберите рабочую нагрузку Разработка классических приложений .NET.
Примечание. Если файл Program.cs не откроется в редакторе, дважды щелкните Program.cs в обозревателе решений, чтобы открыть его.
using System; namespace BankAccountNS < /// /// Bank account demo class. /// 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 $", ba.Balance); > > >
Теперь у вас есть проект с методами, которые можно протестировать. В этой статье тестирование проводится на примере метода Debit . Метод Debit вызывается, когда денежные средства снимаются со счета.
Создание проекта модульного теста
Совет В обозревателе решений щелкните решение правой кнопкой мыши и выберите пункты Добавить>Создать проект.
Примечание. в Visual Studio 2019 версии 16.9 шаблон проекта MSTest имеет формат проекта модульного теста.
Создание тестового класса
Создание тестового класса, чтобы проверить класс BankAccount . Можно использовать UnitTest1.cs, созданный в шаблоне проекта, но лучше дать файлу и классу более описательные имена.
Переименуйте файл и класс
- Чтобы переименовать файл, в обозревателе решений выберите файл UnitTest1.cs в проекте BankTests. В контекстном меню выберите команду Переименовать (или нажмите клавишу F2), а затем переименуйте файл в BankAccountTests.cs.
- Чтобы переименовать класс, поместите курсор в UnitTest1 в редакторе кода, щелкните правой кнопкой мыши и выберите команду Переименовать (или нажмите клавиши F2). Введите название BankAccountTests и нажмите клавишу ВВОД.
Файл 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> Test Обозреватель) в верхней строке меню (или нажмите клавиши 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(() => account.Debit(debitAmount)); >
Мы используем метод ThrowsException для подтверждения правильности созданного исключения. Этот метод приводит к тому, что тест не будет пройден, если не возникнет исключения ArgumentOutOfRangeException. Если временно изменить тестируемый метод для вызова более общего исключения ApplicationException при значении суммы по дебету меньше нуля, то тест работает правильно, т. е. завершается сбоем.
Чтобы проверить случай, когда размер списания превышает баланс, выполните следующие действия:
- Создать новый метод теста с именем Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange .
- Скопировать тело метода из Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange в новый метод.
- Присвоить debitAmount значение, превышающее баланс.
Выполните два теста и убедитесь, что они пройдены.
Продолжение анализа
Еще раз проанализировав тестируемый метод 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
Рефакторинг тестовых методов
Выполните рефакторинг методов теста, удалив вызов 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."); >
Заключение
Усовершенствования тестового кода привели к созданию более надежных и информативных методов теста. Но что более важно, в результате был также улучшен тестируемый код.
В этом пошаговом руководстве используется платформа модульных тестов Microsoft для управляемого кода. Обозреватель тестов также может запускать тесты c платформ модульных тестов стороннего производителя, которые имеют адаптеры для обозревателя тестов. Дополнительные сведения см. в разделе Установка платформ модульного тестирования сторонних поставщиков.
Связанный контент
Сведения о запуске тестов из командной строки см. в разделе Параметры командной строки VSTest.Console.exe.