Как я могу избежать нескольких утверждений в этом модульном тесте? - PullRequest
11 голосов
/ 10 января 2012

Это моя первая попытка провести модульные тесты, поэтому, пожалуйста, наберитесь терпения.
Я все еще пытаюсь выполнить модульное тестирование библиотеки, которая преобразует списки POCO в 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();

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

Итак ... что мне теперь делать?
Этот уровень дублирования все еще приемлем в этой особой ситуации, или есть лучший способ как это проверить?

Ответы [ 5 ]

9 голосов
/ 10 января 2012

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

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

7 голосов
/ 10 января 2012

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

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

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

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 голосов
/ 10 января 2012

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

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

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

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

3 голосов
/ 10 января 2012

Эти 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");

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

1 голос
/ 19 декабря 2012

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

...