前言

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

例如,微软官方示例项目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

如何在单个测试中同时执行多个断言相关推荐

  1. 实操教程:CANoe在CAN总线测试中的应用

    随着新能源.智能网联等概念发展,车载CAN总线环境变得复杂且紊乱,CAN节点质量不稳定会给主机厂带来极大威胁,所以CAN总线测试已成为保证CAN网络安全运行的重要手段. 而CANoe作为一款支持多种总 ...

  2. junit 参数化测试用例_JUnit:在参数化测试中命名单个测试用例

    junit 参数化测试用例 几年前,我写了有关JUnit参数化测试的文章 . 我不喜欢它们的一件事是JUnit使用数字命名了单个测试用例,因此,如果它们失败,您将不知道是哪个测试参数导致了失败. 以下 ...

  3. JUnit:在参数化测试中命名单个测试用例

    几年前,我写了有关JUnit参数化测试的文章 . 我不喜欢它们的一件事是JUnit使用数字命名了单个测试用例,因此,如果它们失败,您将不知道是哪个测试参数导致了失败. 以下Eclipse屏幕快照将向您 ...

  4. Unity脚本在层级面板中的执行顺序测试3

    断断续续的写了3篇,以后有时间可以做成一个系列了 前面2篇测试了GameObject的顺序,以及Awake和OnEnable的时机: Unity脚本在层级面板中的执行顺序测试1 http://www. ...

  5. 【Golang】解决Go test执行单个测试文件提示未定义问题

    [Golang]解决Go test执行单个测试文件提示未定义问题 参考文章: (1)[Golang]解决Go test执行单个测试文件提示未定义问题 (2)https://www.cnblogs.co ...

  6. PHP cURL可以在单个请求中检索响应标头和正文吗?

    本文翻译自:Can PHP cURL retrieve response headers AND body in a single request? Is there any way to get b ...

  7. [转]Web测试中的界面测试用例设计

    一.文本框.按钮等控件测试 1.文本框的测试 如何对文本框进行测试: a.输入正常的字母或数字: b.输入已存在的文件的名称: c.输入超长字符.例如在"名称"框中输入超过允许边界 ...

  8. junit 测试目录_JUnit 5测试中的临时目录

    junit 测试目录 JUnit 4 TemporaryFolder @Rule允许开发人员使用临时目录创建测试. 使用JUnit 5时,不支持@Rule因此测试文件和目录需要一点点额外的工作. 幸运 ...

  9. 在Spring Boot测试中使用Testcontainer进行数据库集成测试

    在此博客文章中,我想演示如何在Spring Boot测试中集成Testcontainer以便与数据库一起运行集成测试. 我没有使用Testcontainers的Spring Boot模块. 如何与他们 ...

最新文章

  1. python 累积正态分布函数_为什么机器学习都围绕正态分布进行讨论?
  2. 【项目管理】敏捷原则
  3. manjaro linux换源
  4. Linux文件夹含义
  5. 《应用时间序列分析:R软件陪同》——导读
  6. 排序算法复习—希尔排序
  7. 一次Maven依赖冲突采坑,把依赖调解、类加载彻底整明白了
  8. 基于Android的家庭财务管理流程图,SYL_Android_FamilyFinaceSystem 双鱼林基于安卓Android家庭财务管理系统毕业课程设计源码 - 下载 - 搜珍网...
  9. php+编辑器+显示html,thinkphp——通过在线编辑器添加的内容在模板里正确显示(只显示内容,而不是html代码)...
  10. Duplicate entry 'xxx' for key 'xxx'
  11. 服务器机房项目总结,机房建设项目总结报告.doc
  12. LeetCode26.删除排序数组中的重复项
  13. TeamTalk服务器编译与部署
  14. android腾讯云通讯注册,腾讯云通信IM SDK集成教程
  15. 点击不同按钮,eachart图显示不同数据,动态的控制echarts折线的条数
  16. CDH6.2.1安装Kudu maste无法启动 Unable to initialize catalog manager
  17. 7-zip安装与使用
  18. 干货 | 如何为Mist建立无服务器应用
  19. msde2000a安装被提示为了安全起见要求使用强SA密码解决方案
  20. 机器视觉运动控制一体机应用例程(三) 基于BLOB分析的多圆定位

热门文章

  1. 85、交换机安全MAC层***配置实验之Port-Security
  2. lsass.exe文件丢失,损坏
  3. Java编码规范,在您进行编码之前应该阅读的规范
  4. append()与extend()
  5. Linux Kickstart无人值守安装(上)
  6. MAD huashi
  7. Pyhton学习——Day25
  8. codevs原创抄袭题 5960 信使
  9. Linux系统文件与目录权限管理
  10. thinkphp与php共享session