Страницы

пятница, 17 февраля 2012 г.

Эффективное тестирование. Продвинутые инструменты.

  В прошлый раз я рассматривал базовые возможности одной из самых популярных библиотек для модульного тестирования – NUnit. Для этого блога немного модифицировал тесты и тестируемый код, дабы показать возможности других инструментов. Давайте напомню, у нас есть тест класса Version:
[Test]
public void Is_not_good_testing_because_some_asserts_are_missed()
{
    var version = Version.ParseWithError("1.2.3.4");

    Assert.AreEqual(1, version.Major);
    Assert.AreEqual(2, version.Minor);
    Assert.AreEqual(3, version.Build);
    Assert.AreEqual(4, version.Revision);
}

  Пусть в коде будут две ошибки (перепутанные индексы в массиве):
public static Version ParseWithError(string version)
{
    var versionNumbers = version.Split('.');

    var major = Convert.ToInt32(versionNumbers[0]);
    var minor = Convert.ToInt32(versionNumbers[1]);
    var build = Convert.ToInt32(versionNumbers[3]); //error
    var revision = Convert.ToInt32(versionNumbers[2]); //error

    return new Version(major, minor, build, revision);
}

  Запустим тест, и посмотрим,  что нам скажет NUnit:


Expected: 3
But was: 4

  В коде две очевидных ошибки, но NUnit сказал нам лишь об одной из них. Почему? Дело в том, что Assert в случае ошибки бросает исключение, и далее тест не выполняется. До Assert’а свойства Revision тест просто не дошёл. С такой ситуацией можно жить, но плохо. Мы не знаем проблему полностью. Вторую ошибку обнаружим только после исправления первой. Классики тестирования рекомендуют писать только один Assert на тест. Такой способ можно применить и к этой ситуации.  Мы увидим все ошибки, но один простой тест будет размазан на четыре. А что если нам надо проверить 16 свойств? Писать 16 тестов совсем не хочется. Так есть ли способ совместить полноту тестирования с лаконичностью? Конечно есть! Улыбка

SharpTestsEx


  Лидер проекта NHibernate написал весьма симпатичную библиотеку SharpTestsEx (если писать SharpTestSex  запомнить будет легче!). Она позволяет выполнять несколько проверок за один раз. SharpTestsEx активно использует методы расширения и лямбда выражения. SharpTest для выглядит так:
[Test]
public void SharpTestsEx_helps_to_improve_multi_assertion()
{
    var version = Version.ParseWithError("1.2.3.4");

    version.Satisfy(x =>
                    x.Major == 1 &&
                    x.Minor == 2 &&
                    x.Build == 3 &&
                    x.Revision == 4);
}

  И вот сообщение, которые мы получим в итоге:


SharpTestsEx.AssertException : Version = '1.2.4.3' Should Satisfy (x => x.Build == 3)
Compared values was: 4 == 3
And
Version = '1.2.4.3' Should Satisfy (x => x.Revision == 4)
Compared values was: 3 == 4

  Теперь мы видим в сообщения обе ошибки,  указанием свойств и несовпадающих значений в рамках одного теста. Кроме тестов с помощью лямбда-выражений SharpTestsEx содержит еще и неплохое FluentApi для тестирования простых значений (особенна полезно возможность применить к какому-либо значению – строке, числу, дате и др.  несколько Assert’ов). Информацию о них можно найти в документации. Крайне рекомендую иметь этот инструмент в вашем арсенале.

 


FluentAssertions


  В отличии от SharpTestsEx FluentAssertions не позволяет провести несколько тестов за один проход. Зато обладает мощными возможностями для сравнения между собой различных объектов. С его помощью предыдущую задачу можно решить с помощью сравнения с эталоном. Например так:
[Test]
public void FluentAssertions_helps_to_improve_multi_assertion()
{
    var version = Version.ParseWithError("1.2.3.4");

    version.ShouldHave().AllProperties().EqualTo(new Version(1,2,3,4));
}

   В качестве эталонного объекта можно использовать даже анонимный тип:
[Test]
public void FluentAssertions_with_an()
{
    var version = Version.ParseWithError("1.2.3.4");

    version.ShouldHave().AllProperties().EqualTo(
        new {Major = 1, Minor = 2, Build = 3, Revision = 4 });
}

  Увы, в бочке мёда затесалось немного дёгтя. Несмотря на простой и удобный синтаксис в сообщение попадает только одна ошибка:


Expected property Revision to be 4, but found 3.

  Хочется надеется, в будущих версиях будут проверятся все свойства, и агрегированное сообщение об ошибках. Тем не менее, во многих случаях можно пожертвовать полным выводом ошибок ради лаконичности и красоты. Код тестов нуждается в поддержке, как и основной код системы. А FluentAssertions избавляет от необходимости добавлять каждое новое свойство в код теста. Скажем, если у нас есть еще один класс ShortVersion:
public class ShortVersion
{
    public int Major { get; set; }

    public int Minor { get; set; }
}

  И надо протестировать метод Version.ToShortVersion(). С FluentAssertions можно написать тест так:
[Test]
public void FluentAssertions_test_shared_fields()
{
    var version = Version.Parse("1.2.3.4");

    var shortVersion = version.ToShortVersion();

    shortVersion.ShouldHave().SharedProperties().EqualTo(version);
}

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

  Как и SharpTestsEx, FluentAssertions облаладет отличным FluentAPI для написания выразительного кода для многих  видов Assert.ов. FluentAssertions пришёл в мой код недавно, но с каждым днём применяю всё чаще и чаще. Попробуйте, возможно он еще не раз вас выручит!

  Код примеров лежит здесь.

Комментариев нет:

Отправить комментарий