测试集合不包含多个成员
Test collection does not contain several members
考虑这段代码:
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));
但是如果我打算再次测试超过 2 个成员的集合,那将是有问题的。有没有更好的语法?我正在使用 NUnit 3.4。
注意:目标不仅仅是测试结果,还应该是正确的断言,这样每当测试失败时,我们都可以从错误消息中更快地确定。这是上一个示例中的错误消息示例(使用 Does.Not.Contains
)
"Expected: not collection containing "user8" 且不包含 "user9"
但是是:< "user1"、"user2"、"user3"、"user4"、"user8" >"
尝试使用 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 }
数组中的任何实例。
错误消息将是这样的:
Expected: not collection containing "user8"
But was: < "user1", "user8", "user2", "user3" >
尝试检查针对用户的排除列表
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
在深入了解创建自定义约束并下载 NUnit 源代码之后,我决定创建自定义 CollectionContainsConstraint
/// <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}\""));
}
}
请注意,这仅用于负面测试。幸运的是,我现在不需要 "any member of a collection is available in another collection" 的测试。大多数情况下,它测试一个集合是另一个集合的一部分,因此,我可以使用 Is.SupersetOf
.
考虑这段代码:
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));
但是如果我打算再次测试超过 2 个成员的集合,那将是有问题的。有没有更好的语法?我正在使用 NUnit 3.4。
注意:目标不仅仅是测试结果,还应该是正确的断言,这样每当测试失败时,我们都可以从错误消息中更快地确定。这是上一个示例中的错误消息示例(使用 Does.Not.Contains
)
"Expected: not collection containing "user8" 且不包含 "user9" 但是是:< "user1"、"user2"、"user3"、"user4"、"user8" >"
尝试使用 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 }
数组中的任何实例。
错误消息将是这样的:
Expected: not collection containing "user8" But was: < "user1", "user8", "user2", "user3" >
尝试检查针对用户的排除列表
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
在深入了解创建自定义约束并下载 NUnit 源代码之后,我决定创建自定义 CollectionContainsConstraint
/// <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}\""));
}
}
请注意,这仅用于负面测试。幸运的是,我现在不需要 "any member of a collection is available in another collection" 的测试。大多数情况下,它测试一个集合是另一个集合的一部分,因此,我可以使用 Is.SupersetOf
.