Feel Good.

02 марта 2010

Hello, NUnit

Тестирование собственного кода - признак хорошего тона, а наличие Unit тестов - еще и признак профессионального подхода.

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

  1. Экономия Вашего времени.

    Тестирование осуществляется в автоматическом режиме, это особенно важно, при наличии огромного количества unit тестов. Если Вы заказчик, и заказали аутсорсинговой компании разработать некий модуль, имея ТЗ. Тогда в этом случае, принимать работу можно в автоматическом режиме, а именно: работа будет принята, если все, заготовленные Вами, unit тесты будут пройдены, при этом, имея некую гарантию правильности кода.

  2. Гарантия правильности кода

    Оценить степень правильности кода можно, зная уровень покрытия кода тестами. Например, если unit тесты покрывают код на 80-100%, то успешное прохождение всех тестов есть гарантия того, что код работает правильно. Обеспечить полное покрытие в сложных модулях довольно сложно, но можно.

  3. Простота тестирования

    Разумеется, чем сложнее устроен модуль, тем сложнее его тестировать, поэтому обычно такие модули разбивают на более простые модули, которые намного проще тестировать. Тогда сложное unit тестирование исходного модуля сводится к проведению серии простых unit тестов. Следует отметить, что современные версии фреймворков Unit тестирования имеют широкий набор удобных средств для тестирования.

  4. Простой рефакторинг

    Представьте, Вам достался в наследство модуль, который Вам предстоит переписать или внести в него изменения(рефакторинг). При наличии unit тестов, Вы сможете вносить изменения, при этом оперативно проверять, по-прежнему ли все тесты проходят успешно, исключая такой вариант, как "сломать работоспособное", тем самым существенно сокращая время на отладке.

  5. Дополнение к документации

    Часто, unit тест можно считать актуальным дополнением к документации модуля, по которому достаточно просто понять, чего следует ожидать от тестируемого модуля.

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


interface ICalculator

{

    int Sum(int a, int b);

}


Добавим в проект ссылку на nunit.framework.dll. После чего напишем простейший тест, для этого создадим класс CalculatorTest помеченный атрибутом [TestFixture] содержащий экземпляр тестируемого объекта ICalculator три метода: SetUp(), SumTest(), TearDown(). Обратите внимание на атрибуты у каждого из методов. Так атрибутами задаются метод инициализации тестирования (SetUp), метод теста(SumTest), метод финализации тестирования TearDown:


[TestFixture]

class CalculatorTest

{

    // Тестировать будем интерфейс ICalculator

    private ICalculator calc = null;

 

    [SetUp]

    public void SetUp()

    {

        // Выполняем здесь инициализацию

        // Конкретная реализация ICalculator

        calc = new Calculator();

    }

 

    [Test]

    public void SumTest()

    {

        // Проводим здесь тестирование

        // Ожидаем получить на выходе 5

        Assert.AreEqual(5, calc.Sum(2, 3));

    }

 

    [TearDown]

    public void TearDown()

    {

        // Выполняем здесь финализацию

        // IDisposable

    }

}


Тесты готовы, отсутствует сама реализация интерфейса ICalculator, поэтому добавим новый класс Calculator, реализующий интерфейс ICalculator:


class Calculator : ICalculator

{

    public int Sum(int a, int b)

    {

        return a + b;

    }

}

Откомпилируем проект, в итоге Вы должны получить сборку(exe или dll файл). Теперь, для того чтобы прогнать все тесты в полученной сборке необходимо воспользоваться утилитой NUnit Gui Runner (или при помощи консольной утилиты), входящей в дистрибутив. Данной утилитой выбираем тестируемую сборку и запускаем процесс автоматического тестирования. Утилита работает со сборкой на уровне reflection. По окончании тестирования, утилита покажет нам какие тесты и с каким результатом были выполнены. В нашем случае, единственный тест был успешно пройден.

Общая идея тестирования заключается в сверке полученного значения с ожидаемым. Однако, ожидаемое значение может задаваться не только в виде точного значения, но и в виде ограничений (например, уникальность элементов массива). В современных версиях NUnit появились параметризованные тесты, с возможностью задания входных параметров теста различным способом: перечисление, из диапазона с неким шагом, из диапазона случайным образом. Более того, к входным параметрам может применяться комбинаторная схема (комбинаторные тесты). Разумеется, исключения это часть логики, поэтому в NUnit есть возможность протестировать функцию на возникновение исключения. Следует отметить, что NUnit является mock фреймворком, позволяющий тестировать объекты-имитаторы(которые необходимы для имитирования поведения), без непосредственной их реализации.

12 комментариев:

  1. хотя бы деление сдлел и вариант деления на 0 рассмотрел.
    Слабовато

    ОтветитьУдалить
  2. @Oleksandr Krysan
    Зачем? Моя цель была показать сам подход, не более.
    А за деталями, это уже к документации. Если уж показывать возможности, то тогда все, а это ни к чему, на это есть документация.

    ОтветитьУдалить
  3. А можно ли в nUnit-е указать сделать такое:
    Допустим имеем n-классов с набором тестов. И я хочу сгруппировать их как-то. И запускать только выбранную группу, которая будет в свою очередь запускать свои тесты.

    ОтветитьУдалить
  4. @Sergey Litvinov
    Да, можно, группа задается атрибутом. [Category("categoryName")]
    http://www.nunit.org/index.php?p=category&r=2.5.3

    ОтветитьУдалить
  5. Интересно еще почитать про это: "Следует отметить, что NUnit является mock фреймворком, позволяющий тестировать объекты-имитаторы(которые необходимы для имитирования поведения), без непосредственной их реализации.".

    ОтветитьУдалить
  6. @magicmaxx
    Спасибо за проявленный интерес! Я как раз работаю над этим, хочу придумать простой, но главное ёмкий пример. Ждите следующей статьи)

    ОтветитьУдалить
  7. Супер, спасибо. Категории дейсвтительно очень удобно, но не нашёл возможность указать последовательность выполнения тестов.
    Нашел в хелпе аттрибут Suite - http://www.nunit.org/index.php?p=suite&r=2.5.3
    Но в сноске на странице сказано, что в nUnit GUI он не работает. R# так же не воспринимает его.
    Сейчас он отображает тесты в алфавитном порядке, а мне нужно запускать их в определённой последовательности

    ОтветитьУдалить
  8. @Sergey Litvinov
    Надо писать свой AddIn.
    http://www.nunit.org/index.php?p=nunitAddins&r=2.5.3
    Например, случайное выполнение тестов:
    http://www.codewrecks.com/blog/index.php/2008/12/06/randomizer-nunit-addin/

    ОтветитьУдалить
  9. Спасибо, мысль понял. Мне вполне подходит.
    Осталось только дождаться ответа от комманды Resharper-а, поддерживают ли они nUnitAddin. Так как по дефолту их TestRunner не подхватил аддин. А nUnit GUI подхватил.

    ОтветитьУдалить
  10. "Например, если unit тесты покрывают код на 80-100%, то успешное прохождение всех тестов есть гарантия того, что код работает правильно."

    К сожалению, 100% покрытие кода тестами еще не гарантирует правильность работы кода :( Скорее, это минимум, которому должен соответствовать удовлетворительный набор тестов

    ОтветитьУдалить
  11. А как добавить ссылку на nunit.framework.dll ?

    ОтветитьУдалить
  12. @Мария
    В VisualStudio, на вкладке solution explorer кликнуть правой кнопкой мыши на проекте, в раскрывшемся меню выбрать пункт Add reference, далее выбрать закладку с browse и найти библиотеку на диске, либо закладка .net и найти библиотеку в списке.

    http://msdn.microsoft.com/en-us/library/wkze6zky%28v=vs.80%29.aspx

    ОтветитьУдалить