原文:
5 Questions Every Unit Test Must Answer
每一个开发者都知道咱们应该编写单元测试以防程序上到生产环境才检测到错误。javascript
大多数开发者都不知道编写单元测试的基本要素。我没法开始计算我看过单元测试失败的次数,只有通过调查我才发现我彻底不知道开发人员试图测试哪些功能,更不用说它出了什么问题或者为何它很重要。java
在最近的一个项目中,咱们让一大堆单元测试进入测试单元,彻底没有描述测试目的。咱们有一只很棒的团队,因此我放松了警戒。结果?咱们仍然有大量的单元测试,只有做者才能真正理解。git
幸运的是,咱们正在从新设计API,咱们将把整个测试单元从新开始,不然,这将成为个人修复列表中的第一优先级任务。github
不要让这发生在你身上。并发
你的测试是你抵御软件缺陷的第一道防线。你的测试比linting和静态分析(它只能找到一个错误的子类,而不是你的实际程序逻辑的问题)更重要。测试与实现自己一样重要。模块化
单元测试结合了许多功能,使它们成为应用程序成功的秘密武器。函数
单元测试不须要曲解操纵来知足全部这些普遍的目标。相反,单元测试的本质是知足全部这些需求。这些好处都是一个精心编写的并有良好覆盖率的测试单元的反作用。单元测试
证据代表测试
来自微软研究院,IBM和Springer的研究测试了,先编写测试和后编写测试的效果,并始终发现,测试优先的流程比稍后添加测试能产生更好的效果。它很是明确:在编写代码以前,先编写测试
怎样才能编写一个良好的单元测试?
咱们将从一个真正的项目看一个很是简单的例子来探索这个过程:来自Stamp Specification的compose()
函数。.net
咱们将使用tape进行单元测试,由于它足够清晰简单。
在咱们可以回答编写好的单元测试以前,首先咱们必须了解如何使用单元测试:
当测试失败,那个测试失败报告一般是您确切发现错误的第一个也是作好的线索-快速追踪根本缘由的秘密是知道从哪里开始寻找。当你有一个很是清晰的错误报告时,这个过程变得更容易。
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
是你的新的默认的断言。它是每一个优秀测试单元的主要组成部分。全部拥有数百种花哨不一样断言的断言库正在破坏你的测试质量。
但愿在编写单元测试时更好?下一周,尝试使用equal
或deepEqual
来编写每个断言,或者在你的断言库中有差很少的选择。不要担忧它的质量会影响你的单元测试。个人收入告诉你这种训练会显著的提升你的单元测试。
下面的代码看起来像什么?
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';
你能够构建一个断言,而不用专门赋值给名为actual
和expected
的变量,可是我最近开始在每一个测试中专门为变量actual
和expected
赋值,并发现它使个人测试更易于阅读。
看这个它是如何使的这个断言清晰的?
assert.equal(actual, expected, 'compose() should returns a function'; )
它在测试代码中,分离了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(); });
还有不少关于单元测试的使用案例,但知道如何编写一个好的测试还有很长的路要走。