Почему в NUnit нет ограничения IsElementOf/IsOneOf?

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

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

Assert.That(aValue, Is.GreaterThan(2.0) & Is.LessThan(5.0));

Вы также можете проверить, находится ли значение в некотором диапазоне:

Assert.That(aValue, Is.InRange(2.0, 5.0));

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

Assert.That(aValue, Is.OneOf(aCollection));

Разве это не так часто встречается в модульном тесте? Указывает ли это на какую-то проблему с моими модульными тестами? Все просто вводят aValue в какую-то фиктивную коллекцию из одного элемента, а затем используют Is.SubsetOf?


person Lambdageek    schedule 18.06.2011    source источник


Ответы (3)


Текущая версия NUnit (3.13) имеет собственный метод для этого: Is.AnyOf(params object[] expected)

Вы можете использовать его следующим образом:

Assert.That(result, Is.AnyOf("red", "green", "blue"));
Assert.That(result, Is.Not.AnyOf("black", "white"));
person Sven Vranckx    schedule 05.07.2021

API утверждений - это все о удобочитаемости, и нет никакой выгоды в удобочитаемости наличия Is.OneOf(коллекция) для этого:

Assert.That(collection.Contains(value));

Он читабелен и понятен, поэтому дублировать каждый случай в Assersion API неправильно, если это ничего не дает. Насколько вы можете видеть, нет простого способа написать альтернативу Is.InRange, и Is.GreaterThan + Is.LessThan более удобочитаема, чем

Assert.That(value > 2.0 && value < 5.0);
//compared to
Assert.That(value, Is.GraterThan(2.0).And.Is.LessThan(5.0));
person Restuta    schedule 18.06.2011
comment
Но, конечно же, та же логика может быть применена к Is.OneOf(collection): вы можете комбинировать его с другими ограничениями: Assert.That(value, Is.OneOf(Foo).And.Is.Not.OneOf(Bar)). Также collection.Contains(value) фокусируется на collection, а не на value. - person Lambdageek; 18.06.2011
comment
Оно может быть как хорошим, так и плохим. Это зависит от того, что вы тестируете, что какая-то коллекция имеет значение или какое-то значение существует в коллекции. И это не та же логика, я не говорил об объединении ограничений. Я могу понять комбинирование ограничений для чисел, но мне это странно видеть. Является.ОднимИз(Foo).И.Есть.Не.ОднимИз(Бар)). Как по мне, там две ассерии в одной и это не очень хороший подход в написании юнит-тестов. - person Restuta; 29.06.2011

@Lambdageek;

Assert.That(aCollection, Has.Member(aValue) 
Assert.That(aCollection, Has.No.Member(aValue) 

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

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

ИЗМЕНИТЬ

    [Test]
    public void Test() {
        var c = new[] {"one", "two"};
        Assert.That(c, Has.Member("three"));
    }

Test failed:
  Expected: collection containing "three"
  But was:  < "one", "two" >
    Tests.cs(73,0): at ...Test()

С уважением,
Беррил

Ограничение бедняка

public static class TestExtensions
{
    public static bool IsOneOf<T>(this T candidate, IEnumerable<T> expected) {
        if (expected.Contains(candidate)) return true;

        var msg = string.Format("Expected one of: '{0}'. Actual: {1}", ", "._insertBetween(expected.Select(x => Convert.ToString(x))), candidate);
        Assert.Fail(msg);
        return false;
    }

    private static string _insertBetween(this string delimiter, IEnumerable<string> items)
    {
        var builder = new StringBuilder();
        foreach (var item in items)
        {
            if (builder.Length != 0)
            {
                builder.Append(delimiter);
            }
            builder.Append(item);
        }
        return builder.ToString();
    }

    internal static IEnumerable<string> GenerateSplits(this string str, params char[] separators)
    {
        foreach (var split in str.Split(separators))
            yield return split;
    }

}

Неудачный тест

    [Test]
    public void IsOneOf_IfCandidateNotInRange_Error()
    {
        IEnumerable<string> expected = new[] { "red", "green", "blue" };
        const string candidate = "yellow";
        Assert.That(candidate.IsOneOf(expected));
    }

IsOneOf_IfCandidateNotInRange_Error' failed:
Expected one of: 'red, green, blue'. Actual: yellow
person Berryl    schedule 19.06.2011
comment
Что ж, как я уже упоминал в ответ на ответ @Restuta: на мой взгляд, Assert.That(x, Has.Member(y)) делает акцент на x, когда я действительно тестирую y. В частности, я хочу, чтобы сообщения об ошибках сообщали мне о фактическом значении aValue в случае сбоя модульного теста, а не о фактическом значении aCollection. - person Lambdageek; 19.06.2011
comment
@Lambdageek - см. мое редактирование выше. Я думал, что вы хотели утверждать, что aValue находится в коллекции, но, возможно, я вас неправильно понимаю. Если нет, то что именно вы хотите утверждать, и как следует читать сообщение о неудачном тесте? Если да, то чего не хватает существующему ограничению? - person Berryl; 19.06.2011
comment
Я хочу проверить, что aValue вычисляется какой-либо функцией: aValue = Foo() является одним из набора допустимых значений: Assert.That(aValue, Is.OneOf({"red", "green", "blue"}); Итак, я хочу, чтобы в сообщении об ошибке говорилось: Ожидается одно из «красного», «зеленого», «синего». Актуально: «желтый» - person Lambdageek; 19.06.2011
comment
@Lambdageek. Понял, и нет, я не вижу ничего подобного в NUnit. Я действительно сохраняю статический класс TestExtensions для тех (редких) случаев, когда мне нужно больше, чем дает NUnit, поэтому написание нужного вам расширения будет выглядеть примерно так, как код, который я добавил в свой ответ. Это действительно несложно сделать, если у вас есть некоторые вспомогательные методы (реквизит Skeet для этого). Ваше здоровье - person Berryl; 20.06.2011