认识自动化测试
自动化测试有以下几个概念:
- 单元测试
- 集成测试
- E2E 测试
- 快照测试
- 测试覆盖率
- TDD 以及 BDD 等
简述
项目开发过程中会有几个经历。
- 版本发布上线之前,会有好几个小时甚至是更长时间对应用进行测试,这个过程非常枯燥而痛苦
- 代码的复杂度达到了一定的级别,当维护者的数量不止一个人,你应该会逐渐察觉到你在开发新功能或修复 bug 的时候,会变得越发小心翼翼,即使代码看起来没什么问题,但你心里还是会犯嘀咕:会不会引起其他的bug。
- 对项目中的代码进行重构的时候,会花费大量的时间进行回归测试
这些问题都是由于大多数使用最基本的手动测试的方式所带来的问题,解决它可以引入自动化测试方式。
我们日常的开发中,代码的完工其实并不等于开发的完工。如果没有测试,不能保证代码能够正常运行。
如何进行应用程序测试?
- 手动测试:通过测试人员与应用程序的交互来检查其是否正常工作。
- 自动化测试:编写应用程序来替代人工检验。
手动测试
开发者都懂得手动测试代码。在编写完源代码之后,下一步理所当然就是去手动测试它。
手动测试的优势在于足够简单灵活,但是缺点也很明显:
- 手动不适合大型项目
- 忘记测试某项功能
- 大部分时间都在做回归测试
虽然有一部分手动测试时间是花在测试新特性上,但是大部分时间还是用来检查之前的特性是否仍正常工作。这种测试被称为回归测试。回归测试对人来说是非常困难的任务————它们是重复性的,要求投入很多注意力,而且没有创造性的输入。总之,这种测试太枯燥了。幸运的是,计算机特别擅长此类工作,这也是自动化测试可以大展身手的地方!
自动化测试
自动化测试是利用计算机程序检查软件是否运行正常的测试方法。换句话说,就是用其他额外的代码检查被测软件的代码。当测试代码编写完之后,就可以不费吹灰之力地进行无数次重复测试。
可使用多种不同的方法来编写自动化测试脚本:
- 可以编写通过浏览器自动执行的程序
- 可以直接调用源代码里的函数
- 也可以直接对比程序渲染之后的截图
每一种方法的优势各不相同,但它们有一大共同点:相比手动测试而言节省了大量时间以及提高了程序的稳定性。
自动化测试还有很多优点,比如:
- 尽早的发现程序的 bug 和不足
- 增强程序员对程序健壮性、稳定性的信心
- 改进设计
- 快速反馈,减少调试时间
- 促进重构
当然,自动化测试不可能保证一个程序是完全正确的,而且事实上,在实际开发过程中,编写自动化测试代码通常是开发者不太喜欢的一个环节。大多数情况下,前端开发者在开发完一项功能后,只是打开浏览器手动点击,查看效果是否正确,之后就很少对该块代码进行管理。造成这种情况的原因主要有两个:
- 一个是业务繁忙,没有时间进行测试的编写
- 另一个是该如何编写测试
测试类型
前端开发最常见的测试主要是以下几种:
- 单元测试:验证独立的单元是否正常工作
- 集成测试:验证多个单元协同工作
- 端到端测试:从用户角度以机器的方式在真实浏览器环境验证应用交互
- 快照测试:验证程序的 UI 变化
单元测试
单元测试是对应用程序最小的部分(单元)运行测试的过程。通常,测试的单元是函数,但在前端应用中,组件也是被测单元。
单元测试可以单独调用源代码中的函数并断言其行为是否正确。
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
// sum.test.js
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)
与端到端测试不同,单元测试运行速度很快,只需要几秒钟的运行时间,因此可以在每次代码变更后都运行单元测试,从而快速得到变更是否破坏现有功能的反馈。
单元测试应该避免依赖性问题,比如不存取数据库、不访问网络等等,而是使用工具虚拟出运行环境。这种虚拟使得测试成本最小化,不用花大力气搭建各种测试环境。
单元测试的优点:
- 提升代码质量,减少 bug
- 快速反馈,减少调试时间
- 让代码维护更容易
- 有助于代码的模块化设计
- 代码覆盖率高
单元测试的缺点:
- 由于单元测试是独立的,所以无法保证多个单元运行到一起是否正确
常见的 JavaScript 单元测试框架:
- Jest
- Mocha
- Jasmine
- Karma
- ava
- Tape
Mocha 跟 Jest 是用的较多的两个单元测试框架,基本上前端单元测试就在这两个库之间选了。总的来说就是 Jest 功能齐全,配置方便,Mocha 灵活自由,自由配置。
推荐使用Jest。
集成测试
定义集成测试的方式并不相同,尤其是对于前端。有些人认为在浏览器环境上运行的测试是集成测试;有些人认为对具有模块依赖性的单元进行的任何测试都是集成测试;也有些人认为任何完全渲染的组件测试都是集成测试。
优点:
- 由于是从用户使用角度出发,更容易获得软件使用过程中的正确性
- 集成测试相对于写了软件的说明文档
- 由于不关注底层代码实现细节,所以更有利于快速重构
- 相比单元测试,集成测试的开发速度要更快一些
缺点:
- 测试失败的时候无法快速定位问题
- 代码覆盖率较低
- 速度比单元测试要慢
端到端测试(E2E)
E2E(end to end)端到端测试是最直观可以理解的测试类型。在前端应用程序中,端到端测试可以从用户的视角通过浏览器自动检查应用程序是否正常工作。
想象一下,你正在编写一个计算器应用程序,并且你想测试两个数求和的运算方法是否正确。你可以编写一个端到端测试,打开浏览器,加载计算器应用程序,单击“1”按钮,单击加号“+”按钮,再次单击“1”按钮,单击等号“=”,最后检查屏幕是否显示正确结果“2”。
编写完一个端到端测试后,可以根据自己的需求随时运行它。想象一下,相比执行数百次同样的手动测试,这样一套测试代码可以节省多少时间!
优点:
- 真实的测试环境,更容易获得程序的信心
缺点:
- 首先,端到端测试运行不够快。启动浏览器需要占用几秒钟,网站响应速度又慢。通常一套端到端测试需要 30 分钟的运行时间。如果应用程序完全依赖于端到端测试,那么测试套件将需要数小时的运行时间。
- 端到端测试的另一个问题是调试起来比较困难。要调试端到端测试,需要打开浏览器并逐步完成用户操作以重现 bug。本地运行这个调试过程就已经够糟糕了,如果测试是在持续集成服务器上失败而不是本地计算机上失败,那么整个调试过程会变得更加糟糕。
一些流行的端到端测试框架:
快照测试
快照测试类似于“找不同”游戏。快照测试会给运行中的应用程序拍一张图片,并将其与以前保存的图片进行比较。如果图像不同,则测试失败。这种测试方法对确保应用程序代码变更后是否仍然可以正确渲染很有帮助。
传统快照测试是在浏览器中启动应用程序并获取渲染页面的屏幕截图。它们将新拍摄的屏幕截图与已保存的屏幕截图进行比较,如果存在差异则显示错误。这种快照测试在操作系统或浏览器存在版本间差异时,即使快照并没有改变,也会遇到测试失败问题。
使用 Jest 测试框架编写快照测试。取代传统对比屏幕截图的方式,Jest 快照测试可以对 JavaScript 中任何可序列化值进行对比。可以使用它们来比较前端组件的 DOM 输出。
应用场景:
- 开发纯函数库,建议写更多的单元测试 + 少量的集成测试
- 开发组件库,建议写更多的单元测试、为每个组件编写快照测试、写少量的集成测试 + 端到端测试
- 开发业务系统,建议写更多的集成测试、为工具类库、算法写单元测试、写少量的端到端测试
测试覆盖率
测试覆盖率是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据,有利于客观认识软件质量,正确了解测试状态,有效改进测试工作
度量测试覆盖率:
- 代码覆盖率
- 需求覆盖率
代码覆盖率
一种面向软件开发和实现的定义。它关注的是在执行测试用例时,有哪些软件代码被执行到了,有哪些软件代码没有被执行到。被执行的代码数量与代码总数量之间的比值,就是代码覆盖率。
根据代码粒度的不同,代码覆盖率可以进一步分为四个测量维度。它们形式各异,但本质是相同的。
- 行覆盖率(line coverage):是否每一行都执行了?
- 函数覆盖率(function coverage):是否每个函数都调用了?
- 分支覆盖率(branch coverage):是否每个if代码块都执行了?
- 语句覆盖率(statement coverage):是否每个语句都执行了?
如何度量代码覆盖率呢?一般可以通过第三方工具完成,比如 Jest 自带了测试覆盖率统计。
这些度量工具有个特点,那就是它们一般只适用于白盒测试,尤其是单元测试。对于黑盒测试(例如功能测试/系统测试)来说,度量它们的代码覆盖率则相对困难多了。
需求覆盖率
对于黑盒测试,例如功能测试/集成测试/系统测试等来说,测试用例通常是基于软件需求而不是软件实现所设计的。因此,度量这类测试完整性的手段一般是需求覆盖率,即测试所覆盖的需求数量与总需求数量的比值。视需求粒度的不同,需求覆盖率的具体表现也有不同。例如,系统测试针对的是比较粗的需求,而功能测试针对的是比较细的需求。当然,它们的本质是一致的。
如何度量需求覆盖率呢?通常没有现成的工具可以使用,而需要依赖人工计算,尤其是需要依赖人工去标记每个测试用例和需求之间的映射关系。
对于代码覆盖率来说,广为诟病的一点就是 100% 的代码覆盖率并不能说明代码就被完全覆盖没有遗漏了。因为代码的执行顺序和函数的参数值,都可能是千变万化的。一种情况被覆盖到,不代表所有情况被覆盖到。
对于需求覆盖率来说,100% 的覆盖率也不能说“万事大吉”。因为需求可能有遗漏或存在缺陷,测试用例与需求之间的映射关系,尤其是用例是否真正能够覆盖对应的测试需求,也可能是存在疑问的。
总结
适用于不同的场景,有各自的优势与不足。需要注意的是,它们不是互相排斥,而是相互补充的。
关于测试覆盖率,最重要的一点应该是迈出第一步,即有意识地去收集这种数据。没有覆盖率数据,测试工作会有点像在“黑灯瞎火”中走路。有了覆盖率数据,并持续监测,利用和改进这个数据,才是一条让测试工作越来越好的光明大道。
是不是所有代码都要有测试用例支持呢?
测试覆盖率还是要和测试成本结合起来,比如一个不会经常变的公共方法就尽可能的将测试覆盖率做到趋于 100%。而对于一个完整项目,前期先做最短的时间覆盖 80% 的测试用例,后期再慢慢完善。
经常做更改的活动页面我认为没必要必须趋近 100%,因为要不断的更改测试永用例,维护成本太高。
大多数情况下,将 100% 代码覆盖率作为目标并没有意义。
实现 100% 代码覆盖率不仅耗时,而且即使代码覆盖率达到 100%,测试也并非总能发现 bug。有时你可能还会做出错误的假设,当你调用一个 API 代码时,假定的是该 API 永远不会返回错误,然而当 API确实在生产环境中返回错误时,应用就崩溃了。
链接:https://juejin.cn/post/7257058135134568508
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。