Это отрывок из электронной книги «Единичное тестирование » Марка Клифтона, любезно предоставленный Syncfusion.
Модульный тест состоит из двух вещей:
- Класс, представляющий тестовое устройство.
- Методы в классе, представляющие юнит-тесты.
Visual Studio автоматически создаст заглушку для тестового проекта, с которого мы и начнем.
Создание проекта модульного теста в Visual Studio
Модульные тесты обычно размещаются в отдельном проекте (в результате получается отдельная сборка) из кода вашего приложения. В Visual Studio 2008 или 2012 вы можете создать проект модульного теста, щелкнув правой кнопкой мыши решение и выбрав Add, а затем New Project из всплывающего меню:
В открывшемся диалоговом окне выберите тестовый проект:
Visual Studio 2008 создаст файл-заглушку «UnitTest1.cs» (если вы выбрали язык C #) с множеством полезных комментариев в заглушке. Visual Studio 2012 создает гораздо более сложную заглушку:
используя Систему; использование Microsoft.VisualStudio.TestTools.UnitTesting; пространство имен VS2012UnitTestProject1 { [TestClass] открытый класс UnitTest1 { [Метод испытания] public void TestMethod1 () { } } }
Для пользователей Visual Studio 2008
Visual Studio 2008 также создаст класс TestContext — его больше нет в VS2012, и мы его игнорируем, — вместо этого используйте предыдущий заглушку из VS2012.
Также удалите файл «ManualTest1.mht», в противном случае вам будет предложено выбрать результаты теста и ввести примечания к тесту вручную.
Испытательные приспособления
Обратите внимание, что класс украшен атрибутом TestClass. Это определяет тестовое устройство — набор тестовых методов.
Методы испытаний
Обратите внимание, что метод украшен атрибутом TestMethod. Это определяет метод, который будет запускать тестовое устройство.
Класс Assert
Класс assert определяет следующие статические методы, которые можно использовать для проверки вычисления метода:
- AreEqual / AreNotEqual
- AreSame / AreNotSame
- IsTrue / IsFalse
- IsNull / IsNotNull
- IsInstanceOfType / IsNotInstanceOfType
Многие из этих утверждений перегружены, и рекомендуется ознакомиться с полной документацией, которую предоставляет Microsoft.
Основы утверждения
Обратите внимание, что в следующих примерах используется VS2008.
Утверждения являются основой каждого теста. В отношении результатов теста можно сделать множество утверждений. Для начала напишем простое утверждение, в котором говорится «один равен одному», другими словами, трюизм:
[TestClass] открытый класс UnitTest1 { [Метод испытания] public void TestMethod1 () { Assert.IsTrue (1 == 1); } }
Запустите тест, который должен привести к «Пройдено»:
AreEqual / AreNotEqual
Методы AreEqual и AreNotEqual сравнивают:
- объекты
- двойники
- одиночный разряд
- строки
- типизированные данные
Они принимают форму сравнения ожидаемого (первый параметр) с фактическим (второй параметр) значением. Что касается одинарных и двойных значений, то можно указать «с определенной точностью». Наконец, все перегрузки имеют возможность отображать сообщение (необязательно отформатированное), если утверждение не выполнено.
Что касается равенства объектов, этот метод сравнивает, идентичны ли экземпляры:
открытый класс AnObject { } [Метод испытания] public void ObjectEqualityTest () { AnObject object1 = new AnObject (); AnObject object2 = new AnObject (); Assert.AreNotEqual (object1, object2); }
Предыдущий тест проходит, так как object1 и object2 не равны. Однако если класс переопределяет метод Equals, тогда равенство основывается на сравнении, выполненном методом Equals, реализованным в классе. Например:
открытый класс AnObject { public int SomeValue {get; устанавливать; } public override bool Equals (объектный объект) { return SomeValue == ((AnObject) obj) .SomeValue; } } [Метод испытания] public void ObjectEqualityTest () { AnObject object1 = new AnObject () {SomeValue = 1}; AnObject object2 = new AnObject () {SomeValue = 1}; Assert.AreEqual (object1, object2); }
AreSame / AreNotSame
Эти два метода проверяют, совпадают ли экземпляры (или нет). Например:
[Метод испытания] public void SamenessTest () { AnObject object1 = new AnObject () {SomeValue = 1}; AnObject object2 = new AnObject () {SomeValue = 1}; Assert.AreNotSame (object1, object2); }
Несмотря на то, что класс AnObject переопределяет оператор Equals, предыдущий тест проходит, поскольку экземпляры двух объектов не совпадают.
IsTrue / IsFalse
Эти два метода позволяют вам проверить истинность сравнения значений. С точки зрения читабельности методы IsTrue и IsFalse обычно используются для сравнения значений , тогда как AreEqual и AreSame обычно используются для сравнения экземпляров (объектов).
Например:
[Метод испытания] public void IsTrueTest () { AnObject object1 = new AnObject () {SomeValue = 1}; Assert.IsTrue (object1.SomeValue == 1); }
Это подтверждает это значение свойства.
IsNull / IsNotNull
Эти два теста проверяют, является ли объект нулевым или нет:
[Метод испытания] public void IsNullTest () { AnObject object1 = null; Assert.IsNull (object1); }
IsInstanceOfType / IsNotInstanceOfType
Эти два метода проверяют, является ли объект экземпляром определенного типа (или нет). Например:
открытый класс AnotherObject { } [Метод испытания] public void TypeOfTest () { AnObject object1 = new AnObject (); Assert.IsNotInstanceOfType (object1, typeof (AnotherObject)); }
неубедительный
Метод Assert.Inconclusive можно использовать, чтобы указать, что ни тест, ни функциональность, стоящая за тестом, еще не реализованы, и поэтому тест не является окончательным.
Что происходит, когда утверждение не выполняется?
Что касается модульного тестирования Visual Studio, то при сбое утверждения метод Assert создает исключение AssertFailedException. Это исключение никогда не должно обрабатываться вашим тестовым кодом.
Другие классы утверждений
Есть два других класса утверждений:
- CollectionAssert
- StringAssert
Как видно из их названий, эти утверждения работают с коллекциями и строками соответственно.
Сборник утверждений
Эти методы реализованы в классе Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert. Обратите внимание, что параметр коллекции в этих методах предполагает, что коллекция будет реализовывать ICollection (в отличие от NUnit, который ожидает IEnumerable).
AllItemsAreInstanceOfType
Это утверждение проверяет, что объекты в коллекции имеют один и тот же тип, который включает в себя производные типы ожидаемого типа, как показано здесь:
открытый класс A {} открытый класс B: A {} [TestClass] открытый класс CollectionTests { [Метод испытания] public void InstancesOfTypeTest () { Список точек = новый список () {новая точка (1, 2), новая точка (3, 4)}; Список <объект> элементы = новый Список <объект> () {новый B (), новый A ()}; CollectionAssert.AllItemsAreInstancesOfType (points, typeof (Point)); CollectionAssert.AllItemsAreInstancesOfType (items, typeof (A)); } }
AllItemsAreNotNull
Это утверждение проверяет, что объекты в коллекции не являются нулевыми.
AllItemsAreUnique
Этот тест гарантирует, что объекты в коллекции являются уникальными. Если сравнивать структуры:
[Метод испытания] public void AreUniqueTest () { Список точек = новый список () {новая точка (1, 2), новая точка (1, 2)}; CollectionAssert.AllItemsAreUnique (точки); }
структуры сравниваются по значению, а не по экземпляру — предыдущий тест не проходит. Однако, даже если класс переопределяет метод Equals:
открытый класс AnObject { public int SomeValue {get; устанавливать; } public override bool Equals (объектный объект) { return SomeValue == ((AnObject) obj) .SomeValue; } }
этот тест проходит:
[Метод испытания] public void AreUniqueObjectsTest () { Список <объект> элементы = новый список <объект> () { новый AnObject () {SomeValue = 1}, новый AnObject () {SomeValue = 1} }; CollectionAssert.AllItemsAreUnique (пункты); }
AreEqual / AreNotEqual
Эти тесты утверждают, что две коллекции равны. Методы включают в себя перегрузки, которые позволяют предоставить метод сравнения. Если объект переопределяет метод Equals, этот метод будет использоваться для определения равенства. Например:
[Метод испытания] public void AreEqualTest () { Список <объект> itemList1 = новый список <объект> () { новый AnObject () {SomeValue = 1}, новый AnObject () {SomeValue = 2} }; Список <объект> itemList2 = новый список <объект> () { новый AnObject () {SomeValue = 1}, новый AnObject () {SomeValue = 2} }; CollectionAssert.AreEqual (itemList1, itemList2); }
Эти две коллекции равны, потому что класс AnObject переопределяет метод Equals (см. Предыдущий пример).
Обратите внимание, что для передачи утверждения списки должны иметь одинаковую длину и считаться не равными, если списки идентичны, за исключением другого порядка. Сравните это с утверждением AreEquivalent, описанным ниже.
AreEquivalent / AreNotEquivalent
Это утверждение сравнивает два списка и считает списки равных элементов эквивалентными независимо от порядка. К сожалению, в реализации есть ошибка, так как этот тест не пройден:
[Метод испытания] public void AreEqualTest () { Список <объект> itemList1 = новый список <объект> () { новый AnObject () {SomeValue = 1}, новый AnObject () {SomeValue = 2} }; Список <объект> itemList2 = новый список <объект> () { новый AnObject () {SomeValue = 2}, новый AnObject () {SomeValue = 1} }; CollectionAssert.AreEquivalent (itemList1, itemList2); }
с сообщением об ошибке:
CollectionAssert.AreEquivalent не удалось. Ожидаемая коллекция содержит 1 случай (ы) из. Фактическая коллекция содержит 0 вхождений.
Принимая во внимание, что реализация NUnit этого утверждения проходит:
Содержит / DoesNotContain
Это утверждение проверяет, что объект содержится в коллекции:
[Метод испытания] public void ContainsTest () { Список <объект> itemList = новый Список <объект> () { новый AnObject () {SomeValue = 1}, новый AnObject () {SomeValue = 2} }; CollectionAssert.Contains (itemList, new AnObject () {SomeValue = 1}); }
используя метод Equals (если он переопределен) для выполнения теста на равенство.
IsSubsetOf / IsNotSubsetOf
Это утверждение проверяет, что первый параметр (подмножество) содержится в коллекции второго параметра (надмножество).
[Метод испытания] public void SubsetTest () { строка подмножество = "abc"; string superset = "1d2c3b4a"; CollectionAssert.IsSubsetOf (subset.ToCharArray (), superset.ToCharArray ()); }
Обратите внимание, что тест подмножества не проверяет порядок или последовательность — он просто проверяет, содержатся ли элементы в списке подмножеств в надмножестве.
Строковые утверждения
Эти методы реализованы в классе Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert:
- Содержит
- Матчи / DoesNotMatch
- StartsWith / EndsWith
Это обсуждается далее.
Содержит
Метод Contains утверждает, что подмножество (обратите внимание, что это второй параметр) содержится в строке (первый параметр). Например, этот тест проходит:
[TestClass] открытый класс StringTests { [Метод испытания] public void ContainsTest () { строка подмножество = "abc"; string superset = "123abc456"; StringAssert.Contains (суперсет, подмножество); } }
Матчи / DoesNotMatch
Этот метод утверждает, что строка (первый параметр) соответствует шаблону регулярного выражения, предоставленному во втором параметре.
StartsWith / EndsWith
Этот метод утверждает, что строка (первый параметр) либо начинается, либо заканчивается другой строкой (второй параметр).
Исключения
Исключения могут быть проверены без написания блоков try-catch вокруг метода test. Например, пока вы могли бы написать это:
[Метод испытания] public void CatchingExceptionsTest () { пытаться { Разделить (5, 0); } catch (ArgumentOutOfRangeException) { // молча принять исключение как допустимое. } }
Намного удобнее использовать атрибут ExpectedException в тестовом методе:
[Метод испытания] [ExpectedException (TypeOf (ArgumentOutOfRangeException))] public void BadParameterTest () { Разделить (5, 0); }
Другие полезные атрибуты
Существуют некоторые дополнительные атрибуты, которые полезны для запуска набора тестов, а также отдельных тестов, которые улучшают возможность повторного использования и читаемость базы кода модульных тестов.
Настройка / Teardown
Модуль модульного тестирования Visual Studio предоставляет четыре дополнительных атрибута метода:
- ClassInitialize
- ClassCleanup
- TestInitialize
- TestCleanup
Эти атрибуты предшествуют и следуют за выполнением всех тестов в приборе (классе), а также до и после каждого теста в приборе.
Обратите внимание, что методы, украшенные этим атрибутом, должны быть статическими.
ClassInitialize
Если метод украшен этим атрибутом, код в методе выполняется до запуска всех тестов в приборе. Обратите внимание, что этот метод требует параметр TestContext.
Этот метод полезен для выделения ресурсов или создания экземпляров классов, на которые полагаются все тесты в приборе. Важное соображение, касающееся ресурсов и объектов, созданных во время инициализации устройства, заключается в том, что эти ресурсы и объекты следует рассматривать только для чтения. Тестам не рекомендуется изменять состояние ресурсов и объектов, от которых зависят другие тесты. Это включает в себя подключения к таким службам, как база данных и веб-службы, чье подключение может быть переведено в недопустимое состояние в результате ошибки в тесте, что делает недействительными все остальные тесты. Кроме того, порядок выполнения тестов не гарантируется. Изменение состояния ресурса и объекта, созданного при инициализации прибора, может привести к побочным эффектам в зависимости от порядка выполнения тестов.
ClassCleanup
Метод, украшенный этим атрибутом, отвечает за освобождение ресурсов, закрытие соединений и т. Д., Которые были созданы во время инициализации класса. Этот метод всегда будет выполняться после запуска тестов внутри прибора, независимо от того, успешны они или нет.
TestInitialize
Подобно атрибуту ClassInitialize, метод, украшенный этим атрибутом, будет выполняться для каждого теста до его запуска. Одной из целей этого атрибута является обеспечение того, чтобы ресурсы или объекты, выделенные кодом ClassInitialize, инициализировались в известное состояние перед выполнением каждого теста.
TestCleanup
В дополнение к атрибуту TestInitialize методы, украшенные TestCleanup, будут выполняться при завершении каждого теста.
Настройка и демонтаж
Следующий код демонстрирует последовательность настройки и демонтажа прибора по отношению к фактическим тестам:
[TestClass] открытый класс SetupTeardownFlow { [ClassInitialize] public static void SetupFixture (контекст TestContext) { Debug.WriteLine («Настройка прибора»); } [ClassCleanup] public static void TeardownFixture () { Debug.WriteLine («Устранение Fixture.»); } [TestInitialize] public void SetupTest () { Debug.WriteLine («Настройка теста.»); } [TestCleanup] public void TeardownTest () { Debug.WriteLine («Тестовый разрыв.»); } [Метод испытания] публичный void TestA () { Debug.WriteLine («Тест А.»); } [Метод испытания] public void TestB () { Debug.WriteLine («Тест Б.»); } }
Запуск этого устройства приводит к следующей трассировке выходных данных отладки:
Настройка прибора. Испытательная установка. Тест А. Тест Разрушение. Испытательная установка. Тест Б. Тест Разрушение. Fixture Teardown.
Как показано в предыдущем примере, прибор инициализируется — затем для каждого теста выполняется настройка теста и код разрыва, после чего в конце завершается разбор прибора.
Менее часто используемые атрибуты
В следующем разделе описаны менее часто используемые атрибуты.
AssemblyInitialize / AssemblyCleanup
Методы, украшенные этим атрибутом, должны быть статическими и выполняться при загрузке сборки. Возникает вопрос: что если в сборке имеется более одного тестового устройства?
[TestClass] открытый класс Fixture1 { [AssemblyInitialize] public static void AssemblyInit (контекст TestContext) { // ... какая-то операция } } [TestClass] открытый класс Fixture2 { [AssemblyInitialize] public static void AssemblyInit (контекст TestContext) { // ... какая-то операция } }
Если вы попробуете это, тестовый механизм не сможет запустить какие-либо модульные тесты, сообщив:
«UTA013: UnitTestExamplesVS2008.Fixture2: невозможно определить более одного метода с атрибутом AssemblyInitialize внутри сборки».
Поэтому для сборки может существовать только один метод AssemblyInitialize и один метод AssemblyCleanup, независимо от количества тестовых приборов в этой сборке. Поэтому рекомендуется, чтобы никакие фактические тесты не помещались в класс, который определяет эти методы:
[TestClass] открытый класс AssemblyFixture { [AssemblyInitialize] public static void AssemblySetup (контекст TestContext) { Debug.WriteLine («Инициализация сборки.»); } [AssemblyCleanup] public static void AssemblyTeardown () { Debug.WriteLine («Очистка сборки.»); } }
в результате получается следующая последовательность выполнения:
Сборка инициализируется. Настройка прибора. Испытательная установка. Тест А. Тест Разрушение. Испытательная установка. Тест Б. Тест Разрушение. Fixture Teardown. Сборка Очистка.
Обратите внимание на дополнительные вызовы инициализации и очистки сборки.
игнорировать
Этот метод может украшать определенные методы или целые приборы.
Игнорировать метод испытаний
Если этот атрибут украшает метод теста:
[TestMethod, игнорировать] публичный void TestA () { Debug.WriteLine («Тест А.»); }
тест не запустится. К сожалению, панель результатов теста Visual Studio не указывает на то, что в настоящее время игнорируются тесты:
Сравните это с NUnit, который ясно показывает проигнорированные тесты:
Дисплей NUnit помечает все тестовое дерево как «неизвестное», если один или несколько методов теста помечены как «Игнорировать»
Игнорировать тестовое приспособление
Методы всего прибора могут быть проигнорированы с помощью атрибута Ignore на уровне класса:
[TestClass, Игнорировать] открытый класс SetupTeardownFlow { ... и т.д ...
Очистка кеша теста
Если вы добавите атрибут Ignore в метод, вы можете заметить, что Visual Studio все еще выполняет тест. Необходимо очистить кэш теста для Visual Studio, чтобы получить изменения. Один из способов сделать это — очистить решение и восстановить его.
владелец
Используемый для отчетности, этот атрибут описывает лицо, ответственное за метод модульного тестирования.
DeploymentItem
Если модульные тесты выполняются в отдельной папке развертывания, этот атрибут можно использовать для указания файлов, которые требуются классу теста или методу теста для запуска. Вы можете указать файлы или папки для копирования в папку развертывания и при желании указать целевой путь относительно папки развертывания.
Описание
Используемый для отчетности, этот атрибут предоставляет описание метода теста. Как ни странно, этот атрибут доступен только в тестовых методах и недоступен в тестовых классах.
HostType
Для методов тестирования этот атрибут используется для указания хоста, на котором будет выполняться модульный тест.
приоритет
Этот атрибут не используется механизмом тестирования, но может быть использован, путем отражения, вашим собственным тестовым кодом. Полезность этого атрибута сомнительна.
Рабочий элемент
Если вы используете Team Foundation Server (TFS), вы можете использовать этот атрибут в методе тестирования, чтобы указать идентификатор рабочего элемента, назначенный TFS для конкретного модульного теста.
CssIteration / CssProjectStructure
Эти два атрибута используются во взаимосвязи с TeamBuild и TestManagementService и позволяют вам указать итерацию проекта, которой соответствует метод теста.
Параметризованное тестирование с атрибутом источника данных
Модуль модульного тестирования Microsoft поддерживает CSV, XML или источники данных базы данных для параметризованного тестирования. Это не совсем верно параметризованное тестирование (см., Как NUnit реализует параметризованное тестирование), потому что параметры не передаются методу модульного теста, а должны быть извлечены из источника данных и переданы тестируемому методу. Однако возможность загрузки тестовых данных в DataTable из различных источников полезна для автоматизации тестирования вождения.
CSV Источник данных
Текстовый файл с разделителями-запятыми может использоваться для источника данных:
Числитель, Знаменатель, Ожидаемый результат 10, 5, 2 20,5, 4 33, 3, 11
и используется в методе испытаний:
[TestClass] открытый класс DataSourceExamples { public TestContext TestContext {get; устанавливать; } [Метод испытания] [DataSource («Microsoft.VisualStudio.TestTools.DataSource.CSV», «C: \\ temp \\ csvData.txt», «csvData # txt», DataAccessMethod.Sequential)] public void CsvDataSourceTest () { int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Знаменатель"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r); } }
Это приводит к следующему выводу:
n = 10, d = 5, r = 2 n = 20, d = 5, r = 4 n = 33, d = 3, r = 11
Обратите внимание, что в окне результатов теста не отображаются параметры запуска (в отличие от NUnit):
Однако есть очевидные преимущества не отображать каждую тестовую комбинацию, особенно для больших наборов данных.
Источник данных XML
Имеется файл XML, такой как:
1
2
3
4
5
|
<Data>
<Row Numerator = «10» Denominator = «5» ExpectedResult = «2»/>
<Row Numerator = «20» Denominator = «5» ExpectedResult = «4»/>
<Row Numerator = «33» Denominator = «3» ExpectedResult = «11»/>
</Data>
|
Пример использования источника данных XML для модульного теста:
[Метод испытания] [DataSource («Microsoft.VisualStudio.TestTools.DataSource.XML», «C: \\ temp \\ xmlData.xml», "Row", DataAccessMethod.Sequential)] public void XmlDataSourceTest () { int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Знаменатель"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r); }
Обратите внимание, что кроме параметров атрибута источника данных, тестовый код такой же.
Источник данных базы данных
Таблица базы данных также может быть использована в качестве источника данных. Учитывая таблицу, такую как:
и данные:
Пример теста с использованием этих данных выглядит следующим образом:
[Метод испытания] [DataSource («System.Data.SqlClient», «Источник данных = INTERACX-HP; Начальный каталог = UnitTesting; Интегрированная безопасность = True», «DivideTestData», DataAccessMethod.Sequential)] public void XmlDataSourceTest () { int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Знаменатель"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r); }
Опять же, обратите внимание, что сам код тестового метода одинаков — единственное, что мы здесь сделали, изменили определение источника данных.
Атрибут TestProperty
Документация MSDN для этого атрибута иллюстрирует объявление пары имя-значение TestProperty и затем, используя отражение, получение имени и значения. Кажется, это тупой способ создания параметризованных тестов.
Кроме того, код, описанный в блоге Крейга Андеры , для использования атрибута TestProperty для параметризации процесса инициализации теста не влияет на коллекцию TestContext.Properties в Visual Studio 2008 или Visual Studio 2012.