别找借口,写单元测试
无论使用哪种编程语言,单元测试有时都会比较棘手。原因有以下几点:
- 人们担心单元测试会占用团队没有的时间。
- 你的团队无法就可接受的测试覆盖率达成一致,或者陷入无休止的争论*
- 人们在修改代码时,如果测试失败,会感到非常沮丧。
首先,我们花点时间理解一下单元测试的含义。单元可以是任何可以隔离并独立执行的代码块。它可以是一个函数,甚至是一组函数,尽管后者由于涉及的组件较多,测试起来会更加困难。
如果一个函数总是产生相同的输出,也就是说,当给定相同的输入(参数)时,它从函数内部返回相同的内容,那么这个函数就很容易测试。
它非常适合测试,因为我们可以根据这些返回值做出假设并设定预期。其理念是,当测试通过时,无论函数是如何得到该结果的,它仍然满足断言中的要求。
一个简单的测试示例:
import { it } from 'mocha';
import { expect } from 'chai';
/**
* Add numbers together
*
* @param {int} numbers One or many numbers to add
*/
const add = (...numbers) => {
return numbers.reduce((acc, val) => {
return acc + val;
}, 0);
};
it('should add numbers', () => {
const expected = 15;
const actual = add(1, 2, 3, 4, 5);
expect(actual).to.equal(expected); // true
});
/**
* Subtract numbers from an initial number
*
* @param {int} initialNumber The number we start from when subtracting
* @param {int} numbers One or many numbers to subtract
*/
const minus = (initialNumber, ...numbers) => {
return numbers.reduce((acc, val) => {
return acc - val;
}, initialNumber);
};
it('should minus numbers', () => {
const expected = 5;
const actual = minus(15, 5, 3, 2);
expect(actual).to.equal(expected); // true
});
您可以根据需要对这些测试进行深入探索。如果需要,我们可以添加一些测试,例如当加法和减法函数接收到非数字值时会发生什么情况,是否需要处理负数?
即使对于最简单的函数,添加测试也能提供更多信息,例如:
- 函数的使用难度(参数数量、通过函数名称理解输出结果)
- 该功能在自然环境中存在并被其他开发者使用时可能存在的潜在风险
- 函数是否功能过多,可能是因为需要模拟世界才能运行,也可能是因为每个函数断言的内容过多。
编写测试有很多好处,而不编写测试则会损失很多。
你还有时间进行单元测试
对代码进行单元测试前期需要花费一些额外的时间,因为你当然需要编写额外的代码——测试代码。
然后,在你编写完这些测试代码后,几周甚至几个月过去了,当你修改了某个已经测试过的函数时,测试就失败了。糟糕!现在你不得不去修复这个测试。
我听说有人抱怨修复有问题的测试用例很困难、耗时,甚至浪费时间。我的回应是:你更愿意在哪里修复这个 bug?是希望在生产环境中修复,让用户因为功能故障而愤怒,还是希望在单元测试中修复,从而延长任务完成时间?
如果你修改了一个 API,就应该出现问题。如果测试没有失败,而修改后的代码又上线了,那么所有使用这段代码的地方现在都出问题了,你就会遇到 99 个问题,但幸运的是,测试不在其中。
我告诉你,大多数团队根本没时间修复生产环境中的 bug,但他们总能挤出时间来做这件事。从经理到开发人员,每个人都知道修复生产环境中出现的 bug 很重要,但我们总是等到 bug 上线后才去修复它们。
在我看来,我们或许可以把修复 bug 的时间提前,把更多精力放在提升代码清晰度上,这样才能更好地理解代码。修复 bug 时,一半的时间都花在了弄清楚 bug 到底是怎么发生的。如果有单元测试,它会在你修改代码并运行测试后立即告诉你问题所在。
编写测试用例的次数越多,就越容易。你会发现,一段时间后,你编写的代码很容易进行测试,因为你在编写代码的同时就已经在思考如何测试它了!想想看!
赶紧把该死的测试写出来
工程师以过度设计而闻名。我们习惯于抽象思考,而且对于原本应该很简单的解决方案,我们往往会想得太多。最难的是意识到自己可能做得太过火了。
通常,当出现新事物时,我们只想到最佳解决方案,却未能以有效的方式解决核心问题。
制定团队编码最佳实践非常重要,包括测试覆盖率、测试内容和测试方法等等。但阻止团队尝试和从错误中学习则是不可取的。
别让它阻碍你编写该死的测试。对于任何新软件来说,一条好的经验法则是:
先让它能运行,然后再把它改正。
这条规则可以以多种方式应用于单元测试,但我发现最有用的方法是先编写代码使程序运行,最好是编写一些小函数,然后再为其编写测试。
现在你已经有了一个经过测试的函数,修改该函数的内部代码,看看测试是否仍然通过。
简而言之——编写、测试、重构。
处理失败的测试
当你编写测试一段时间后,你会发现越来越多的改动会导致现有测试失效。这其实是件好事。千万不要低估失效测试的威力。
首先,这迫使破坏测试的开发人员更深入地了解一段代码的运行方式,例如,根据测试的完善程度,预期会有哪些输入和输出。
其次,它迫使任何 API 变更都必须经过深思熟虑,并且根据变更的大小,可能需要团队进行讨论。
第三,也是最重要的一点,是你自己在终端上发现了这个问题,而不是当客户尝试做某事时才发现的。
就像任何事情一样,测试也不能过度。至于单元测试的深度,则取决于具体的应用程序。
以我的经验来看,我觉得没有充分的理由不进行单元测试。在一些预期场景下运行代码,看看会发生什么。
就像你部署应用程序后,开始点击按钮,与应用程序进行交互一样。
你不可能部署完应用程序就把它忘得一干二净!
你会吗?今天就开始单元测试吧。从小处着手,逐步深入。
*“自行车棚效应”指的是把时间花在解决相对不重要的问题上,而应该先解决更大的问题,然后再处理次要的细节。
文章来源:https://dev.to/jackmarchant/no-excuses-write-unit-tests