Как я могу избежать нескольких утверждений в этом unit test?

Это моя первая попытка провести единичные тесты, поэтому, пожалуйста, будьте терпеливы со мной. Я все еще пытаюсь использовать unit test библиотеку, которая преобразует списки POCOs в ADO.Recordsets.

Сейчас я пытаюсь написать тест, который создает List<Poco>, преобразует его в набор записей (используя метод, который я хочу протестировать), а затем проверяет, содержат ли они ту же информацию (например, если Poco.Foo == RS.Foo и т.д.).

Это POCO:

public class TestPoco
{
    public string StringValue { get; set; }
    public int Int32Value { get; set; }
    public bool BoolValue { get; set; }
}

... и это тест до сих пор (я использую xUnit.net):

[Fact]
public void TheTest()
{
    var input = new List<TestPoco>();
    input.Add(new TestPoco { BoolValue = true, Int32Value = 1, StringValue = "foo" });

    var actual = input.ToRecordset();

    Assert.Equal(actual.BoolValue, true);
    Assert.Equal(actual.Int32Value, 1);
    Assert.Equal(actual.StringValue, "foo");
}

То, что мне не нравится в этом, - это три утверждения в конце, по одному на собственность POCO.
Я читал много раз, что несколько утверждений в одном тесте являются злыми (и я понимаю причины, и я согласен).

Проблема в том, как я могу избавиться от них?

У меня есть Рой Ошерове отличная книга "Искусство тестирования единиц" прямо перед собой, и у него есть пример, который точно охватывает это (для тех, у кого есть книга: глава 7.2.6, стр. 202/203):

В своем примере тестируемый метод возвращает объект AnalyzedOutput с несколькими свойствами, и он хочет утвердить все свойства, чтобы проверить, содержит ли каждый из них ожидаемое значение.

Решение в этом случае:
Создайте еще один экземпляр AnalyzedOutput, заполните его ожидаемыми значениями и утвердите, если он равен тому, который возвращается тестируемым методом (и переопределяет Equals(), чтобы это сделать).

Но я думаю, что не могу этого сделать в моем случае, потому что метод, который я хочу проверить, возвращает ADODB.Recordset.

И чтобы создать еще один Recordset с ожидаемыми значениями, мне сначала нужно было бы полностью его создать:

// this probably doesn't actually compile, the actual conversion method 
// doesn't exist yet and this is just to show the idea

var expected = new ADODB.RecordsetClass();
expected.Fields.Append("BoolValue", ADODB.DataTypeEnum.adBoolean);
expected.Fields.Append("Int32Value", ADODB.DataTypeEnum.adInteger);
expected.Fields.Append("StringValue", ADODB.DataTypeEnum.adVarWChar);

expected.AddNew();
expected.BoolValue = true;
expected.Int32Value = 1;
expected.StringValue = "foo";
expected.Update();

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

Итак... что я могу сделать сейчас?
Является ли этот уровень дублирования по-прежнему приемлемым в этой особой ситуации или есть лучший способ проверить это?

Ответ 1

Я бы сказал, что в духе вещи это прекрасно. Причина, по которой несколько утверждений "зла", если я правильно помню, заключается в том, что это означает, что вы тестируете несколько вещей в одном тесте. В этом случае вы действительно делаете это, проверяя каждое поле, предположительно, чтобы убедиться, что это работает для нескольких разных типов. Так как все испытания на равенство объектов все равно, я думаю, что вы в явном виде.

Если вы действительно хотели воинствовать об этом, напишите один тест на каждое свойство (j/k!)

Ответ 2

Несколько утверждений за unit test отлично подходят для моей книги, если несколько утверждений утверждают одно и то же условие теста. В вашем случае они проверяют, что преобразование было успешным, поэтому проверка прохождения является условной для всех утверждений, являющихся истинными. В результате это прекрасно!

Я бы классифицировал "одно утверждение за тест" в качестве ориентира, а не жесткое правило. Когда вы игнорируете это, подумайте, почему вы игнорируете его.

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

public class ClassWithProperities
{
    public string Foo { get; set; }
    public int Bar { get; set; }
}

public static class Converter
{
    public static ClassWithProperities Convert(string foo, int bar)
    {
        return new ClassWithProperities {Foo=foo, Bar=bar};
    }
}
[TestClass]
public class PropertyTestsWhenFooIsTestAndBarIsOne
{
    private static ClassWithProperities classWithProperties;

    [ClassInitialize]
    public static void ClassInit(TestContext testContext)
    {
        //Arrange
        string foo = "test";
        int bar = 1;
        //Act
        classWithProperties = Converter.Convert(foo, bar);
        //Assert
    }

    [TestMethod]
    public void AssertFooIsTest()
    {
        Assert.AreEqual("test", classWithProperties.Foo);
    }

    [TestMethod]
    public void AssertBarIsOne()
    {
        Assert.AreEqual(1, classWithProperties.Bar);
    }
}

[TestClass]
public class PropertyTestsWhenFooIsXyzAndBarIsTwoThousand
{
    private static ClassWithProperities classWithProperties;

    [ClassInitialize]
    public static void ClassInit(TestContext testContext)
    {
        //Arrange
        string foo = "Xyz";
        int bar = 2000;
        //Act
        classWithProperties = Converter.Convert(foo, bar);
        //Assert
    }

    [TestMethod]
    public void AssertFooIsXyz()
    {
        Assert.AreEqual("Xyz", classWithProperties.Foo);
    }

    [TestMethod]
    public void AssertBarIsTwoThousand()
    {
        Assert.AreEqual(2000, classWithProperties.Bar);
    }
}

Ответ 3

Я согласен со всеми остальными комментариями, что это хорошо, если вы логически проверяете одно.

Однако существует разница между множеством утверждений в одном unit test, чем с отдельным unit test для каждого свойства. Я называю это "Блокировка утверждений" (вероятно, лучшее название там). Если у вас много утверждений в одном тесте, вы узнаете только об отказе в первом свойстве, которое не подтвердило утверждение. Если вы скажете, что 10 свойств и 5 из них возвратили неверные результаты, вам придется пройти через исправление первого, повторно запустить тест и заметить, что другой не удалось, затем исправить это и т.д.

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

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

Ответ 4

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

public class When_converting_a_TestPoco_to_Recordset
{
    protected static List<TestPoco> inputs;
    protected static Recordset actual;

    Establish context = () => inputs = new List<TestPoco> { new TestPoco { /* set values */ } };

    Because of = () => actual = input.ToRecordset ();

    It should_have_copied_the_bool_value = () => actual.BoolValue.ShouldBeTrue ();
    It should_have_copied_the_int_value = () => actual.Int32Value.ShouldBe (1);
    It should_have_copied_the_String_value = () => actual.StringValue.ShouldBe ("foo");
}

Обычно я использую mspec в качестве эталона, чтобы узнать, имеют ли смысл мои тесты. Ваши тесты хорошо читаются с помощью mspec, и это дает мне некоторые полуавтоматические теплые пушистики, которые я тестирую правильно.

В этом отношении вы сделали лучшую работу с несколькими утверждениями. Я ненавижу просмотр тестов, которые выглядят так:

Assert.That (actual.BoolValue == true && actual.Int32Value == 1 && actual.StringValue == "foo");

Потому что, когда это не удается, сообщение об ошибке "Ожидание True, получившее False" совершенно бесполезно. Множество утверждений и максимально возможное использование инфраструктуры тестирования модулей помогут вам многое.

Ответ 5

Это должно быть что-то, чтобы проверить http://rauchy.net/oapt/ Инструмент, который генерирует новый тестовый пример для каждого утверждения.