Можно ли иметь несколько утверждений в одном модульном тесте?

397

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

На домашней странице проекта написано следующее:

Правильные юнит-тесты должны проваливаться ровно по одной причине, поэтому вы должны использовать одно утверждение на юнит-тест.

И, кроме того, Рой написал в комментариях:

Моя рекомендация обычно заключается в том, что вы тестируете одну логическую КОНЦЕПЦИЮ на тест. Вы можете иметь несколько утверждений на один и тот же объект . они обычно будут той же самой концепцией, которая тестируется.

Я думаю, что в некоторых случаях требуется несколько утверждений (например, Guard Assertion ), но в целом я стараюсь этого избегать. Каково твое мнение? Пожалуйста, приведите пример из реальной жизни, где действительно нужно несколько утверждений .

Рестута
источник
2
Как вы делаете насмешки, не имея несколько утверждений? Каждое ожидание на макете само по себе является утверждением, включая любой порядок вызовов, которые вы навязываете.
Кристофер Кройциг
15
Я видел философию «один утверждают за метод», которой злоупотребляли в прошлом. Старый сотрудник использовал механизм фанки наследования, чтобы сделать это возможным. Это привело к большому количеству подклассов (по одному на ветку) и множеству тестов, которые выполняли один и тот же процесс настройки / разборки только для проверки разных результатов. Это было медленно, трудно читать и серьезная проблема обслуживания. Я никогда не убеждал его вернуться к более классическому подходу. Об этом подробно рассказывается в книге Жерара Месароса .
Трэвис Паркс
3
Я думаю, что в качестве общего практического правила вы должны попытаться свести к минимуму количество подтверждений за тест. Однако, если тест достаточно сузит проблему до определенного места в коде, это полезный тест.
ConditionRacer
2
Я видел случаи, когда вместо RowTest(MbUnit) / TestCase(NUnit) использовалось несколько утверждений для тестирования различных вариантов поведения в крайнем случае. Используйте соответствующие инструменты для работы! (К сожалению, MSTest, похоже, пока не имеет возможности для тестирования строк.)
GalacticCowboy
@GalacticCowboy Вы можете получить аналогичную функциональность RowTestи TestCaseиспользовать источники тестовых данных . Я использую простой CSV-файл с большим успехом.
Jullealgon

Ответы:

236

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

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

Как вы решаете несколько утверждений?

Jaco Pretorius
источник
9
Как этот ответ - то есть его хорошо, но это, в общем, не хорошо (-:
Murph
5
Эхх? Почему ты бы так поступил? выполнение метода точно такое же?
jgauffin
198
Одно утверждение на модульный тест - отличный способ проверить способность читателя прокручивать вверх и вниз.
Том
3
Есть ли какие-либо причины этого? Как и в этом текущем ответе просто говорится, что это должно быть, но не почему.
Стивен Джеурис
37
Сильно не согласен. Ответ не содержит никаких преимуществ наличия единственного утверждения, и очевидным недостатком является то, что вам нужно копировать-вставлять тесты только потому, что ответ в Интернете говорит об этом. Если вам нужно протестировать несколько полей результата или нескольких результатов одной операции, вам обязательно следует утверждать все из них в независимых утверждениях, потому что это дает гораздо более полезную информацию, чем тестирование их в большом утверждении большого двоичного объекта. В хорошем тесте вы тестируете одну операцию, а не один результат операции.
Питер
296

Тесты должны проваливаться только по одной причине, но это не всегда означает, что должно быть только одно Assertутверждение. ИМХО, важнее придерживаться модели « Аранжируй, действуй, утверждай ».

Ключ заключается в том, что у вас есть только одно действие, а затем вы проверяете результаты этого действия, используя утверждения. Но это «Устройся, Действуй, Утверждай, Конец теста ». Если у вас возникнет желание продолжить тестирование, выполнив другое действие, а затем еще несколько утверждений, сделайте это отдельным тестом.

Я счастлив видеть несколько утверждений assert, которые образуют части тестирования одного и того же действия. например

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

или же

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

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

например, первый может быть

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

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

NB : код, который я пишу здесь, - это C # с NUnit, но принципы будут соответствовать другим языкам и фреймворкам. Синтаксис также может быть очень похожим.

Энтони
источник
32
Ключ заключается в том, что у вас есть только одно действие, а затем вы проверяете результаты этого действия, используя утверждения.
Амитабха
1
Я думаю, что это также интересно, иметь более одного Assert, если Arrange дорого.
Рекшино
1
@Rekshino, если упорядочение требует много времени, мы можем поделиться кодом упорядочения, например, поместив код упорядочения в процедуру инициализации теста.
Шон Люттин,
2
Поэтому, если я сравню с ответом Жако, «только одно утверждение» станет «только одной группой утверждений», которые имеют для меня больше смысла.
Вальфрат
2
Это хороший ответ, но я не согласен с тем, что единственное утверждение не лучше. Пример плохого утверждения не лучше, но это не значит, что одно утверждение не будет лучше, если все сделано правильно. Во многих библиотеках допускаются настраиваемые утверждения / сопоставления, так что что-то может быть создано, если его еще нет. Например , Assert.IsBetween(10, 100, value)что печатает Expected 8 to be between 10 and 100 это лучше , чем две отдельные утверждает , на мой взгляд. Вы, конечно, можете утверждать, что в этом нет необходимости, но обычно стоит подумать о том, легко ли свести его к одному утверждению перед тем, как создавать их целиком.
Thor84no
85

Я никогда не думал, что более чем одно утверждение было плохой вещью.

Я делаю это все время:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Здесь я использую несколько утверждений, чтобы убедиться, что сложные условия можно превратить в ожидаемый предикат.

Я тестирую только одну единицу ( ToPredicateметод), но покрываю все, что могу придумать в тесте.

Мэтт Эллен
источник
46
Многократные утверждения плохи из-за обнаружения ошибок. Если вы провалили свой первый Assert.IsTrue, другие утверждения не будут выполнены, и вы не получите от них никакой информации. С другой стороны, если у вас есть 5 тестов вместо 1 с 5 утверждениями, вы можете получить что-то полезное
Sly
6
Считаете ли вы все еще плохим, если все утверждения проверяют одинаковую функциональность? Как и выше, в примере проверяются условные выражения, и если что-то из этого не получается, вы должны это исправить. Имеет ли для вас значение, что вы можете пропустить последние 2 утверждения в случае неудачи предыдущего?
съеживаться
106
Я решаю свои проблемы по одному. Так что тот факт, что тест может провалиться несколько раз, меня не беспокоит. Если бы я разделил их, у меня были бы те же самые ошибки, но все сразу. Мне легче все исправить, шаг за шагом. Я признаю, что в этом случае два последних утверждения, вероятно, могут быть преобразованы в их собственный тест.
Мэтт Эллен
19
Ваш случай очень показателен, поэтому в NUnit есть дополнительный атрибут TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
Фреймворк для тестирования Google C ++ имеет ASSERT () и EXPECT (). ASSERT () останавливается при сбое, а EXPECT () продолжается. Это очень удобно, когда вы хотите проверить более чем одну вещь в тесте.
Раткок
21

Когда я использую модульное тестирование для проверки поведения высокого уровня, я просто помещаю несколько утверждений в один тест. Вот тест, который я на самом деле использую для какого-то кода экстренного уведомления. Код, который выполняется перед тестом, переводит систему в состояние, при котором при запуске основного процессора отправляется сигнал тревоги.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Он представляет условия, которые должны существовать на каждом этапе процесса, чтобы я был уверен, что код ведет себя так, как я ожидаю. Если одно утверждение не удастся, мне все равно, что остальные не будут запущены; поскольку состояние системы больше не является действительным, эти последующие утверждения не скажут мне ничего ценного. * В случае assertAllUnitsAlerting()неудачи я не буду знать, что делать assertAllNotificationSent()с успехом ИЛИ сбоем, пока не определю, что было причиной предыдущей ошибки. и исправил это.

(* - Хорошо, они, возможно, могут быть полезны при устранении проблемы. Но самая важная информация о том, что тест не пройден, уже получена.)

BlairHippo
источник
Когда вы делаете это, вам лучше использовать инфраструктуры тестирования с зависимыми тестами, это лучше (например, testng поддерживает эту функцию)
Kemoda
8
Я тоже пишу такие тесты, чтобы вы могли быть уверены в том, что делает код и изменяет состояние. Я не думаю, что это модульный тест, а интеграционный тест.
mdma
Что вы думаете о рефакторинге его в assertAlarmStatus (int numberOfAlarmMessages) ;?
Борхаб
1
Ваши утверждения сделали бы для очень хороших имен теста.
Бьорн
Лучше разрешить запуск теста, даже при неверном вводе. Это дает вам больше информации таким образом (и особенно, если она все еще проходит, когда вы этого не ожидали).
CurtainDog
8

Другая причина, по которой я считаю, что несколько утверждений в одном методе - это неплохо, описана в следующем коде:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

В моем тесте я просто хочу проверить, что service.process()возвращает правильное число в Innerэкземплярах класса.

Вместо тестирования ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

я делаю

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
Betlista
источник
2
И это хорошо, у вас не должно быть никакой условной логики в ваших тестах. Это делает тест более сложным и менее читабельным. И я предполагаю, что Рой прямо в своем посте в блоге обрисовал, что несколько утверждений на одном объекте в большинстве случаев нормально. Так что те, что у вас есть, являются лишь защитными утверждениями, и это нормально, когда они есть.
Рестута
2
Ваши Assert.notNullизбыточны, ваш тест провалится с NPE, если они нулевые.
Сара
1
Кроме того, ваш первый пример (с if) пройдет, если resэтоnull
Сара
1
@kai notNull - это избыточно, я согласен, но я чувствую, что вместо исключения
лучше
1
Что ж, несостоявшееся утверждение также вызывает исключение, и в обоих случаях вы получаете прямую ссылку на конкретную строку, которая генерирует его, с сопровождающим трассировкой стека, поэтому лично я бы не стал загромождать тест предварительными условиями, которые все равно будут проверены. Я бы предпочел oneliner a la Assert.assertEquals(..., service.process().getInner());, возможно с извлеченными переменными, если строка становится «слишком длинной»
sara
6

Я думаю, что существует множество случаев, когда написание нескольких утверждений является допустимым в рамках правила, согласно которому тест должен проваливаться только по одной причине.

Например, представьте себе функцию, которая анализирует строку даты:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

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

Пит
источник
3

Я не знаю ни одной ситуации, когда было бы неплохо иметь несколько утверждений внутри самого метода [Test]. Основная причина, по которой людям нравится иметь несколько утверждений, заключается в том, что они пытаются создать один класс [TestFixture] для каждого тестируемого класса. Вместо этого вы можете разбить свои тесты на несколько классов [TestFixture]. Это позволяет увидеть несколько способов, которыми код, возможно, не отреагировал так, как вы ожидали, а не тот, в котором первое утверждение не удалось. Способ достижения этого заключается в том, что у вас есть по крайней мере один каталог на класс, который тестируется с большим количеством классов [TestFixture] внутри. Каждый класс [TestFixture] будет назван в соответствии с конкретным состоянием объекта, который вы будете тестировать. Метод [SetUp] переводит объект в состояние, описываемое именем класса. Затем у вас есть несколько методов [Test], каждый из которых утверждает разные вещи, которые, как вы ожидаете, будут истинными, учитывая текущее состояние объекта. Каждый метод [Test] назван в честь того, что он утверждает, за исключением того, что, возможно, он может быть назван в честь концепции, а не только для чтения кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. за исключением, возможно, он может быть назван в честь концепции, а не просто чтение кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. за исключением, возможно, он может быть назван в честь концепции, а не просто чтение кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. и что вы ожидаете, просто посмотрев на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. и что вы ожидаете, просто посмотрев на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок.

Обычно это означает, что последняя строка кода внутри метода [SetUp] должна хранить значение свойства или возвращаемое значение в частной переменной экземпляра [TestFixture]. Тогда вы можете утверждать несколько разных вещей об этой переменной экземпляра из разных методов [Test]. Вы также можете сделать утверждение о том, какие различные свойства тестируемого объекта установлены сейчас, когда он находится в желаемом состоянии.

Иногда по ходу дела вам нужно делать утверждения, когда вы переводите тестируемый объект в желаемое состояние, чтобы убедиться, что вы не ошиблись, прежде чем перевести объект в желаемое состояние. В этом случае эти дополнительные утверждения должны появиться внутри метода [SetUp]. Если что-то пойдет не так внутри метода [SetUp], будет ясно, что что-то не так с тестом, прежде чем объект попадет в желаемое состояние, которое вы намеревались проверить.

Еще одна проблема, с которой вы можете столкнуться, заключается в том, что вы можете тестировать исключение, которое вы ожидали получить. Это может соблазнить вас не следовать приведенной выше модели. Тем не менее, это все еще может быть достигнуто путем перехвата исключения внутри метода [SetUp] и сохранения его в переменной экземпляра. Это позволит вам утверждать разные вещи об исключении, каждый в своем собственном методе [Test]. Затем вы можете также утверждать и другие вещи относительно тестируемого объекта, чтобы убедиться в отсутствии непреднамеренных побочных эффектов от создаваемого исключения.

Пример (это будет разбито на несколько файлов):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
INTPnerd
источник
Как вы обрабатываете ввод TestCase в этом случае?
Саймон Гилби,
Итак, в моей нынешней организации у нас более 20 000 модульных тестов, очень похожих на те, что вы показываете. Это кошмар. Большая часть кода настройки теста копируется / вставляется, что приводит к неправильной настройке теста и недействительным тестам, которые проходят. Для каждого [Test]метода этот класс повторно создается, и [SetUp]метод выполняется снова. Это убивает .NET Garbage Collector и заставляет тесты выполняться очень медленно: 5+ локально, 20+ минут на сервере сборки. Тесты 20К должны выполняться примерно через 2-3 минуты. Я бы вообще не рекомендовал этот стиль тестирования, особенно для большого набора тестов.
четверг в ночь на
@fourpastmidnight Большая часть того, что вы сказали, кажется обоснованной критикой, но вопрос о копировании и вставке установочного кода и, следовательно, неправильности, это не проблема структуры, а безответственных программистов (что может быть результатом безответственных менеджеров или негативной среды тем более, чем плохие программисты). Если люди просто копируют и вставляют код и надеются, что он правильный, и не потрудились понять код, в любом контексте и по любой причине, им нужно либо научиться не делать этого, либо отпустить, если они не могут обучен. Это противоречит принципам программирования.
still_dreaming_1
Но в целом, я согласен, это сумасшедшее излишество, которое приведет к большому количеству раздувания / багажа / дублирования, что приведет к всевозможным проблемам. Раньше я был сумасшедшим и рекомендовал подобные вещи. Это то, что я надеюсь иметь возможность говорить каждый день о себе накануне, потому что это означает, что я никогда не перестаю находить лучшие способы ведения дел.
still_dreaming_1
@ still_dreaming_1 по отношению к "безответственным программистам": я согласен с тем, что такое поведение является серьезной проблемой. Тем не менее, такая структура тестов действительно требует такого поведения, к лучшему или к худшему. Помимо плохой практики разработки, мое основное возражение против этой формы заключается в том, что она действительно убивает производительность теста. Нет ничего хуже, чем медленно работающий набор тестов. Медленно работающий набор тестов означает, что люди не будут запускать тесты локально и даже испытывают искушение пропустить их при промежуточных сборках - опять-таки, проблема с людьми, но это случается - чего можно избежать, если обеспечить быстрое выполнение тестов для запуска с участием.
четверг в ночь на
2

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

Я не могу придумать сценарий, в котором действительно требуется несколько утверждений , поскольку вы всегда можете переписать их как несколько условий в одном утверждении. Однако может быть предпочтительнее, если, например, у вас есть несколько шагов для проверки промежуточных данных между шагами, а не рискуете, что последующие шаги потерпят крах из-за неправильного ввода.

Guffa
источник
1
Если вы объединяете несколько условий в одно утверждение, то при сбое все, что вам известно, это то, что одно не выполнено. С несколькими утверждениями вы точно знаете о некоторых из них (до отказа и включая его). Проверьте, что возвращаемый массив содержит одно значение: убедитесь, что оно не равно нулю, тогда у него ровно один элемент, а затем значение этого элемента. (В зависимости от платформы) простая проверка значения немедленно может дать нулевое разыменование (менее полезно, чем завершение нулевого утверждения) и не проверяет длину массива.
Ричард
@Richard: получение результата, а затем извлечение чего-либо из этого результата будет процессом в несколько этапов, поэтому я рассмотрел это во втором абзаце в ответе.
Гуффа
2
Основное правило: если в тесте есть несколько утверждений, каждое из них должно иметь свое сообщение. Тогда у вас нет этой проблемы.
Энтони
А использование качественного тестового прогона, такого как NCrunch, покажет вам, на какой именно строке тест не пройден, как в тестовом коде, так и в тестируемом коде.
четверг в ночь на
2

Если ваш тест не пройден, вы не будете знать, нарушатся ли следующие утверждения. Зачастую это означает, что вам не хватит ценной информации, чтобы выяснить источник проблемы. Мое решение заключается в использовании одного утверждения, но с несколькими значениями:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

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

Аарон Дигулла
источник
2

Если у вас есть несколько утверждений в одной тестовой функции, я ожидаю, что они будут иметь непосредственное отношение к тесту, который вы проводите. Например,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

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

hagubear
источник
1

Цель юнит-теста состоит в том, чтобы дать вам как можно больше информации о том, что не работает, а также помочь в первую очередь точно определить наиболее фундаментальные проблемы. Когда вы логически знаете, что одно утверждение потерпит неудачу, учитывая, что другое утверждение не выполнено, или, другими словами, между тестами существует зависимость, тогда имеет смысл свернуть их как несколько утверждений в одном тесте. Преимущество этого заключается в том, что результаты теста не засоряются очевидными ошибками, которые можно было бы устранить, если бы мы выручили первое утверждение в рамках одного теста. В случае, когда такого отношения не существует, естественным образом предпочтение будет отдавать этим утверждениям на отдельные тесты, потому что в противном случае для обнаружения этих отказов потребовалось бы несколько итераций прогонов тестов для решения всех проблем.

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

оборота jpierson
источник
1

Да, можно иметь несколько утверждений, если неудачный тест дает вам достаточно информации для диагностики сбоя. Это будет зависеть от того, что вы тестируете и каковы режимы сбоя.

Правильные юнит-тесты должны проваливаться ровно по одной причине, поэтому вы должны использовать одно утверждение на юнит-тест.

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

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

Итак, просто сосредоточьтесь на ясности кода и ясности результатов теста, и пусть это будет определять количество утверждений, которые вы используете, а не наоборот.

CurtainDog
источник
0

Ответ очень прост - если вы тестируете функцию, которая изменяет более одного атрибута, одного и того же объекта или даже двух разных объектов, и правильность функции зависит от результатов всех этих изменений, то вы хотите утверждать что каждое из этих изменений было выполнено правильно!

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

Возьмите логическую концепцию банковской транзакции - снятие суммы с одного банковского счета в большинстве случаев ДОЛЖНО включать добавление этой суммы на другой счет. Вы НИКОГДА не хотите отделить эти две вещи, они образуют атомную единицу. Возможно, вы захотите сделать две функции (снять / добавить деньги) и, таким образом, написать два разных модульных теста - в дополнение. Но эти два действия должны выполняться в рамках одной транзакции, и вы также хотите убедиться, что транзакция работает. В этом случае просто недостаточно убедиться, что отдельные шаги были успешными. Вы должны проверить оба банковских счета, в вашем тесте.

Вначале могут быть более сложные примеры, которые вы бы не тестировали в модульном тесте, а вместо этого в интеграционном или приемочном тесте. Но эти границы свободно, ИМХО! Это не так легко решить, это вопрос обстоятельств и, возможно, личных предпочтений. Снятие денег с одного и добавление их на другой счет - все еще очень простая функция, и, безусловно, кандидат на юнит-тестирование.

cslotty
источник
-1

Этот вопрос связан с классической проблемой баланса между проблемами кода спагетти и лазаньи.

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

Есть некоторые исключения, но в этом случае удержание маятника посередине является ответом.

GSF
источник
-3

Я даже не согласен с «провалом только по одной причине» в целом. Что более важно, так это то, что тесты короткие и четко читаются на ИМО.

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

Йохан Ларссон
источник