Статьи

Модульное тестирование кратко: NUnit

Это отрывок из электронной книги «Единичное тестирование » Марка Клифтона, любезно предоставленный Syncfusion.

В следующей таблице сопоставляются атрибуты, используемые для написания тестов с NUnit с Visual Studio:

Атрибут NUnit

Атрибут Visual Studio

Описание

TestFixture

TestClass

Определяет тестовый прибор

Тестовое задание

Метод испытания

Определяет метод теста в классе тестового прибора

TestFixtureSetUp

ClassInitialize

Задает код, который запускается перед запуском всех методов тестирования в тестовом приборе.

TestFixtureTearDown

ClassCleanup

Задает код, который запускается после завершения всех тестов в приборе.

Настроить

TestInitialize

Определяет код для запуска перед выполнением каждого теста.

Срывать

TestCleanup

Определяет код для запуска при завершении каждого теста.

SetUpFixture (см. Ниже)

AssemblyInitialize

Задает код, который запускается при загрузке сборки, содержащей все тестовые приборы.

SetUpFixture (см. Ниже)

AssemblyCleanup

Задает код, который запускается при выгрузке сборки, содержащей все тестовые приборы.

игнорировать

игнорировать

Игнорирует конкретный тест.

Описание (также относится к тестовым приборам).

Описание

Описание метода испытаний. В NUnit этот атрибут также может украшать тестовое устройство.

Следующие атрибуты Visual Studio не соответствуют никаким атрибутам NUnit:

  • владелец
  • DeploymentItem
  • HostType
  • приоритет
  • Рабочий элемент
  • CssIteration
  • CssProjectStructure
  • TestProperty

Атрибут SetUpFixture, который применяется к классам, отличается от AssemblyInitialize и AssemblyCleanup в Visual Studio, поскольку он применяется к осветителям в данном пространстве имен. Если все ваши классы тестовых приборов находятся в одном и том же пространстве имен, то этот атрибут ведет себя аналогично AssemblyInitialize и AssemblyCleanup. Учитывая код:

В результате получается:

Тем не менее, добавив еще одно пространство имен:

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

Также см. «Действия сборки» в разделе «Определенные пользователем атрибуты действий» в следующем разделе.


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

  • категория
  • свита
  • Явный
  • Тайм-аут

Атрибут Category позволяет группировать тесты и запускать тесты в выбранных категориях. Этот атрибут может применяться к тестам в приборе, а также к отдельным тестам в приборе. Определенные категории могут быть выбраны в консоли приложения, используя аргументы / include и / exclude, или в программе запуска GUI:

NUnit Категории
NUnit Категории

Вкладка для выбора категорий для включения или исключения находится на левом краю бегунка GUI.

Атрибут Suite предоставляет возможность программно определять тестовые устройства, которые должен тестировать участник консоли. Эта опция доступна только в консоли. Концепция запуска только определенных тестов или наборов тестов (приспособлений) лучше поддерживается атрибутом Category, описанным ранее.

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

Использование этого атрибута — запуск только длительных тестов, когда это явно требуется, когда служба запущена и т. Д.

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

Сравните этот атрибут с атрибутом MaxTime, описанным ниже.

Также обратите внимание, что при запуске модульных тестов в отладчике атрибут Timeout игнорируется — в противном случае отлаживаемый тест может завершиться, поскольку вы вручную просматриваете код, просматриваете значения и т. Д.

  • культура
  • SetCulture
  • SetUICulture

Атрибут Культура может использоваться для прибора, чтобы определить культуры, для которых должны выполняться тесты. Тесты, которые специфичны для конкретной культуры, также должны быть украшены этим атрибутом, описывающим тестируемые культуры. Тест или прибор пропускаются, если текущая культура не соответствует культуре, для которой написан тест. Например:

В предыдущем коде прибор работает, если текущая культура имеет значение «fr» или «en»; тем не менее, TestA указывает, что тест должен выполняться только в том случае, если для культуры задано значение «fr.». Следовательно, это устройство приведет к:

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

поскольку «TestA» не запускается, потому что текущая культура не «фр.»

Такие тесты, как «TestB», в которых не указана какая-либо спецификация культуры, всегда выполняются, если только весь прибор не исключен, поскольку текущая культура не соответствует.

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

результаты в TestA не работает. Также обратите внимание на поведение TestB по умолчанию, когда культура установлена ​​в приборе и как она переопределяется в TestC:

Переопределение в TestC
Переопределение в TestC

Этот атрибут должен устанавливать культуру для пользовательского интерфейса; однако, это очевидно ничего не делает. Например (не пишите модульный тест, как это, это только для иллюстрации):

приводит к значению «1.2», отображаемому в моей текущей культуре, будучи en-US:

Атрибут SetUICulture ничего не делает
Атрибут SetUICulture ничего не делает

Если я изменю атрибут на «SetCulture», то пользовательский интерфейс отобразит значение в правильном формате культуры (возможно, вам придется щуриться, чтобы увидеть разницу между «1 балл 2» и «1 запятая 2»:

SetCulture изменяет представление пользовательского интерфейса
SetCulture изменяет представление пользовательского интерфейса

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

Например, этот тест:

приводит к следующему выводу (и обратите внимание, что график теста также отображает параметры теста):

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

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

  • Ценности
  • ValuesSource
  • комбинаторный
  • Попарно (не реализовано)
  • последовательный
  • случайный
  • Ассортимент
  • Прецедент
  • TestCaseSource

Как показано в предыдущем примере, атрибут Values ​​используется для указания значений, передаваемых в метод теста для каждого параметра. Поскольку это значения, назначенные в атрибуте, они должны иметь тип значения: константные выражения, выражения typeof или массив типов атрибутов.

Также обратите внимание, что NUnit по умолчанию будет проверять каждую комбинацию значений для каждого параметра (см. Атрибуты Combinatorial, Pairwise и Sequential).

С атрибутом ValueSource вы можете указать источник IEnumerable для значений определенного параметра. Источником может быть поле, свойство или метод отдельного класса или текущего класса. Например, все это допустимые способы выражения источников стоимости:

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

Тесты ValueSource
Тесты ValueSource

Этот атрибут является необязательным, поскольку NUnit автоматически применяет параметры теста во всех возможных комбинациях. Например:

приводит ко всем комбинациям «x» и «s», независимо от того, указано «Combinatorial»:

Комбинаторный тест
Комбинаторный тест

Этот атрибут существует, но не реализован — он предназначен для уменьшения количества комбинаций, когда заданы комбинаторные параметры; однако этот атрибут игнорируется.

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

Результаты в этом выводе:

Последовательный атрибут
Последовательный атрибут

Обратите внимание, что строка (имеющая только два значения) по умолчанию равна нулю для двух последних параметризованных тестов, а параметр int, имеющий три случая, по умолчанию равен 0 для последнего случая.

Атрибут Random может применяться с комбинаторными параметризованными тестами для предоставления случайных значений, а не конкретных значений для параметров. Случайные значения параметров всегда двойного типа. Например:

Результаты в пяти тестах со случайными значениями:

Случайный тест
Случайный тест

Минимальный и максимальный значения также могут быть указаны для диапазона случайных значений.

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

Атрибут Range указывает диапазон значений для тестирования. За исключением целочисленного диапазона, другие диапазоны (long, float, double) требуют указания шага. Шаг для целочисленных значений не является обязательным.

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

Вместо комбинаторного или последовательного параметризованного теста вы можете явно определить значения параметров, передаваемых в каждый тестовый набор для запуска для конкретного теста. Преимущество этого заключается в уменьшении количества комбинаторных тестов, а также в нацеливании на конкретные сценарии тестирования. Кроме того, атрибут TestCase включает в себя дополнительную характеристику возможности проверять возвращаемое значение (при условии, что это тип «значение») со значением, указанным в метаданных атрибута. Наконец, дополнительные свойства, такие как атрибуты зеркального отображения, отображаются в атрибуте TestCase.

Базовый тестовый пример выглядит следующим образом (обратите внимание на отсутствие атрибута TestFixture):

Однако реальная сила атрибута TestCase заключается в возможности напрямую применять тестовые примеры к методу и проверять результат:

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

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

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

Другие свойства, которые могут быть назначены TestFixture, проиллюстрированы в этом примере:

Есть также альтернативы, указывающие ожидаемое исключение:

  • ExpectedExceptionName
  • ExpectedExceptionMessage
  • MatchType

который позволяет вам указать полное имя (соответствующее значению свойства Type.FullName) или сообщение об исключении, а также способ сопоставления сообщения об исключении — Contains, Exact, Regex или StartsWith.

Атрибут TestCaseSource ведет себя подобно атрибуту ValueSource, описанному ранее; однако предпочтительной реализацией является указание типа класса, который реализует IEnumerable. Эта предпочтительная реализация ограничивает вас чем-то вроде этого:

Обратите внимание, что значения тестового примера — это int [] (целочисленный массив), в котором каждый элемент массива отображается на параметр в тестовой функции. Также отметьте еще раз, что атрибут Sequential указан для теста, гарантируя, что данные теста используются последовательно, а не комбинаторно.

Есть несколько других атрибутов NUnit, описанных далее.

Атрибут Platform может использоваться для указания платформы, для которой должен выполняться тест. Если платформа не соответствует текущей платформе, тест не запускается и даже не включается в игнорируемые тесты или общее количество тестов. Просмотрите документацию по адресу http://www.nunit.org/index.php?p=platform&r=2.6.2, чтобы увидеть список поддерживаемых спецификаторов платформы.

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

Атрибут MaxTime (если сравнивать с атрибутом Timeout, описанным ранее) приведет к сбою теста, если он превысит указанное время в миллисекундах. Однако, в отличие от атрибута Timeout, тест будет разрешено завершить.

Атрибут Repeat может применяться только к тестам и игнорируется для параметризованных тестов. Он инструктирует тестовый двигатель повторить тест указанное количество раз. Если какое-либо повторение теста не удается, остальные тесты не запускаются, и двигатель сообщает об ошибке.

С помощью этого атрибута вы можете указать дополнительные сборки (в файле AssemblyInfo), которые требуются для модульных тестов.

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

  • Сборка еще не выполняется в потоке MTA.
  • Содержащий прибор уже не работает в потоке MTA.

Если для всего устройства создан поток, все тесты в этом устройстве выполняются в новом потоке.

Этот атрибут, примененный к тесту или устройству, указывает, что тест должен выполняться в однопоточной квартире. NUnit создаст новый поток для теста, если:

  • Сборка еще не выполняется в потоке STA.
  • Содержащий прибор уже не работает в потоке STA.

Если для всего устройства создан поток, все тесты в этом устройстве выполняются в новом потоке.

Этот атрибут, примененный к тесту или устройству, указывает, что тест должен выполняться в отдельном потоке. NUnit создаст новый поток, если этот атрибут указан в файле AssemblyInfo:

Если в приборе указан этот атрибут, все тесты в приборе запускаются во вновь созданном потоке.

Если в тесте указан этот атрибут, тест будет выполняться во вновь созданном потоке.

Дополнительно можно указать состояние квартиры, что приводит к созданию потока MTA или STA.

Например:

Обратите внимание на идентификаторы потоков при выполнении теста:

Примеры потоков
Примеры потоков

Тест A и Тест C выполняются в потоке, созданном прибором, тогда как Тест B, указав атрибут RequThread, выполняется в отдельном потоке от потока других тестов в этом приборе.

Теория — это параметризованный тест, который проверяет допущения о передаваемых значениях. Если допущение не выполняется, тест не проходит. Работает с атрибутом Datapoint (s).

Эти два класса эквивалентны. Первый иллюстрирует использование атрибута Datapoint, второй — атрибут Datapoints:

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

В обоих случаях NUnit создаст все возможные комбинации значений из точек данных. Обратите внимание, как метод теста делает определенные предположения:

  • Знаменатель не может быть 0.
  • Ожидаемый результат должен быть равен н / д.

Причина второго предположения заключается в том, что тест не пройден для большинства комбинаций — например, 5/10 не равен 2. Как видно из прогона теста, допущения не приводят к провалу теста:

Теории
Теории

Комбинации значений параметров, которые не соответствуют допущениям, в тесте не учитываются. Типичный образец для теоретического теста:

  • Сформулируйте предположения о параметрах.
  • Выполните тест.
  • Сделайте утверждения о результатах.

Следует соблюдать осторожность при проверке теории из-за растущего числа комбинаций аргументов.

Также обратите внимание, что если явно не указано, NUnit автоматически заполняет комбинации значений параметров для типов bool и enum.


Атрибуты действий — это пользовательские атрибуты, которые позволяют составлять несвязанный код настройки теста для каждого теста. Зачастую размещение всего кода настройки в настройке прибора или пространства имен нарушает принцип единственной ответственности — некоторые тесты требуют определенных настроек, в то время как другие тесты требуют других настроек. Атрибуты действия позволяют вам точно указать настройку, которая требуется для теста.

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

Пользовательское действие является производным от TestActionAttribute:

или может быть получен из System.Attribute и реализовать интерфейс ITestAction:

В последнем случае также должно быть определено свойство «Цели».

Цель действия (либо прибор, либо набор) или метод тестирования можно определить с помощью перечисления ActionTargets (определено в NUnit.Framework):

Указание значения ActionTargets.Default позволяет применять определенный пользователем атрибут как к тестовым осветителям (классам), так и к тестам (методам). Однако при применении к осветителю (классу) атрибут ведет себя так, как если бы был указан ActionTargets.Suite — определяемое пользователем действие выполняется один раз для всего набора тестов.

Если ActionTargets.Test возвращается пользовательским действием, это меняет поведение при вызове пользовательского действия. Применительно к устройству (классу) пользовательское действие выполняется для каждого теста. Если пользовательский атрибут применяется к определенному тесту, то для этого конкретного теста выполняется пользовательское действие.

Если цель действия атрибута является набором, но используется в контексте метода тестирования, об ошибке не сообщается — действие просто не выполняется.

ActionTargets являются флагами, поэтому их можно комбинировать:

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

Этот класс создается с информацией о готовящемся приборе или тесте (из NUnit.Framework.TestDetails):

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

Придерживаясь большего в концепции Visual Studio для AssemblyInitialize и AssemblyCleanup, вы можете использовать пользовательское действие на основе сборки. Например, с предыдущим кодом, если в файл AssemblyInfo.cs добавлена ​​следующая строка:

тогда пользовательское действие будет выполняться при загрузке сборки:

Если целевым действием для пользовательского действия является Suite, пользовательское действие выполняется до и после всех тестов осветителей, ведя себя как «до или после всех наборов». Однако обратите внимание, что если указан TargetActions.Test, то действие выполняется до или после всех тестов во всех пакетах. Поэтому следует соблюдать осторожность в отношении возвращаемого значения свойства Target и желаемого поведения определенного пользователем действия.

Два вопроса могут быть:

  • Как передать информацию из прибора в пользовательское действие?
  • Как использовать объект, экземпляр которого определяется пользовательским действием в самом тесте?

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

и применяя его к светильнику:

приводит к следующему потоку испытаний:

Обратите внимание, что пользовательское действие для набора выполняется после настройки «пространства имен» и после настройки прибора. Если для определенного пользователем действия требуется некоторая информация, которую нельзя передать с помощью конструктора (конструкторы атрибутов ограничены константами), то может оказаться полезным подход, аналогичный описанному ниже. Однако имейте в виду, что определяемые пользователем действия должны быть как можно более автономными — следует избегать перепутывания с тестовыми состояниями.

Это невозможно без какой-либо формы пограничного контейнера. Чрезвычайно простая реализация — создать пакет объектов из словаря значения ключа:

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

Данные могут быть доступны в тестовых методах:

Существуют более сложные подходы, и предыдущие примеры предназначены для иллюстрации того, как координировать пользовательское действие с обменом информацией. В предыдущем примере есть множество проблем — безопасность типов, отсутствие проверок того, существует ли объект в контейнере и т. Д. — проблемы, которые должен обрабатывать надежный класс контейнера.


NUnit предоставляет те же или похожие утверждения в классе NUnit.Framework.Assert, что и в классе Assert в Visual Studio, как описано ранее. NUnit также предоставляет дополнительные утверждения:

Эти утверждения проверяют, является ли строка пустой или нет, или что коллекция пуста или нет:

Эти два утверждения работают с числовыми значениями и объектами, реализующими IComparable, утверждая, что первое значение больше или меньше второго значения. Цель этого утверждения — улучшить читаемость теста.

Эти два утверждения работают с числовыми значениями и объектами, реализующими IComparable, утверждая, что первое значение «больше или равно» или «меньше или равно» второму значению. Цель этого утверждения — улучшить читаемость теста.

Эта пара методов утверждает, что ожидаемый тип можно назначить из экземпляра объекта совместимого типа. Это похоже на утверждение IsInstanceOfType в тестовом движке Visual Studio. Например, учитывая:

Этот тест проходит, потому что объекту типа A может быть присвоен экземпляр типа B.

Неуниверсальная форма этого утверждения подтверждает, что исключение выдается (или нет) при вызове указанного метода. Например:

Этот тест проходит.

Универсальная версия помещает тип исключения в качестве универсального параметра:

Эти два метода утверждают, что метод генерирует тип исключения или один из его производных. Напротив, метод Throws до этого утверждает, что выбрасывается именно указанный тип исключения. Например, этот код передает:


В классе NUnit.Framework.CollectionAssert NUnit реализует те же утверждения, что и в классе CollectionAssert Visual Studio, с этими дополнительными утверждениями:

  • IsEmpty / IsNotEmpty
  • IsOrdered

Обратите внимание, что параметр коллекции в этих методах предполагает, что коллекция будет реализовывать IEnumerable (в отличие от тестовой среды Visual Studio, которая ожидает ICollection).

Метод CollectionAssert.IsEmpty такой же, как метод Assert.IsEmpty.

Этот метод проверяет, что значения в коллекции упорядочены. Например, потому что AnObject реализует IComparable:

этот тест проходит:


Эти методы утверждения являются членами класса NUnit.Framework.StringAssert и такие же, как и в классе StringAssert в Visual Studio, с несколькими отличиями:

  • AreEqualIgnoringCase
  • IsMatch

которые обсуждаются далее.

Утверждение «AreEqualIgnoringCase» выполняет сравнение двух строк, игнорируя регистр.

Этот метод похож на метод StringAssert.Matches в Visual Studio — он утверждает, что строка соответствует шаблону регулярного выражения.


Эти методы утверждения являются членами класса NUnit.Framework.FileAssert.

Эти методы утверждают, что два файла идентичны или нет. Побайтовое сравнение выполняется для двух файлов. Если файлы пусты, они считаются равными. Если один или оба файла не существуют, выдается исключение FileNotFoundException.


Эти методы утверждения являются членами класса NUnit.Framework.DirectoryAssert.

Эти методы утверждают, что файлы в двух каталогах одинаковы или нет.

Эти методы утверждают, что каталог пуст или нет.

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


В то время как одна из форм этого метода принимает булевский параметр в качестве подтверждения истины (ведет себя точно так же, как Assert.IsTrue), преобладающей формой этого метода является возможность сравнивать фактическое значение с использованием определенного ограничения. Документация NUnit по ограничениям довольно обширна в отношении ограничений, поэтому здесь будет приведен только краткий пример. Например, учитывая этот тест:

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

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

Этот метод проверяет, является ли двойное значение «не числом». Типичным случаем, когда значение не является числом, является деление на ноль или квадратный корень из отрицательного значения.


Класс assert также предоставляет несколько служебных методов:

Assert.Pass немедленно завершает тест, выдавая исключение SuccessException, в результате чего тест помечается как проходящий.

Assert.Fail немедленно завершает тест, выдавая исключение AssertionException, в результате чего тест помечается как неудачный.

Assert.Ignore немедленно завершает тест, выдавая IgnoreException, сообщая о том, что тест игнорируется.

Assert.Inconclusive немедленно завершает тест, выдавая исключение InconclusiveException, сообщая о том, что тест не завершен. Это может быть полезно, когда модульное тестирование или устройство определяет, что требуемая служба не работает, и поэтому тесты для этой службы не могут быть продолжены.