You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NUnit中断言集合存在符合自定义条件的元素?

解决方案

1. 单个元素的优雅断言写法

针对单个输入元素的场景,你可以直接使用NUnit的Has.Some.Matches约束,直接对结果集合断言,无需额外转换:

[Test]
public void SinglePersonConversionTest()
{
    var inputPerson = GenerateOtherPerson();
    List<Person> result = _classUnderTest.ConverToPerson(new[] { inputPerson }.ToList());

    // 直接对结果集合断言,验证存在匹配的元素
    Assert.That(result, Has.Some.Matches<Person>(p => AreEquivalent(p, inputPerson)));
}

public bool AreEquivalent(Person p1, OtherPerson p2)
{
    return p1.Name == $"{p2.FirstName} {p2.LastName}"
        && p1.Age == DateTime.Now.Year - p2.DateOfBirth.Year
        && p1.Uuid == p2.Guid.ToString();
}

2. 多个元素的场景:验证每个输入都有对应匹配

如果是多输入元素的场景,要确保每个输入的OtherPerson都能在结果中找到等价的Person,可以用以下两种方式:

方式一:循环验证每个输入的匹配存在

这种方式直观清晰,适合需要明确每个输入都对应结果的场景:

[Test]
public void MultiplePersonsConversionTest()
{
    var inputPersons = new List<OtherPerson>
    {
        GenerateOtherPerson(),
        GenerateOtherPerson(),
        GenerateOtherPerson()
    };
    List<Person> result = _classUnderTest.ConverToPerson(inputPersons);

    // 对每个输入元素,验证结果中存在匹配项
    foreach (var input in inputPersons)
    {
        Assert.That(result, Has.Some.Matches<Person>(p => AreEquivalent(p, input)));
    }
}

方式二:双向验证确保一一对应

如果需要同时验证结果集合没有多余元素(输入和结果完全一一匹配),可以补充双向断言:

[Test]
public void ExactMatchConversionTest()
{
    var inputPersons = new List<OtherPerson>
    {
        GenerateOtherPerson(),
        GenerateOtherPerson()
    };
    List<Person> result = _classUnderTest.ConverToPerson(inputPersons);

    // 先验证数量一致
    Assert.That(result.Count, Is.EqualTo(inputPersons.Count));

    // 验证每个输入都有对应结果,且每个结果都对应一个输入
    Assert.That(inputPersons, All.Matches<OtherPerson>(input => 
        result.Any(p => AreEquivalent(p, input))));
    Assert.That(result, All.Matches<Person>(p => 
        inputPersons.Any(input => AreEquivalent(p, input))));
}

3. 进阶:自定义NUnit约束简化断言

如果这类等价性验证频繁出现,可以自定义NUnit约束,让断言更简洁:

public class EquivalentToOtherPersonConstraint : Constraint
{
    private readonly OtherPerson _expected;

    public EquivalentToOtherPersonConstraint(OtherPerson expected)
    {
        _expected = expected;
    }

    public override ConstraintResult ApplyTo<TActual>(TActual actual)
    {
        if (actual is not Person person)
            return new ConstraintResult(this, actual, ConstraintStatus.Failure);

        bool isEquivalent = person.Name == $"{_expected.FirstName} {_expected.LastName}"
            && person.Age == DateTime.Now.Year - _expected.DateOfBirth.Year
            && person.Uuid == _expected.Guid.ToString();

        return new ConstraintResult(this, actual, isEquivalent ? ConstraintStatus.Success : ConstraintStatus.Failure);
    }
}

// 扩展方法简化调用
public static class ConstraintExtensions
{
    public static EquivalentToOtherPersonConstraint EquivalentTo(this ConstraintExpression expression, OtherPerson expected)
    {
        var constraint = new EquivalentToOtherPersonConstraint(expected);
        expression.Append(constraint);
        return constraint;
    }
}

之后断言可以写成非常简洁的形式:

Assert.That(result, Has.Some.EquivalentTo(inputPerson));

内容的提问来源于stack exchange,提问作者ewok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 21:55:18