C# 测试集合不包含多个成员

C# 测试集合不包含多个成员,c#,collections,nunit,assertion,nunit-3.0,C#,Collections,Nunit,Assertion,Nunit 3.0,考虑以下代码: const string user8 = "user8"; const string user9 = "user9"; string[] users = { "user1", "user2", "user3", "user4", user8 }; 我想测试用户是否不包含user8或user9。我以前用过 Assert.That(users, Is.Not.SupersetOf(new[] {user8, user9 })); 不幸的是,它通过了测试(这不是预期的)。我可以用

考虑以下代码:

const string user8 = "user8";
const string user9 = "user9";
string[] users = { "user1", "user2", "user3", "user4", user8 };
我想测试
用户
是否不包含user8或user9。我以前用过

Assert.That(users, Is.Not.SupersetOf(new[] {user8, user9 }));
不幸的是,它通过了测试(这不是预期的)。我可以用

Assert.That(users, Does.Not.Contains(user8).And.Not.Contains(user9));
但如果我打算对超过2个成员测试集合,这将是一个问题。有更好的语法吗?我用的是NUnit3.4

注意:目标不仅仅是测试结果,还应该是正确的断言,这样无论何时测试失败,我们都可以从错误消息中更快地确定。这是上一个示例中的错误消息示例(使用
dos.Not.Contains

应为:不包含“user8”和“user9”的集合
但是是:<“user1”、“user2”、“user3”、“user4”、“user8”>“

尝试使用
CollectionAssert.IsNotSubsetOf()

更新:

嗯,你总是可以使用基本循环

Array.ForEach(new[] { user8, user9 }, u => Assert.That(users, Has.No.Member(u)));
这将通过循环检查
users
是否在
new[]{user8,user9}
数组中包含任何实例

错误消息如下所示:

应为:不包含“user8”的集合 但是是:<“用户1”、“用户8”、“用户2”、“用户3”>


尝试使用
CollectionAssert.IsNotSubsetOf()

更新:

嗯,你总是可以使用基本循环

Array.ForEach(new[] { user8, user9 }, u => Assert.That(users, Has.No.Member(u)));
这将通过循环检查
users
是否在
new[]{user8,user9}
数组中包含任何实例

错误消息如下所示:

应为:不包含“user8”的集合 但是是:<“用户1”、“用户8”、“用户2”、“用户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文档-

尝试根据用户检查排除列表

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文档-

在深入了解了创建自定义约束和下载NUnit源代码之后,我决定创建自定义集合ContainsConstraint 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;
    }
}
//
///CollectionContainesAnyConstraint用于测试集合
///包含预期集合中的任何成员。
/// 
/// 
公共类CollectionContainsAnyConstraint:CollectionContainsConstraint
{
公共集合containsAnyConstraint(预期为IEnumerable):基本(预期为)
{
}
公共重写字符串描述
=>Regex.Replace(base.Description,@“^\s*包含的集合”,“包含任何项的集合”);
/// 
///测试预期集合中的任何成员是否在实际集合中可用
/// 
///实际收款
/// 
受保护的覆盖布尔匹配(IEnumerable实际)
{
var convertedExpected=(IEnumerable)预期值;
var convertedActual=确保安全性平均类型(实际,类型(T));
返回convertedActual.Any(x=>convertedExpected.Contains(x));
}
private IEnumerable EnsurePassaMegenericType(IEnumerable实际类型,expectedType类型)
{
var sourceType=actual.GetType();
var sourceGeneric=sourceType.IsArray
?sourceType.GetElementType()
:sourceType.GetGenericArguments().FirstOrDefault();
if(sourceGeneric==null)
抛出新ArgumentException(“实际集合必须包含有效的泛型参数”);
如果(!sourceGeneric.Equals(expectedType))
抛出新ArgumentException($“实际是{sourceGeneric.Name}的集合,但预期是{expectedType.Name}的集合”);
返回(IEnumerable)实际值;
}
}
公共静态类约束
{
/// 
///返回一个新的检查
///存在与实际集合相对的集合的任何对象。
/// 
/// 
/// 
///其成员之一在实际集合中可用的集合
/// 
公共静态集合containsAnyConstraint ContainsAny(此约束表达式,
参数T[]预期值)
{
var约束=新集合包含SANYCONSTRAINT(预期);
expression.Append(约束);
回报约束;
}
}
用法如下所示

[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}\""));
    }
}
[TestFixture]
类GivenCustomCollectionContainsTest
{
常量字符串User8=“User8”;
常量字符串User9=“User9”;
私有只读列表用户=新列表{“user1”、“user2”、“user3”、“user4”、“user5”};
[测试]
当实际包含预期的和之前的不可操作项应失败()时,公共无效
{
var-actual=users.ToList();
实际添加(User8);
var assert=assert.Throws(()=>assert.That(实际,不.Not.ContainsAny(User8,User9));
Assert.That(Assert.ResultState.Status,Is.EqualTo(TestStatus.Failed));
Assert.That(Assert.Message,Does.Contain(“不包含任何内容的集合”).And.Contain($“\{User8}\”);
}
[测试]
当实际包含预期的分配和之前的不可操作项应失败()时,公共无效
{
var-actual=users.ToList();
实际添加(User8);
添加(User9);
var assert=assert.Throws(()=>assert.That(实际,不.Not.ContainsAny(User8,User9));
Assert.That(Assert.ResultState.Status,Is.EqualTo(TestStatus.Failed));
Assert.That(Assert.Message,Does.Contain(“不包含任何内容的集合”).And.Contain($“\”{User8}\,\“{User9}\”));
}
}

请注意,这仅用于阴性测试。幸运的是,我现在不需要测试“一个集合的任何成员在另一个集合中都可用”。大多数情况下,它测试一个集合是否是另一个集合的一部分,因此,在深入了解创建自定义约束和下载NUnit源代码后,我可以使用
is.SupersetOf
,因此我决定创建自定义集合contains约束

/// <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;
    }
}
//
///集合包含一个CONST