前言
虽然,推荐做法是每次测试只断言一件事,但是,在实际工作中,我们可能需要对同一个对象同时执行多个断言。
例如,微软官方示例项目eShopOnContainers
有一个测试用例的实现代码如下:
[Fact]
public async Task Add_to_cart_success()
{//Arrangevar fakeCatalogItem = GetFakeCatalogItem();_basketServiceMock.Setup(x => x.AddItemToBasket(It.IsAny<ApplicationUser>(), It.IsAny<Int32>())).Returns(Task.FromResult(1));//Actvar orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);orderController.ControllerContext.HttpContext = _contextMock.Object;var actionResult = await orderController.AddToCart(fakeCatalogItem);//AssertAssert.Equal("Catalog", redirectToActionResult.ControllerName);Assert.Equal("Index", redirectToActionResult.ActionName);
}
如果我们将所有断言都改成必定失败:
//Assert
Assert.Equal("WrongCatalog", redirectToActionResult.ControllerName);
Assert.Equal("WrongIndex", redirectToActionResult.ActionName);
但运行测试时,只会提示第一个失败的断言:
在这种情况下,一个个断言去验证并修正比较耗时。
如果同时能够看到所有失败的断言则更加有帮助。
手工实现
查看`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);elsethrow new EqualException(expected, actual);}}if (!comparer.Equals(expected, actual))throw new EqualException(expected, actual);
}
而所有断言异常都继承自基类XunitException
:
因此,我们可以捕获每个断言的异常,然后将多个异常添加到集合中,在测试结束时再抛出:
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());
}
这里虽然列出了所有失败断言,但是所有错误显示了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");
}
结论
有时,将多个断言组合在一起测试是有意义的。
在这种情况下,可以使用FluentAssertions的AssertionScope
来编写此类测试。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“
参考资料
[1]
Assert.Equal
的实现代码: https://github.com/xunit/assert.xunit/blob/main/EqualityAsserts.cs#L78