Тестовая коллекция не содержит нескольких членов

Рассмотрим этот код:

const string user8 = "user8";
const string user9 = "user9";
string[] users = { "user1", "user2", "user3", "user4", user8 };

Я хочу проверить, что users не содержит ни user8, ни user9. я использовал

Assert.That(users, Is.Not.SupersetOf(new[] {user8, user9 }));

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

Assert.That(users, Does.Not.Contains(user8).And.Not.Contains(user9));

Но это будет проблематично, если я собираюсь протестировать коллекцию более чем на двух участниках. Есть ли лучший синтаксис? Я использую NUnit 3.4.

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

«Ожидалось: не коллекция, содержащая «user8» и не содержащая «user9». Но было: ‹ «user1», «user2», «user3», «user4», «user8» >»


person iroel    schedule 18.01.2017    source источник
comment
Вы пробовали CollectionAssert?   -  person hendryanw    schedule 18.01.2017
comment
@ Хендри, в CollectionAssert есть несколько методов. Можете ли вы сказать более конкретно? К вашему сведению, я предпочитаю использовать ограничение. Но если это единственное решение, то проблем быть не должно.   -  person iroel    schedule 18.01.2017


Ответы (3)


Попробуйте проверить список исключений для пользователей

const string user8 = "user8";
const string user9 = "user9";
string[] users = { "user1", "user2", "user3", "user4", user8 };

Assert.Multiple(() => {
    var exclude = new[] { user8, user9 }; 
    foreach(var user in exclude) {
        Assert.That(users, Has.No.Member(user));
    }
}

Этот тест должен завершиться неудачей, поскольку список пользователей содержит user8

Документы NUnit — CollectionContainsConstraint

person Nkosi    schedule 18.01.2017
comment
В этом сценарии тест должен завершиться неудачей, поскольку users содержит user8. - person iroel; 18.01.2017
comment
как это должно. вы указали I want to test that the users does not contain either user8 or user9, что означает, что тест должен завершиться неудачно, если user8 находится в списке пользователей. Уточните, что вы хотите. - person Nkosi; 18.01.2017
comment
Да, это тестовое ожидание. Цель состоит в том, чтобы найти встроенный синтаксис, поэтому сообщение об ошибке также будет иметь соответствующее сообщение. Это сработает в большинстве случаев. Можно ли написать собственное ограничение, содержащее этот код? - person iroel; 18.01.2017

Попробуйте использовать CollectionAssert.IsNotSubsetOf()

CollectionAssert.IsNotSubsetOf(new[] {user8Name, user9Name }), users);

Обновление:

Ну, вы всегда можете использовать базовый цикл.

Array.ForEach(new[] { user8, user9 }, u => Assert.That(users, Has.No.Member(u)));

Это проверит, содержит ли users какие-либо экземпляры в массиве new[] { user8, user9 }, зациклив его.

Сообщение об ошибке будет примерно таким:

Ожидается: не коллекция, содержащая «user8». Но была: ‹ «user1», «user8», «user2», «user3» >

person hendryanw    schedule 18.01.2017
comment
Ожидается, что сценарий завершится ошибкой, поскольку он содержит user8. - person iroel; 18.01.2017
comment
boolean тестирование всегда работает, хотя и не подходит для этого условия, потому что всякий раз, когда тест терпит неудачу, оно генерирует неинформативное сообщение. Спасибо за ответ в любом случае. - person iroel; 18.01.2017
comment
@iroel Обновленный ответ обеспечивает лучшее сообщение об ошибке, но это все еще не встроенная функция, хотя вы всегда можете использовать метод расширения. Я просто оставлю это здесь на случай, если этого достаточно для других. - person hendryanw; 18.01.2017

После изучения создания настраиваемого ограничения и загрузки исходного кода NUnit я решил создать собственное свойство CollectionContainsConstraint a

/// <summary>
/// CollectionContainsAnyConstraint is used to test whether a collection
/// contains any member in expected collection.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CollectionContainsAnyConstraint<T> : CollectionContainsConstraint
{
    public CollectionContainsAnyConstraint(IEnumerable<T> expected) : base(expected)
    {
    }

    public override string Description
        => Regex.Replace(base.Description, @"^\s*collection containing", "collection containing any of");

    /// <summary>
    /// Test whether any member in expected collection is available in actual collection
    /// </summary>
    /// <param name="actual">Actual collection</param>
    /// <returns></returns>
    protected override bool Matches(IEnumerable actual)
    {
        var convertedExpected = (IEnumerable<T>)Expected;
        var convertedActual = EnsureHasSameGenericType(actual, typeof(T));
        return convertedActual.Any(x => convertedExpected.Contains(x));
    }

    private IEnumerable<T> EnsureHasSameGenericType(IEnumerable actual, Type expectedType)
    {
        var sourceType = actual.GetType();
        var sourceGeneric = sourceType.IsArray
            ? sourceType.GetElementType()
            : sourceType.GetGenericArguments().FirstOrDefault();
        if (sourceGeneric == null)
            throw new ArgumentException("The actual collection must contain valid generic argument");
        if (!sourceGeneric.Equals(expectedType))
            throw new ArgumentException($"The actual is collection of {sourceGeneric.Name} but the expected is collection of {expectedType.Name}");
        return (IEnumerable<T>)actual;
    }
}

public static class ConstraintExtensions
{
    /// <summary>
    /// Returns a new <see cref="CollectionContainsAnyConstraint{T}"/> checking for the
    /// presence of any object of the <see cref="expected"/> collection against actual collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    /// <param name="expected">A collection where one of its member is available in actual collection</param>
    /// <returns></returns>
    public static CollectionContainsAnyConstraint<T> ContainsAny<T>(this ConstraintExpression expression,
        params T[] expected)
    {
        var constraint = new CollectionContainsAnyConstraint<T>(expected);
        expression.Append(constraint);
        return constraint;
    }
}

и использование будет выглядеть так

[TestFixture]
class GivenCustomCollectionContainsTest
{
    const string User8 = "user8";
    const string User9 = "user9";
    private readonly List<string> users = new List<string> { "user1", "user2", "user3", "user4", "user5" };

    [Test]
    public void WhenActualContainsOneOfExpectedAndPreceededByNotOperatorThenItShouldFail()
    {
        var actual = users.ToList();
        actual.Add(User8);

        var assert = Assert.Throws<AssertionException>(() => Assert.That(actual, Does.Not.ContainsAny(User8, User9)));
        Assert.That(assert.ResultState.Status, Is.EqualTo(TestStatus.Failed));
        Assert.That(assert.Message, Does.Contain("not collection containing any of").And.Contain($"\"{User8}\""));
    }

    [Test]
    public void WhenActualContainsAllOfExpectedAndPreceededByNotOperatorThenItShouldFail()
    {
        var actual = users.ToList();
        actual.Add(User8);
        actual.Add(User9);

        var assert = Assert.Throws<AssertionException>(() => Assert.That(actual, Does.Not.ContainsAny(User8, User9)));
        Assert.That(assert.ResultState.Status, Is.EqualTo(TestStatus.Failed));
        Assert.That(assert.Message, Does.Contain("not collection containing any of").And.Contain($"\"{User8}\", \"{User9}\""));
    }
}

Просто обратите внимание, что это предназначено только для отрицательного теста. К счастью, в данный момент мне не нужен тест на то, что «любой член коллекции доступен в другой коллекции». В основном он проверяет, является ли коллекция частью другой коллекции, поэтому я могу использовать Is.SupersetOf.

person iroel    schedule 18.01.2017