Я работаю над некоторыми тестовыми сценариями в данный момент, и я регулярно нахожу, что заканчиваю с несколькими, утверждает в каждом случае. Например (упрощенный и комментарии, разделенные для краткости):
[Test]
public void TestNamePropertyCorrectlySetOnInstantiation()
{
MyClass myInstance = new MyClass("Test name");
Assert.AreEqual("Test Name", myInstance.Name);
}
Это выглядит приемлемым в принципе, но точка теста должна проверить, что, когда класс инстанцируют с именем, свойство Name установлено правильно, но это перестало работать, если что-нибудь идет не так, как надо на инстанцировании, прежде чем это даже доберется до утверждения.
Я осуществил рефакторинг его как это:
[Test]
public void TestNamePropertyCorrectlySetOnInstantiation()
{
MyClass myInstance;
string namePropertyValue;
Assert.DoesNotThrow(() => myInstance = new MyClass("Test name"));
Assert.DoesNotThrow(() => namePropertyValue = myInstance.Name);
Assert.AreEqual("Test Name", namePropertyValue);
}
но конечно, теперь я на самом деле тестирую три вещи здесь; В этом тесте я не интересуюсь тестированием, инстанцировали ли экземпляр MyClass успешно, или что свойство Name было считано успешно, они тестируются в другом случае. Но как я могу протестировать последнее утверждение, не утверждая другие первые два, учитывая, что не возможно даже сделать тест, если первые два перестали работать?
Просто есть другие тесты, которые проверяют, что исключение будет выброшено, если вы инициализируете его недопустимым образом. Первая форма вполне подходит, IMO.
Лично я бы не стал зацикливаться на догме "одно утверждение на тест". Старайтесь тестировать один логический путь через код, с той степенью детализации, которая имеет практический смысл.
Grzenio прав в этом. Первый, самый простой пример теста завершится неудачей, если будет выброшено исключение - нет необходимости явно проверять это.
Вы должны, однако, проверить, что исключение будет выброшено, когда ему переданы недопустимые данные.
[Test]
public void TestInvalidNameThrowsException {
MyClass myInstance;
string namePropertyValue;
Assert.Throws(() => myInstance = new MyClass(null));
Assert.Throws(() => myInstance = new MyClass("INVALID_NAME_125356232356"));
}
(например, я не знаю C# ни капли, но вы должны понять идею)
.Я действительно не понимаю, что вы имеете в виду под избыточным тестированием ИМО, избыточное тестирование - это что-то вроде попытки протестировать частные методы.
Я считаю свои тесты документированием кода. Итак, если у меня есть более одного утверждения в операторе, то есть большая вероятность, что я могу реорганизовать тестируемый метод на несколько более мелких методов или иногда я разбиваю свой тестовый метод на несколько разных методов тестирования. Следование правилу one-assert-per-test позволяет вам иметь разумные имена тестовых методов, которые, в свою очередь, формируют документацию вашего кода. Я придерживаюсь соглашения об именах для методов тестирования: methodName_scenario_expectation (из книги Роя Ошерова Искусство модульного тестирования ). Итак, мы также думаем о документации кода. Вы думаете, что утверждение assert (помимо проверки ожидания) поможет вам / другому разработчику лучше понять код, а затем напишите это assert. Но, повторюсь еще раз, всегда проверяйте правильность названий методов тестирования.
В вашем конкретном примере вам не нужно утверждать, что что-то не бросается, если это часть выполнения теста. Этот аспект уже проверен в вашем первом тесте, который гораздо более читабелен. Если конструктор или геттер свойств выбросит исключение, NUnit провалит тест с соответствующим сообщением об ошибке. На самом деле я не уверен, в чем идея Assert.DoesNotThrow(), потому что если вы опустите его, то получите практически тот же результат - но определенно вы не должны использовать его как часть обычного выполнения теста. Весь смысл наличия исключений как части языка в том, что вам не нужно проверять ошибки после каждой отдельной строки кода.