[译]每一个单元测试必须回答的5个问题

原文:
5 Questions Every Unit Test Must Answer

每一个开发者都知道咱们应该编写单元测试以防程序上到生产环境才检测到错误。javascript

大多数开发者都不知道编写单元测试的基本要素。我没法开始计算我看过单元测试失败的次数,只有通过调查我才发现我彻底不知道开发人员试图测试哪些功能,更不用说它出了什么问题或者为何它很重要。java

在最近的一个项目中,咱们让一大堆单元测试进入测试单元,彻底没有描述测试目的。咱们有一只很棒的团队,因此我放松了警戒。结果?咱们仍然有大量的单元测试,只有做者才能真正理解。git

幸运的是,咱们正在从新设计API,咱们将把整个测试单元从新开始,不然,这将成为个人修复列表中的第一优先级任务。github

不要让这发生在你身上。并发

是什么影响了测试的规则?

你的测试是你抵御软件缺陷的第一道防线。你的测试比linting和静态分析(它只能找到一个错误的子类,而不是你的实际程序逻辑的问题)更重要。测试与实现自己一样重要。模块化

单元测试结合了许多功能,使它们成为应用程序成功的秘密武器。函数

  1. 设计帮助: 首先编写测试可让您更清楚地了解理想的API设计
  2. 功能文档(针对开发人员): 每一个实现的功能需求都包含在代码中的测试说明。
  3. 测试开发人员的理解程度: 开发人员是否对这个问题理解到位,从代码中全部关键部分的阐述便能看出。
  4. 质量保证: 手动QA容易出错。根据个人经验,在更改、添加或删除功能后,开发人员不可能记住全部须要测试的功能。
  5. 持续交付: 自动化QA提供了自动防止部署到生产环境中破损构建的机会。

单元测试不须要曲解操纵来知足全部这些普遍的目标。相反,单元测试的本质是知足全部这些需求。这些好处都是一个精心编写的并有良好覆盖率的测试单元的反作用。单元测试

TDD科学(Test-driven development)

证据代表测试

  • TDD能够减小bug的密度
  • TDD能够鼓励更多的模块化设计(加强软件敏捷性/团队开发速度)
  • TDD能够下降代码的复杂度

先写测试

来自微软研究院,IBM和Springer的研究测试了,先编写测试和后编写测试的效果,并始终发现,测试优先的流程比稍后添加测试能产生更好的效果。它很是明确:在编写代码以前,先编写测试

什么是良好的单元测试?

怎样才能编写一个良好的单元测试?

咱们将从一个真正的项目看一个很是简单的例子来探索这个过程:来自Stamp Specificationcompose()函数。.net

咱们将使用tape进行单元测试,由于它足够清晰简单。

在咱们可以回答编写好的单元测试以前,首先咱们必须了解如何使用单元测试:

  • 设计帮助: 在编写代码以前的设计阶段编写
  • 功能文档&测试开发者的理解程度: 测试应该提供被测功能的清晰描述
  • 质量保证/持续交付: 测试应当在发生故障时中止交付管道,并在失败时产生一份好的错误报告。

将单元测试做为测试报告

当测试失败,那个测试失败报告一般是您确切发现错误的第一个也是作好的线索-快速追踪根本缘由的秘密是知道从哪里开始寻找。当你有一个很是清晰的错误报告时,这个过程变得更容易。

什么是好的测试失败报告

  1. 你在测试什么?
  2. 它应该作什么?
  3. 输出是什么(实际行为)?
  4. 指望输出是什么(指望行为)?

测试报告

从回答"你在测试什么?"开始

  • 你在测试组件的什么方面?
  • 该功能应该作什么?你正在测试什么特定的行为要求?

compose()函数使用任意数量的邮票(可组合的工厂函数)并生成一个新邮票。
为了编写这个测试,咱们将从任何单个测试的最终目标开始向后工做:测试特定的行为要求。为了经过这个测试,代码必须产生什么样的特定行为?

该功能应该作什么?

我喜欢从写一个字符串开始。没有分配给任何东西。没有传入任何函数。只是清楚地记关注组件必须知足的特定要求。在这种状况下,咱们将从compose()函数应该返回一个函数的事实开始。

一个简单的,可测试的要求

'compose() should return a function.'

如今咱们将跳过一些东西,并充实剩下的测试。这个字符串是咱们的目标。事先陈述有助于咱们保持对最终结果的关注。

你正在测试什么组件的那一方面?

组件各个方面的含义因测试而异,具体取决于为测试组件提供足够覆盖度所需的粒度。

在上面的例子中,咱们将测试compose()函数的返回类型,以因确保它返回正确的类型,而不是在运行时抛出'undefined'或什么都不抛出。

让咱们将这个问题转换为测试代码。答案进入测试描述。这一步也是咱们进行函数调用并将回调函数传递给测试运行器时,它在测试运行时会调用回调函数的地方。

test('<What component aspect are we testing?>', assert => {});

在这个例子中,咱们将测试compose函数的输出

test('Compose function output type.', assert => {  
})

咱们固然也须要咱们最开始的描述。它将放进咱们的回调函数:

test('Compose function output type', assert => {
  'compose() should return a function.'
})

输出是什么(指望的或实际的)?

equal()是我最喜欢的断言。若是每一个测试单元惟一可能的断言是equal(),那么世界上几乎全部的测试单元都会更好。为何?

由于equal(),天然的回答每一个测试单元都回答的两个重要问题,可是大多数没有:

  • 什么是实际的输出?
  • 什么是指望的输出?

若是你完成了测试但没有回答这两个问题,你并无在进行一个真正的单元测试。你只是有一个马虎的,不成熟的测试。

若是你从这篇文章中只看出一件事,那这件事就是: Equal是你的新的默认的断言。它是每一个优秀测试单元的主要组成部分。全部拥有数百种花哨不一样断言的断言库正在破坏你的测试质量。

一个挑战

但愿在编写单元测试时更好?下一周,尝试使用equaldeepEqual来编写每个断言,或者在你的断言库中有差很少的选择。不要担忧它的质量会影响你的单元测试。个人收入告诉你这种训练会显著的提升你的单元测试。

下面的代码看起来像什么?

const actual = '<what is the actual output?>';
const expected = '<what is the expected output?>';

第一个问题确实是在测试失败中承担双重责任。经过回答这个问题,你的代码也能够回答另一个问题:

const actual = '<how is the test reproduced>';

须要注意的很重要的点是,actual值必须在使用组件公共API时被生产出来。不然,测试毫无心义。

让咱们回到例子:

const actual = typeof compose();
const expected = 'function';

你能够构建一个断言,而不用专门赋值给名为actualexpected的变量,可是我最近开始在每一个测试中专门为变量actualexpected赋值,并发现它使个人测试更易于阅读。

看这个它是如何使的这个断言清晰的?

assert.equal(actual, expected, 
  'compose() should returns a function';
)

它在测试代码中,分离了how和what

  • how:即指咱们是如何得到这个值的?看看变量赋值。
  • what:即指咱们正在测试什么?看看断言描述吧。

阅读测试的结果应该和阅读一个高质量的bug报告同样。

咱们来看看上下文中的全部内容:

import test from 'tape';
import compose from '../source/compose';

test('Compose function output type', assert => {
  const actual = typeof compose();
  const expected = 'function';

  assert.equal(actual, expected, 'compose() should return a function');

  assert.end();
})

下一次,你编写测试代码,记得回答下面全部的问题:

  • 你在测试什么?
  • 它应该作什么?
  • 实际的输出是什么?
  • 指望的输出是什么?
  • 如何再现测试?

最后的问题由用于导出actual值得代码回答。

一个单元测试模板

import test from 'tape';

// For each unit test your write,
// answser these questions

test('What component aspect are you testing?', assert => {
  const actual = 'What is the actual output?';
  const expected = 'What is the expected output?';

  assert.equal(actual, expected, 
  'what should the feature do?');

  assert.end();
});

还有不少关于单元测试的使用案例,但知道如何编写一个好的测试还有很长的路要走。

相关文章
相关标签/搜索