Как мне написать тест для чистого метода, который ничего не возвращает?

13

У меня есть куча классов, которые занимаются проверкой значений. Например, RangeValidatorкласс проверяет, находится ли значение в указанном диапазоне.

Каждый класс валидатора содержит два метода:, is_valid(value)который возвращает Trueили Falseзависит от значения, и ensure_valid(value)который проверяет указанное значение и либо ничего не делает, если значение является допустимым, либо выдает конкретное исключение, если значение не соответствует предопределенным правилам.

В настоящее время с этим методом связаны два модульных теста:

  • Тот, который передает недопустимое значение и гарантирует, что было сгенерировано исключение.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Тот, который передает действительное значение.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

Несмотря на то, что второй тест выполняет свою работу - терпит неудачу, если выбрасывается исключение, и успешен, если ensure_validничего не выдает - тот факт, что внутри нет asserts, выглядит странным. Кто-то, кто читает такой код, сразу же спрашивает себя, почему существует тест, который, похоже , ничего не делает.

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

Арсений Мурзенко
источник
11
Педантичный момент, но если у вас есть функция, которая не принимает аргументов (за исключением selfссылки) и не возвращает результата, она не является чистой функцией.
Дэвид Арно
10
@DavidArno: Это не педантичный вопрос, он идет к сути вопроса: метод трудно проверить именно потому, что он нечистый.
Йорг Миттаг
@DavidArno Более педантичный момент, у вас может быть метод «ничего не делать» (при условии, что «не возвращает результатов» интерпретируется как «возвращает тип модуля», который вызывается voidво многих языках и имеет глупые правила.) В качестве альтернативы, вы можете иметь бесконечное число цикл (который работает, даже когда «не возвращает результата» действительно означает, что не возвращает результатов.
Дерек Элкинс покинул SE
В любом языке есть одна чистая функция типа, unit -> unitкоторая считает единицу стандартным типом данных, а не чем-то магически специальным. К сожалению, он просто возвращает единицу и больше ничего не делает.
Фоши

Ответы:

21

Большинство тестовых сред имеют явное утверждение «не выбрасывает», например, у Jasmine есть expect(() => {}).not.toThrow();и у nUnit, и у друзей есть такое.

DeadMG
источник
1
И если в тестовой среде такого утверждения нет, всегда можно создать локальный метод, который делает то же самое, делая код самоочевидным. Хорошая идея.
Арсений Мурзенко
7

Это сильно зависит от используемого языка и структуры. Говоря с точки зрения NUnit, есть Assert.Throws(...)методы. Вы можете передать им лямбда-метод:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

который выполняется в рамках Assert.Throws. Вызов лямбда, скорее всего, будет обернут try { } catch { }блоком, и утверждение не будет выполнено, если будет обнаружено исключение.

Если ваш фреймворк не предоставляет этих средств, вы можете обойти его, обернув вызов самостоятельно (я пишу на C #):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

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

редактировать

Как отметил в комментариях Док Браун, проблема заключается не в том, чтобы указать, что метод выдает, а что он не выдает. В NUnit также есть утверждение

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))
Пол Керчер
источник
1

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

Как вы можете видеть из других ответов, все остальное делает код более сложным и загроможденным. С комментариями там другие программисты будут знать цель теста.

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

jhyot
источник
1

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

Например:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

В вашем тесте:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
Габриэль Роберт
источник