如何在单个测试中同时执行多个断言

前言

虽然,推荐做法是每次测试只断言一件事,但是,在实际工作中,我们可能需要对同一个对象同时执行多个断言。

例如,微软官方示例项目eShopOnContainers有一个测试用例的实现代码如下:

[Fact]
public async Task Add_to_cart_success()
{
    //Arrange
    var fakeCatalogItem = GetFakeCatalogItem();

    _basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny<ApplicationUser>(), It.IsAny<Int32>()))
        .Returns(Task.FromResult(1));

    //Act
    var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);
    orderController.ControllerContext.HttpContext = _contextMock.Object;
    var actionResult = await orderController.AddToCart(fakeCatalogItem);

    //Assert
    Assert.Equal("Catalog", redirectToActionResult.ControllerName);
    Assert.Equal("Index", redirectToActionResult.ActionName);
}

如果我们将所有断言都改成必定失败:

//Assert
Assert.Equal("WrongCatalog", redirectToActionResult.ControllerName);
Assert.Equal("WrongIndex", redirectToActionResult.ActionName);

但运行测试时,只会提示第一个失败的断言:

70767309db00afc6a01cd950d0017030.png

在这种情况下,一个个断言去验证并修正比较耗时。

如果同时能够看到所有失败的断言则更加有帮助。

手工实现

查看`Assert.Equal`的实现代码[1],它是通过抛出EqualException异常来表明断言失败:

public static void Equal<T>(T expected, T actual, IEqualityComparer<T> comparer)
{
    GuardArgumentNotNull(nameof(comparer), comparer);

    var expectedAsIEnum = expected as IEnumerable;
    var actualAsIEnum = actual as IEnumerable;

    // If both are IEnumerable (or null), see if we got an AssertEqualityComparer<T>, so that
    // we can invoke it to get the mismatched index.
    if ((expectedAsIEnum != null && (actual == null || actualAsIEnum != null)) ||
        (actualAsIEnum != null && expected == null))
    {
        var aec = comparer as AssertEqualityComparer<T>;
        int? mismatchedIndex;

        if (aec != null && !aec.Equals(expected, actual, out mismatchedIndex))
        {
            if (mismatchedIndex.HasValue)
                throw EqualException.FromEnumerable(expectedAsIEnum, actualAsIEnum, mismatchedIndex.Value);
            else
                throw new EqualException(expected, actual);
        }
    }

    if (!comparer.Equals(expected, actual))
        throw new EqualException(expected, actual);
}

而所有断言异常都继承自基类XunitException

89b3bacad82d4b4949db8f645f5f23b2.png

74c815aa5aa137fe83890c7080be8b99.png

因此,我们可以捕获每个断言的异常,然后将多个异常添加到集合中,在测试结束时再抛出:

var xunitExceptions = new List<XunitException>();
try
{
    Assert.Equal("WrongCatalog", redirectToActionResult.ControllerName);
}
catch (XunitException ex)
{
    xunitExceptions.Add(ex);
}

try
{
    Assert.Equal("WrongIndex", redirectToActionResult.ActionName);
}
catch (XunitException ex)
{
    xunitExceptions.Add(ex);
}

if (xunitExceptions.Any())
{
    throw new AggregateException(xunitExceptions.ToArray());
}

7124bcb9edfbf35043d3c9f77529b3ba.png

这里虽然列出了所有失败断言,但是所有错误显示了2遍,而且我们必须为测试编写大量的try-catch代码。

有不有更好的方法呢?!

FluentAssertions

FluentAssertions是一组.NET扩展方法,允许用更自然的语法去验证断言。

引用Nuget包FluentAssertions,示例的断言可以修改成如下格式:

redirectToActionResult.ControllerName.Should().Be("WrongCatalog");
redirectToActionResult.ActionName.Should().Be("WrongIndex");

除此之外,还可以将多个断言放到一个AssertionScope中,以便FluentAssertions在所有失败的范围末尾抛出一个异常:

using (new FluentAssertions.Execution.AssertionScope())
{
    redirectToActionResult.ControllerName.Should().Be("WrongCatalog");
    redirectToActionResult.ActionName.Should().Be("WrongIndex");
}

b0bf3d0f7bb8dabde37e1c933dbd20a4.png

结论

有时,将多个断言组合在一起测试是有意义的。

在这种情况下,可以使用FluentAssertions的AssertionScope来编写此类测试。

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“

参考资料

[1]

Assert.Equal的实现代码: https://github.com/xunit/assert.xunit/blob/main/EqualityAsserts.cs#L78

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值