[译]重新思考单元测试断言

原文地址:https://medium.com/javascript-scene/rethinking-unit-test-assertions-55f59358253f

作者:Eric Elliott

「断言」是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。

每当测试失败的时候,靠谱的自动化测试总能生成一份优秀的错误报告(bug report),但是很少有开发者花时间去思考一个好的错误报告需要哪些信息。

在此之前,我已经详细地叙述过 每个单元测试必须回答的 5 个问题 ,所以这次我们将它们一笔带过。

  1. 被测单元是什么(模块,函数,类,等等)?
  2. 它将做什么?
  3. 实际输出是什么?
  4. 期望的输出是什么?
  5. 如何将失败重现?

许多测试框架允许你忽略这些问题中的一个或者多个,这会导致错误报告并不实用。

让我们看一下使用一个虚拟测试框架的示例,该框架提供常用的 pass() 以及 fail() 断言。

describe(‘addEntity()‘, async ({ pass, fail }) => {
  const myEntity  = { id: ‘baz‘, foo: ‘bar‘ };
  try {
    const response = await addEntity(myEntity);
    const storedEntity = await getEntity(response.id);
    pass(‘should add the new entity‘);
  } catch(err) {
    fail(‘failed to add and read entity‘, { myEntity, error });
  }
});

我们走在正确的轨道上,但是我们遗漏了一些信息。让我们尝试使用此测试中提供的数据回答 5 个问题:

  1. 被测单元是什么? addEntity()
  2. 它将做什么? should add the new entity
  3. 实际输出是什么? 哎呀,我们不知道。我们没有将这些数据提供给测试框架。
  4. 期望的输出是什么? 我们再一次的不知道。我们这里没有测试返回值。相反,我们假设它不抛出,一切都按照预期运行——但是如果没有呢?如果函数返回一个值或者是 promise ,我们应该测试结果值。
  5. 如何将失败重现? 我们可以在测试设置中看到这一点,但我们可以更明确地说明这一点。例如,对你输入的东西进行简单的描述以便让我们更好地理解测试用例的意图。

满分为 5 分的情况下,我的得分为 2.5 分。这项测试没有完成它应尽的职责。显然没有回答每个单元测试必须回答的 5 个问题。

大多数测试框架的问题在于它们的功能太过强大,你可以轻松地使用它们提供的各种 “方便(convenient)” 断言,以至于忘记了在测试失败时实现测试的最大价值。

在失败阶段,编写测试问题让我们更加容易弄清楚出了什么问题。

每个单元测试必须回答的 5 个问题 ,我这样写道:

equal() 是我最喜欢的断言。如果每个测试套件中唯一可用的断言是 equal(),那么世界上几乎所有的测试套件都会更好。

自从我写这篇文章以来的几年里,我一直坚持着我的这一信念。虽然测试框架忙于添加更多 “方便” 断言,但我却在 Tape(译者注:一个开源测试框架) 上进行了一层简单的封装,使它只暴露了一个深度的相等断言。换句话说,我最低程度地使用了 Tape 库,并删除了一些功能,以提高测试体验。

在 RITE Way 测试原则的影响下,我将封装库称为 RITEway。RITE Way 测试应该是这样的:

  • 可读( Readable )
  • 隔离( Isolated )(用于单元测试)或集成( Integrated )(用于功能或集成测试,测试应该隔离并且集成组件 / 模块)
  • 彻底( Thorough )
  • 明确( Explicit )

RITEway 强制你编写可读,隔离以及彻底的测试,因为这是你使用 API 唯一的方法。由于编写测试断言是如此简单,以至于你将沉迷于编写测试,这使得你更容易进行彻底的测试。

这是 RITEway 中 assert() 的 函数签名:

assert({
  given: Any,
  should: String,
  actual: Any,
  expected: Any
}) => Void

断言必须位于一个 describe() 块中,它的第一个参数将作为单元测试的一个标签。完整的测试如下:

describe(‘sum()‘, async assert => {
  assert({
    given: ‘no arguments‘,
    should: ‘return 0‘,
    actual: sum(),
    expected: 0
  });
});

它的运行结果如下所示:

TAP version 13
# sum()
ok 1 Given no arguments: should return 0

让我们再看看上面的 2.5 分的测试,看看我们能否提高我们的分数:

describe(‘addEntity()‘, async assert => {
  const myEntity  = { id: ‘baz‘, foo: ‘bar‘ };
  const given =  ‘an entity‘;
  const should = ‘read the same entity from the api‘;
  try {
    const response = await addEntity(myEntity);
    const storedEntity = await getEntity(response.id);
    assert({
      given,
      should,
      actual: storedEntity,
      expected: myEntity
    });
  } catch(error) {
    assert({
      given,
      should,
      actual: error,
      expected: myEntity
    });
  }
});
  1. 被测单元是什么? addEntity()
  2. 它将做什么? given an entity: should read the same entity from the api
  3. 实际输出是什么? { id: ‘baz‘, foo: ‘bar‘ }
  4. 期望的输出是什么? { id: ‘baz‘, foo: ‘bar‘ }
  5. 如何将失败重现? 现在,消息中更明确地说明了如何重现测试:提供 given 以及描述。

很好!现在我们通过了测试的测试。

一个深度相等断言已经足够了吗?

在过去的一年半中的几个大型项目中,我几乎每天都使用 RITEway。通过界面的简单化,我们将其提升了一些,但是我从来没有想过另外的断言,我们的测试套件是我在整个职业生涯中见过的最简单,最易读的测试套件。

我认为是时候与世界其他地方分享这项创新了。如果你想开始使用 RITEway

npm install --save-dev riteway

它会改变你对测试软件的看法。

简而言之:

测试越简单越好(Simple tests are better tests)

附:我在本文中一直使用 “单元测试” 这个术语,这仅仅是因为它比 “自动化软件测试” 或 “单元测试、功能测试以及集成测试” 更容易写,但是我在本文中所说的关于单元测试的所有内容都适用于我能想到的每个自动化软件测试。我也喜欢这些比 Cucumber/Gherkin 更好的测试。

下一步

EricElliottJS.com 的会员可以获得有关测试驱动开发的视频课程,如果你还不是会员,请立即前往 注册

Eric Elliott“Programming JavaScript Applications”O’Reilly)的作者,也是软件导师平台 DevAnywhere.io 的创始人。 他为 Adobe Systems,Zumba Fitness,华尔街日报,ESPN,BBC 以及包括 Usher,Frank Ocean,Metallica 等在内的顶级录音软件的用户体验做出了杰出贡献。

原文地址:https://www.cnblogs.com/karthuslorin/p/10206055.html

时间: 2024-08-27 10:53:02

[译]重新思考单元测试断言的相关文章

cakephp 单元测试断言方法总结

前言 cakephp 的单元测试 参考http://book.cakephp.org/2.0/en/development/testing.html 以下是CakeTestCase类的断言,也就是cakephp 定义的断言,实际使用中还可以使用CakeTestCase的父类 PHPUnit_Framework_TestCase里面的断言 1.assertEqual 是否相等,测试期望的数据和结果是否相等 2.assertNotEqual 是否不相等 3.assertPattern 是否符合正则匹

单元测试断言利器 AssertJ

前言 由于JUnit的Assert是公认的烂API,所以不推荐使用,目前推荐使用的是AssertJ. AssertJ网站: http://joel-costigliola.github.io/assertj/ github上examples 地址 https://github.com/joel-costigliola/assertj-examples 附件中assertj-examples-octo-assertj-core-1.5.0是官方examples 整理一些常用的例子如下 对字符串断言

单元测试断言汇总

XCTFail(...) 任何尝试都会测试失败,...是输出的提示文字.(后面都是这样) XCTAssertNil(expression, ...) expression为空时通过,否则测试失败. expression接受id类型的参数. XCTAssertNotNil(expression, ...) expression不为空时通过,否则测试失败. expression接受id类型的参数. XCTAssert(expression, ...) expression为true时通过,否则测试失

.NET单元测试的艺术-3.测试代码

开篇:上一篇我们学习单元测试和核心技术:存根.模拟对象和隔离框架,它们是我们进行高质量单元测试的技术基础.本篇会集中在管理和组织单元测试的技术,以及如何确保在真实项目中进行高质量的单元测试. 系列目录: 1.入门 2.核心技术 3.测试代码 一.测试层次和组织 1.1 测试项目的两种目录结构 (1)集成测试和单元测试在同一个项目里,但放在不同的目录和命名空间里.基础类放在单独的文件夹里. (2)集成测试和单元测试位于不同的项目中,有不同的命名空间. 实践中推荐使用第二种目录结构,因为如果我们不把

.NET单元测试的艺术-1.入门

开篇:最近在看Roy Osherove的<单元测试的艺术>一书,颇有收获.因此,将其记录下来,并分为四个部分分享成文,与各位Share.本篇作为入门,介绍了单元测试的基础知识,例如:如何使用一个测试框架,基本的自动化测试属性等等,还有对应的三种测试类型.相信你可以对编写单元测试从一无所知到及格水平,这也是原书作者的目标. 系列目录: 1.入门 2.核心技术 3.测试代码 4.设计和流程 一.单元测试基础 1.1 什么是单元测试 一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后

.NET单元测试的艺术-2.核心技术

开篇:上一篇我们学习基本的单元测试基础知识和入门实例.但是,如果我们要测试的方法依赖于一个外部资源,如文件系统.数据库.Web服务或者其他难以控制的东西,那又该如何编写测试呢?为了解决这些问题,我们需要创建测试存根.伪对象及模拟对象.这一篇中我们会开始接触这些核心技术,借助存根破除依赖,使用模拟对象进行交互测试,使用隔离框架支持适应未来和可用性的功能. 系列目录: 1.入门 2.核心技术 3.测试代码 4.设计和流程 一.破除依赖-存根 1.1 为何使用存根? 当我们要测试的对象依赖另一个你无法

NET单元测试的艺术

NET单元测试的艺术 开篇:上一篇我们学习基本的单元测试基础知识和入门实例.但是,如果我们要测试的方法依赖于一个外部资源,如文件系统.数据库.Web服务或者其他难以控制的东西,那又该如何编写测试呢?为了解决这些问题,我们需要创建测试存根.伪对象及模拟对象.这一篇中我们会开始接触这些核心技术,借助存根破除依赖,使用模拟对象进行交互测试,使用隔离框架支持适应未来和可用性的功能. 系列目录: 1.入门 2.核心技术 3.测试代码 4.设计和流程 一.破除依赖-存根 1.1 为何使用存根? 当我们要测试

在Visual Studio2015中使用单元测试

所谓的单元测试(Unit Test),就是对软件的一些模块进行测试以检查其正确性和可靠性,这些模块可以是一个类或者是一个方法等.在Visual studio中,这十分容易实现. 打开Visual studio,文件->新建->项目,在此演示的是为通用Windows平台创建单元测试,因此展开Visual C#->Windows->通用->单元测试应用(通用Windows).如图, 这里单元测试的项目名为UnitTest4Demo 创建完成后,为了使条理清晰,我们在解决方案中新建

MapReduce的C#实现及单元测试(试验)

MapReduce.cs类文件代码  MapReduce的执行方法 using System; using System.Collections.Generic; //using System.Linq; //using System.Threading.Tasks; namespace MapReduce { /// <summary> /// Very Simple MapReduce implementation in C# /// </summary> /// <re