经典断言与流畅断言概述
学习断言的基本原理以及区分其两种类型的因素。
简介
单元测试的目标是编写全面的测试用例,并确保所有测试用例都能通过。为了保证应用程序代码的行为符合预期,我们使用断言。断言是一种将实际值与期望值进行比较的语句。比较的结果可能是通过、失败或忽略。
断言的输入参数
大多数断言都支持多种参数形式。但最常见的两个参数是期望值和实际值。期望参数是决定断言是否通过的值。实际值则是测试用例中调用被测代码后得到的结果。从正式定义上看,实际值是作用于准备好的数据后的结果。有些断言仅接受实际参数,而期望值在断言语句中已内定,尽管这种情况较为少见。
断言结果与测试用例结果的关系
如果一个测试用例中的所有断言都返回 true,则该测试用例通过;如果有一个或多个断言返回 false,则整个测试用例失败。
两种类型的断言
NUnit 提供了两种断言模型——经典断言和约束断言。两者的主要区别如下:
-
经典断言:之所以称为“经典”,是因为在 NUnit 3 版本之前,这是唯一的断言模型。经典断言使用独立的方法来表达断言,而不是通过链式调用多个组件方法。
-
约束断言:在约束断言中,断言通过组合多个组件并以链式方式构建。由于这些组件是串联的,其结构类似于自然语言的句子,因此也被称为流畅断言。
约束断言相比经典断言的优势
约束断言的主要优势在于:断言语句更贴近自然语言的表达方式。第二个优势是,当约束断言失败时,仅通过查看测试探索器的输出就能更容易地判断失败原因。以下示例展示了这些优势。
经典断言模型示例
假设我们希望检查一个列表是否恰好包含一个值为 8 的元素。在经典断言模型中,可以这样写:
1[Test] 2public void classic_assertion_model() 3{ 4 List<int> myList = new List<int> { 2, 4, 6 }; 5 Assert.AreEqual(1, myList.Where(x => x == 8).Count()); 6}
运行此测试的输出如下:
1Expected: 1 2But was: 0
约束断言模型示例
现在我们仍希望检查列表是否包含值 8。在约束断言模型中,可以这样写:
1[Test] 2public void constraint_assertion_model() 3{ 4 List<int> myList = new List<int> { 2, 4, 6 }; 5 Assert.That(myList, Has.Exactly(1).EqualTo(8)); 6}
运行此测试的输出如下:
1Expected: exactly one item equal to 8 2But was: < 2, 4, 6 >
从这个例子可以看出,约束断言不仅读起来更自然流畅,而且在断言失败时也更容易理解失败的原因。
为失败的断言添加说明信息
我们应尽量在断言中加入描述性消息。当断言失败时,这些消息会作为测试输出的一部分显示出来。
1[Test] 2public void withdraw_money() 3{ 4 // Arrange 5 BankAccount account = new BankAccount(1000); 6 // Act 7 account.Withdraw(300); 8 // Assert 9 Assert.AreEqual(700, account.Balance, "The balance after withdrawing money is incorrect"); 10}
如果断言失败,测试运行的输出如下:
1The balance after withdrawing money is incorrect 2Expected: 700 3But was: XXX
注意:XXX 是与 700 不相等的实际结果。
从上述示例可以看出,当测试失败时,附加的消息能为失败的测试提供上下文信息,从而加快修复应用代码的速度。
代码演示环境
下方的代码区域包含了上述所有代码片段的内容。运行以下测试,以验证代码片段的执行结果:
No configuration found for ID: cb86d400bedfe15e153a3469ad6c0123bc613ed2c29cc10d59619f3f0df0d2d2
总结
断言是任何单元测试项目的核心。它们可以采用经典模型或约束模型。两种模型均可使用。建议在断言中加入消息,以便在断言失败时提升测试代码的可读性和文档性。