C#. NUnit. Делаем тесты правильно. Часть 2
Привет, сегодня мы продолжим знакомство с Framework NUnit. Если не в курсе, что это такое, можешь прочесть первую часть моей статьи в декабрьском номере. Я покажу еще пару интересных моментов, остальное все можно спокойно прочесть в документации по NUnit, там же и примеры есть.
Поэтому не откладываем дело в долгий ящик, запускаем Visual Studio, создаем пустой проект, подключаем NUnit и начинаем кодить.
Строковые тесты
Строковые тесты, как понятно из названия
, проверяют содержание строк.
Для работы со строками, будем использовать класс StringAssert. Допустим мы хотим проверить, что строка str1 содержит в себе строку str2. Тогда нам надо написать:
StringAssert.Contains(str2,str1);
Если надо убедиться,что str1 начинается с str2 или заканчивается,то соответственно пишем:
StringAssert.StartsWith(str2, str1 );
и
StringAssert.EndsWith( str2, str1);
Допустим, теперь мы хотим сравнить 2 строки str1 и str2 без учета регистра.Т.е. строки типа "Hello" и "HeLlO" должны считаться равнозначными. Для этого используем следующую конструкцию:
StringAssert.AreEqualIgnoringCase(str1,str2);
На этом упражнения со строками закончили, три-четыре!
Файловые тесты
Теперь посмотрим, как можно применить тестирования для работы с файлами. Основное направление здесь – проверка на равенство/неравенство 2 файлов. Используется 3 варианта:
   1. Сравнивать потоки Stream, которые читают данные из файлов (не путай с потоками Thread, которые решают другие задачи
).
   2. Сравнивать классы FileInfo, связанные с файлами.
   3. Сравнивать файлы путем указания в качестве аргументов их абсолютных путей.
3 вариант самый простой, поэтому остановимся на первых двух.
Для варианта с потоками, надо считать содержимое файлов в потоки, а потом передать их в качестве аргументов функции сравнения. Все это делает приведенный ниже код:
///создаем 1 поток, на основе файла 1.txt StreamReader stream1 = new StreamReader(@"C:\1.txt"); ///создаем 1 поток, на основе файла 1.txt StreamReader stream2 = new StreamReader(@"C:\2.txt"); ///сравниваем потоки FileAssert.AreEqual(stream1.BaseStream,stream2.BaseStream);
Кстати, для работы с классом StreamReader надо подключить пространство имен IO:
using System.IO;
Небольшие пояснения к коду: класс Stream сам по себе является абстрактным, следовательно, объект класса Stream создать нельзя. Мы создаем объект класса StreamReader, который является наследником класса Stream. А благодаря свойству BaseStream мы как раз обращаемся, к базовому потоку.
Теперь рассмотрим вариант с классом FileInfo. Тут, я думаю, можно ограничься кодом. И так все понятно.
///создаем объект для первого файла FileInfo fi1 = new FileInfo(@"C:\1.txt"); ///создаем объект для второго файла FileInfo fi2 = new FileInfo(@"C:\2.txt"); ///сравниваем 2 объекта FileAssert.AreEqual(fi1,fi2);
В NUnit существует класс DirectoryAssert, который служит для создания тестов, связанных с папками, файловой системой. Работа с данным классом очень похожа на предыдущие примеры с FileAssert, поэтому на них я останавливаться не буду.
Атрибуты
NUnit предлагает нам с тобой множество дополнительных фич для управления поведением тестируемых функций. Например, мы можем передавать различные параметры, указав конкретные значения, либо диапазон значений, либо вообще “сказать” NUnit, чтобы он генерировал случайное значение. Все это, как и многое другое, реализуется с помощью атрибутов. Указав тот или иной атрибут перед тестируемой функцией, мы можем заставить работать тест по нашим правилам.
Итак, раз уж мы заговорили о параметрах, то с них и начнем. Допустим, наша тестовая функция имеет следующую сигнатуру:
public static void MyTestFunc(int x,string s)
и мы хотим передать ей некоторый тестовые значения. Например, нам интересно, как поведет себя функция при x Є{1,2,5} и s равном “hello” и “buy”. Для этого нам надо тестовой функции указать такие атрибуты:
public static void MyTestFunc( [Values(1, 2, 5)]int x, [Values("hello", "buy")]string s).
В данном случае функция будет вызвана 6 раз :
public static void MyTestFunc(1,”hello”) public static void MyTestFunc(1,”buy”) public static void MyTestFunc(2,”hello”) public static void MyTestFunc(2,”buy”) public static void MyTestFunc(5,”hello”) public static void MyTestFunc(5,”buy”).
С этим все понятно, но, допустим , в качестве аргумента x нам надо передать промежуток значений, например, от 1 до 100. Не будем же мы перечислять все значения в атрибуте Values. Для этого придумали атрибут Range, который предлагает несколько вариантов использования, но мы пока остановимся только на двух. Указанная выше задача с параметрами от 1 до 100 решается следующим элегантным путем:
public static void MyTestFunc( [Range(1,100)]int x, [Values("hello", "buy")]string s).
Здесь мы указали значения from и to равными 1 и 100 соответственно. В результате будет сделано 200 тестовых вызовов нашей функции с разными параметрами.
Хорошо, а если нам не надо, чтобы функция проверяла все варианты от 1 до 100,а допустим только нечетные числа, т.е. 1,3,5 и т.д. Для этого есть второй вариант атрибута Range,который 3 параметром принимает шаг движения по нужному промежутку значений. Вот так:
public static void MyTestFunc( [Range(1,100,2)]int x, [Values("hello", "buy")]string s).
Допустим, функция в итоге имеет такой вид:
[Test] public static void MyTestFunc( [Range(1,100,2)]int x, [Values("hello", "buy")]string s) { Assert.IsTrue(x<50); }
Мы проверяем, является ли x меньше 50 или нет. Вот как будет выглядеть окно NUnit,после запуска данного теста:

Как мы видим, первые 50 тестов помечены зеленым (пройдены успешно), а когда значение x становится больше 50, тесты уже считаются проваленными. Как результат, в целом тестирование тоже провалилось.
Как я говорил, есть еще варианты использования атрибута Range. В них вместо значений типа int (а у нас все параметры именно целые: 1,100 и 2) можно также использовать long,double и float. Но в этом случае,3 параметр (шаг) является уже обязательным. Такой вариант даже не скомпилируется:
[Test] public static void MyTestFunc( [Range(1.02,100.05)]float x, [Values("hello", "buy")]string s).
Напоследок рассмотрим еще один интересный вариант для работы параметрами- Random. Как ясно из названия, он позволяет передавать функции случайные значения. Существует 3 варианта использования Random,мы кратко их все и рассмотрим.
Первый вариант требует только указать ему, сколько случайных значений тебе надо, все остальное он сделает сам. Пример:
public static void MyTestFunc( [Random(100)]int x, [Values("hello", "buy")]string s).
Здесь мы указали, что нам надо только 100 случайных значений. Запусти пример, а потом открой его в NUnit и ты увидишь, какие случайные значений сгенерировал NUnit. Мое окно выглядит следующим образом:

Как видишь, он сгенерировал значения типа double, хотя у нас аргумент x имеет тип int. Для того, чтобы NUnit нам выдал случайные целочисленные значения, используем второй вариант атрибута Random,который принимает три аргумента: начальное значение, конечное значение и количество. Все три аргумента имеют тип int.
[Test] public static void MyTestFunc( [Random(1,1000,100)]int x, [Values("hello", "buy")]string s).
Какие у меня сгенерировались значения можно увидеть на картинке:

3 вариант Random’а похож на второй, только он в качестве начальных и конечных значение принимает тип double.
ИТОГО
Я бы еще долго мог заниматься переводом мануала по NUnit, но, думаю, основные моменты ты уяснил
.Тем более, мануал этот общедоступен и даже не обладая большими познаниями в английском ты разберешься.
Компьютеры уже повсюду, миллионы людей пользуется различным ПО (в том числе, возможно, и твоим
), и программисты должны понимать ответственность за свою работу. Поэтому автоматические тесты сейчас становятся нормой даже для небольших проектов, потому что цена ошибки существенно возросла. Надеюсь, мои 2 небольших рассказа про возможности Framework’а NUnit помогут тебе внедрить или более широко использовать автоматические тесты в своей работы.
Удачи, если что пиши мне на zheka@xakep.ru.
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
- 6338 просмотров



Комментарии
1 комментария(ев)Дата: ЧТ, 30/06/2011 - 04:24
Интересная статья. Спасибо. По теме NUnit - я недавно в свободное время начал переводить его мануал. Если кому будет интересно почитать/поучаствовать в переводе - вэлком: ]]>https://sites.google.com/site/rumonodevelop/nunit/nunithome]]>