Как я могу избежать нескольких утверждений в этом 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/
Инструмент, который генерирует новый тестовый пример для каждого утверждения.