自动化测试学习 - 自动化测试基础概念

概述

在开发过程中总会经历下面类似的问题:

  • 每次在版本发布上线之前,要花费好几个小时甚至是更长时间对你的应用进行测试,这个过程非常枯燥而痛苦。
  • 当代码的复杂度达到了一定的级别,当维护者的数量不止你一个,你应该会逐渐察觉到你在开发新功能或修复 bug 的时候,会变得越发小心翼翼,即使代码看起来没什么问题,但你心里还是会犯嘀咕:这个 Feature 会不会带来其他 Bug ?这个 Fix 会不会引入其他 “Feature” ?
  • 当你想要对项目中的代码进行重构的时候,你会花费大量的时间进行回归测试(对以往的功能进行测试)

以上这些问题都是由于大多数开发者所使用最基本的手动测试的方式所带来的问题,解决它的根本原因就在于引入自动化测试方案。

什么是应用程序测试

一个简单的定义是:应用程序测试是指检查程序运行过程是否正确。

在日常的开发中,代码的完工其实并不等于开发的完工。如果没有测试,不能保证代码能够正常运行。

如何进行应用程序测试

  • 手动测试:通过测试人员与应用程序的交互来检查其是否正常工作。
  • 自动化测试:编写应用程序来替代人工检验。

手动测试

每一个称职的开发人员都懂得手动测试代码。在编写完源代码之后,下一步理所当然就是去手动测试它。

手动测试的优势在于足够简单灵活,但是缺点也很明显:

  • 手动不适合大型项目
  • 忘记测试某项功能
  • 大部分时间都在做回归测试

虽然有一部分手动测试时间是花在测试新特性上,但是大部分时间还是用来检查之前的特性是否仍正常工作,这种测试被称为回归测试

回归测试对人类来说是非常困难的任务——它们是重复性的,要求投入很多注意力,而且没有创造性的输入。

总之,这种测试太枯燥了。幸运的是,计算机特别擅长此类工作,这也是自动化测试可以大展身手的地方!

自动化测试

自动化测试(automated testing)是利用计算机程序检查软件是否运行正常的测试方法。换句话说,就是用其他额外的代码检查被测软件的代码。当测试代码编写完之后,就可以不费吹灰之力地进行无数次重复测试。

可使用许多种不同的方法来编写自动化测试脚本:

  • 可以编写通过浏览器自动执行的程序
    • 例如通过程序打开浏览器,输入用户名密码,点击登录
  • 可以直接调用源代码里的函数
    • 使用测试方法调用函数,记录过程和覆盖率等
  • 也可以直接对比程序渲染之后的截图
    • 例如开发的组件库,可以比对快照

虽然每一种方法的优势各不相同,但它们有一大共同点:相比手动测试而言节省了大量时间以及提高了程序的稳定性

当然不仅如此,自动化测试还有很多优点,比如:

  • 尽早的发现程序的 bug 和不足
  • 增强程序员对程序健壮性、稳定性的信心
  • 改进设计
  • 快速反馈,减少调试时间
  • 促进重构

当然了,自动化测试不可能保证一个程序是完全正确的,而且事实上,在实际开发过程中,编写自动化测试代码通常是开发人员不太喜欢的一个环节。

大多数情况下,前端开发人员在开发完一项功能后,只是打开浏览器手动点击,查看效果是否正确,之后就很少对该块代码进行管理。造成这种情况的原因主要有两个:

  • 一个是业务繁忙,没有时间进行测试的编写
  • 另一个是不知道如何编写测试

主要困惑来自于:

  • 如何进行前端应用测试?
  • 应用程序中哪些部分应该被优先测试?
  • 这些部分应该使用什么方法进行测试?
  • 一些特殊场景下的测试问题该怎么解决?
  • 我们如何从一开始就整合不同的测试技巧,编制一个高效的测试套件?

这些问题不应该作为开发者掌握前端自动化测试的绊脚石,一旦掌握了前端自动化测试方案无论是应对大型项目的开发还是升职加薪都是有益的。

测试分类

前端开发最常见的测试主要是以下几种:

  • 单元测试:验证独立的单元是否正常工作
  • 集成测试:验证多个单元协同工作
  • 端到端测试:从用户角度以机器的方式在真实浏览器环境验证应用交互
  • 快照测试:验证程序的 UI 变化

单元测试

单元测试是对应用程序最小的部分(单元)运行测试的过程。通常,测试的单元是函数,但在前端应用中,组件也是被测单元。

单元测试可以单独调用源代码中的函数并断言其行为是否正确。

// sum.js
// 待测函数
export default function sum(a,b) {
  return a + b
}


// sum.spec.js
// 将 sum 函数导入测试文件
import sum from './sum.js'

function testSum() {
  // 如果 sum 函数不返回 2,则抛出错误
  if (sum(11) !== 2) {
    throw new Error('sum(1,1) did not return 2')
  }
}

testSum()

与端到端测试不同,单元测试运行速度很快,只需要几秒钟的运行时间,因此你可以在每次代码变更后都运行单元测试,从而快速得到变更是否破坏现有功能的反馈。

单元测试应该避免依赖性问题,比如不存取数据库、不访问网络等等,而是使用工具虚拟出运行环境。这种虚拟使得测试成本最小化,不用花大力气搭建各种测试环境。

单元测试的优点:

  • 提升代码质量,减少 Bug
  • 快速反馈,减少调试时间
  • 让代码维护更容易
  • 有助于代码的模块化设计
  • 代码覆盖率高

单元测试的缺点:

  • 由于单元测试是独立的,所以无法保证多个单元运行到一起是否正确

常见的 JavaScript 单元测试框架:

Mocha 跟 Jest 是目前最火的两个单元测试框架,基本上目前前端单元测试就在这两个库之间选了。总的来说就是 Jest 功能齐全,配置方便,Mocha 灵活自由,自由配置。

两者功能覆盖范围粗略可以表示为:Jest === Mocha + Chai + Sinon + mockserver + istanbul

实际使用来看,目前最火的是 Jest,推荐使用。

集成测试

人们定义集成测试的方式并不相同,尤其是对于前端。

有些人认为在浏览器环境上运行的测试是集成测试;有些人认为对具有模块依赖性的单元进行的任何测试都是集成测试;也有些人认为任何完全渲染的组件测试都是集成测试。

个人笼统的认为,将多个单元组合在一起进行的测试可以称为集成测试。

集成测试从用户角度出发,不关注细节和过程,只关注结果。

优点:

  • 由于是从用户使用角度出发,更容易获得软件使用过程中的正确性
  • 集成测试相对于写了软件的说明文档
  • 由于不关注底层代码实现细节,所以更有利于快速重构
  • 相比单元测试,集成测试的开发速度要更快一些

缺点:

  • 测试失败的时候无法快速定位问题
  • 代码覆盖率较低
  • 速度比单元测试要慢

端到端测试(E2E)

E2E(end to end)端到端测试是最直观可以理解的测试类型。在前端应用程序中,端到端测试可以从用户的视角(可见的)通过浏览器自动检查应用程序是否正常工作。

想象一下,你正在编写一个计算器应用程序,并且你想测试两个数求和的运算方法是否正确。你可以编写一个端到端测试,打开浏览器,加载计算器应用程序,单击“1”按钮,单击加号“+”按钮,再次单击“1”按钮,单击等号“=”,最后检查屏幕是否显示正确结果“2”。

function testCalculator(browser) {
  browser
  .url('http://localhost:8080')
  .click('#button-1')
  .click('#button-plus')
  .click('#button-1')
  .click('#button-equal')
  .assert.containsText('#result''2')
  .end()
}

编写完一个端到端测试后,可以根据自己的需求随时运行它。想象一下,相比执行数百次同样的手动测试,这样一套测试代码可以节省多少时间!

优点:

  • 真实的测试环境,更容易获得程序的信心

缺点:

  • 首先,端到端测试运行不够快。启动浏览器需要占用几秒钟,网站响应速度又慢。通常一套端到端测试需要 30 分钟的运行时间。如果应用程序完全依赖于端到端测试,那么测试套件将需要数小时的运行时间。
  • 端到端测试的另一个问题是调试起来比较困难。要调试端到端测试,需要打开浏览器并逐步完成用户操作以重现 bug。本地运行这个调试过程就已经够糟糕了,如果测试是在持续集成服务器上失败而不是本地计算机上失败,那么整个调试过程会变得更加糟糕。

一些流行的端到端测试框架:

快照测试

快照测试类似于“找不同”游戏。快照测试会给运行中的应用程序拍一张图片,并将其与以前保存的图片进行比较。如果图像不同,则测试失败。这种测试方法对确保应用程序代码变更后是否仍然可以正确渲染很有帮助。

传统快照测试是在浏览器中启动应用程序并获取渲染页面的屏幕截图。它们将新拍摄的屏幕截图与已保存的屏幕截图进行比较,如果存在差异则显示错误。这种快照测试在操作系统或浏览器存在版本间差异时,即使快照并没有改变,也会遇到测试失败问题。

开发者可以使用 Jest 测试框架编写快照测试。取代传统对比屏幕截图的方式,Jest 快照测试可以对 JavaScript 中任何可序列化值进行对比。你可以使用它们来比较前端组件的 DOM 输出。

Jest 首次会将组件渲染的 html 内容作为快照保存下来,之后测时的时候会用新的 html 比对快照的 html。

快照测试可以用来测试提供给外部使用的组件库(例如 vant 对每个组件都做了快照测试),保证 UI 的稳定性。

应该编写哪种测试类型

我们应该编写哪种测试类型?答案是:都写,根据情况灵活分配。

  • 单元测试:从程序角度出发,对应用程序最小的部分(函数、组件)运行测试的过程,它是从程序员的角度编写的,保证一些方法执行特定的任务,给出特定输入,得到预期的结果。
  • 集成测试:从用户角度出发,对应用中多个模块组织到一起的正确性进行测试。
  • 快照测试:快照测试类似于“找不同”游戏,主要用于 UI 测试。
  • 端到端测试:端到端测试是从用户的角度编写的,基于真实浏览器环境测试用户执行它所期望的工作。

测试金字塔

如果你真的想为你的软件构建自动化测试,你必须知道一个关键的概念:测试金字塔。Mike Cohn 在他的著作《Succeeding with Agile》一书中提出了这个概念。这个比喻非常形象,它让你一眼就知道测试是需要分层的。它还告诉你每一层需要写多少测试。

在这里插入图片描述

金字塔模型自下而上分为单元测试、集成测试、UI 测试,之所以是金字塔结构是因为单元测试的成本最低,与之相对,UI 测试的成本最高。所以单元测试写的数量最多,UI 测试写的数量最少。同时需注意的是越是上层的测试,其通过率给开发者带来的信心是越大的。

为了维持金字塔模型的形状,一个健康、快速、可维护的测试组合应该是这样的:许多小而快的单元测试,适当的一些更粗粒度的集成测试,少量高层次的端到端测试。

奖杯模型

奖杯模型摘自 Kent C. Dots 提出的 The Testing Trophy,该模型是笔者比较认可的前端现代化测试模型,模型示意图如下。

在这里插入图片描述

参考:

奖杯模型中自下而上分为静态测试、单元测试、集成测试、e2e 测试,它们的职责大致如下:

  • 静态测试:在编写代码逻辑阶段时进行报错提示。(代表库:ESLint、Flow、TypeScript)(严格来讲它不属于测试内容,而是用来保证代码质量的。)
  • 单元测试:在奖杯模型中,单元测试的职责是对一些边界情况或者特定的算法进行测试。(代表库:JestMocha)
  • 集成测试:模拟用户的行为进行测试,对网络请求、获取数据库的数据等依赖第三方环境的行为进行 Mock(模拟)。(代表库:Jestreact-testing-libraryVue Testing Library 等)
  • e2e 测试:模拟用户在真实环境上操作行为(包括网络请求、获取数据库数据等)的测试。(代表库:Cypress

越是上层的测试给开发者带来的自信是越大的,与此同时,越是下层的测试,测试的效率是越高的。奖杯模型综合考虑了这两点因素,可以看到其在集成测试中的占比是最高的。

为了维持奖杯模型的形状,一个健康、快速、可维护的测试组合应该是这样的:

  • 在底层为应用配置静态测试,比如使用 ESLint 约束代码规范、使用 TypeScript 增强类型定义
  • 为应用中的特定算法或是工具函数编写小而快的单元测试
  • 写许多模拟真实用户行为的集成测试,增强应用构建信心
  • 为稳定的组件编写快照测试
  • 为应用核心业务流程编写少量的高层次端到端测试

个人建议

  • 如果是开发纯函数库,建议写更多的单元测试 + 少量的集成测试
  • 如果是开发组件库,建议写更多的单元测试、为每个组件编写快照测试、写少量的集成测试 + 端到端测试
  • 如果是开发业务系统,建议参考奖杯模型,写更多的集成测试、为工具类库、算法写单元测试、写少量的端到端测试

测试覆盖率

测试覆盖率(test coverage)是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据,有利于客观认识软件质量,正确了解测试状态,有效改进测试工作。

如何度量测试覆盖率呢?

  • 代码覆盖率
  • 需求覆盖率

代码覆盖率

最著名的测试覆盖率就是代码覆盖率。这是一种面向软件开发和实现的定义。它关注的是在执行测试用例时,有哪些软件代码被执行到了,有哪些软件代码没有被执行到。被执行的代码数量与代码总数量之间的比值,就是代码覆盖率

根据代码粒度的不同,代码覆盖率可以进一步分为四个测量维度。它们形式各异,但本质是相同的:

  • 行覆盖率(line coverage):是否每一行都执行了?
  • 函数覆盖率(function coverage):是否每个函数都调用了?
  • 分支覆盖率(branch coverage):是否每个 if 代码块都执行了?
  • 语句覆盖率(statement coverage):是否每个语句都执行了?

如何度量代码覆盖率呢?一般可以通过第三方工具完成,比如 Jest 自带了测试覆盖率统计(如图)。

在这里插入图片描述

这些度量工具有个特点,那就是它们一般只适用于白盒测试(代码测试),尤其是单元测试。对于黑盒测试(例如功能测试/系统测试)来说,度量它们的代码覆盖率则相对困难多了。

需求覆盖率

对于黑盒测试,例如功能测试/集成测试/系统测试等来说,测试用例通常是基于软件需求而不是软件实现所设计的。因此,度量这类测试完整性的手段一般是需求覆盖率,即测试所覆盖的需求数量与总需求数量的比值

需求粒度的不同,需求覆盖率的具体表现也有不同。例如,系统测试针对的是比较粗的需求,而功能测试针对的是比较细的需求。当然,它们的本质是一致的。

如何度量需求覆盖率呢?通常没有现成的工具可以使用,而需要依赖人工计算,尤其是需要依赖人工去标记每个测试用例和需求之间的映射关系

对于代码覆盖率来说,广为诟病的一点就是 100% 的代码覆盖率并不能说明代码就被完全覆盖没有遗漏了。因为代码的执行顺序和函数的参数值,都可能是千变万化的。一种情况被覆盖到,不代表所有情况被覆盖到。

对于需求覆盖率来说,100% 的覆盖率也不能说“万事大吉”。因为需求可能有遗漏或存在缺陷,测试用例与需求之间的映射关系,尤其是用例是否真正能够覆盖对应的测试需求,也可能是存在疑问的。

总结

代码覆盖率和需求覆盖率适于不同的场景,有各自的优势与不足。需要注意的是,它们不是互相排斥,而是相互补充的。

关于测试覆盖率,最重要的一点应该是迈出第一步,即有意识地去收集这种数据。没有覆盖率数据,测试工作会有点像在“黑灯瞎火”中走路。有了覆盖率数据,并持续监测,利用和改进这个数据,才是一条让测试工作越来越好的光明大道。

既然测试这么好,那是不是所有代码都要有测试用例支持呢?

我认为测试覆盖率还是要和测试成本结合起来,比如一个不会经常变的公共方法就尽可能的将测试覆盖率做到趋于 100%。而对于一个完整项目,我建议前期先做最短的时间覆盖 80% 的测试用例,后期再慢慢完善。

经常做更改的活动页面我认为没必要必须趋近 100%,因为要不断的更改测试用例,维护成本太高。

大多数情况下,将 100% 代码覆盖率作为目标并没有意义。当然,如果你在开发一个极其重要的支付应用,存在的 Bug 可能会导致数百万美元的损失,那么 100% 代码覆盖率对你是有用的。

实现传说中的 100% 代码覆盖率不仅耗时,而且即使代码覆盖率达到 100%,测试也并非总能发现 bug。有时你可能还会做出错误的假设,当你调用一个 API 代码时,假定的是该 API 永远不会返回错误,然而当 API确实在生产环境中返回错误时,你的应用就崩溃了。

测试开发方式

测试不仅能够验证软件功能、保证代码质量,也能够影响软件开发的模式。

测试开发有两个流派:

  • TDD:测试驱动开发,先写测试后实现功能
  • BDD:行为驱动开发,先实现功能后写测试

TDD

TDD(Test-driven development),测试驱动开发,是敏捷开发中的一项核心实践和技术,也是一种软件设计方法论。

  • 它的原理就是在编写代码之前先编写测试用例,由测试来决定我们的代码;
  • 而且 TDD 更多的需要编写独立的测试用例,比如只测试一个组件的某个功能点,某个工具函数等;

TDD 开发流程

在这里插入图片描述

  1. 编写测试用例
  2. 运行测试(失败)
  3. 编写代码使测试通过
  4. 重构/优化代码
  5. 新增功能,重复上述步骤

TDD 的原则

  • 独立测试:不同代码的测试应该相互独立,一个类对应一个测试类(对于C代码或C++全局函数,则一个文件对应一个测试文件),一个函数对应一个测试函数。用例也应各自独立,每个用例不能使用其他用例的结果数据,结果也不能依赖于用例执行顺序。 一个角色:开发过程包含多种工作,如:编写测试代码、编写产品代码、代码重构等。做不同的工作时,应专注于当前的角色,不要过多考虑其他方面的细节。
  • 测试列表:代码的功能点可能很多,并且需求可能是陆续出现的,任何阶段想添加功能时,应把相关功能点加到测试列表中,然后才能继续手头工作,避免疏漏。
  • 测试驱动:即利用测试来驱动开发,是TDD的核心。要实现某个功能,要编写某个类或某个函数,应首先编写测试代码,明确这个类、这个函数如何使用,如何测试,然后在对其进行设计、编码。
  • 先写断言:编写测试代码时,应该首先编写判断代码功能的断言语句,然后编写必要的辅助语句。
  • 可测试性:产品代码设计、开发时的应尽可能提高可测试性。每个代码单元的功能应该比较单纯,“各家自扫门前雪”,每个类、每个函数应该只做它该做的事,不要弄成大杂烩。尤其是增加新功能时,不要为了图一时之便,随便在原有代码中添加功能,对于C++编程,应多考虑使用子类、继承、重载等方法。
  • 及时重构:对结构不合理,重复等“味道”不好的代码,在测试通过后,应及时进行重构。
  • 小步前进:软件开发是复杂性非常高的工作,小步前进是降低复杂性的好办法。

TDD 的优点

  • 保证代码质量,因为先编写测试,所以可能出现的问题都被提前发现了;
  • 促进开发人员思考,有利于程序的模块设计;
  • 测试覆盖率高,因为后编写代码,因此测试用例基本都能照顾到;

TDD 的缺点

  • 代码量增多,大多数情况下测试代码是功能代码的两倍甚至更多;
  • 业务耦合度高,测试用例中使用了业务中一些模拟的数据,当业务代码变更的时候,要去重新组织测试用例;
  • 关注点过于独立,由于单元测试只关注这一个单元的健康状况,无法保证多个单元组成的整体是否正常;

在前端应用实际开发过程中 TDD 更适合开发纯函数库、比如 Lodash、Vue、React 等。

BDD

TDD 最大一个问题是在于开发人员最终做出来的东西和实际功能需求可能相偏离,为了解决这一问题有人发明了 BDD。

BDD 解决的另外一个关键问题就是如何定义 TDD 或单元测试过程中的细节。一些不良的单元测试的一个常见问题是过于依赖被测试功能的实现逻辑。这通常意味着如果你要修改实现逻辑,即使输入输出没有变,通常也需要去更新测试代码。这就造成了一个问题,让开发人员对测试代码的维护感觉乏味和厌烦。

BDD(Behavior-driven development)行为驱动开发,是测试驱动开发延伸出来的一种敏捷软件开发技术。

  • BDD 核心目的是为了解决 TDD 模式下开发和实际功能需求不一致而诞生;
  • BDD 不需要再面向实现细节设计测试,取而代之的是面向行为来测试。
  • 它是从产品角度出发,鼓励开发人员和非开发人员(产品、QA、客户等)之间的协作;
  • 由于 BDD 的核心是关注软件的功能测试,所以 BDD 更多的是结合集成测试进行,它是黑盒的;

BDD 的开发流程

基本上,日常 BDD 活动是三步迭代过程:

  1. 首先,以即将对系统进行的一个小改动(a User Story)为例,开发人员和非开发人员一起讨论确认需求,探索、发现并商定预期要做的事情的细节。
  2. 以一种自动化的方式将需求建立起来(设计可自动化测试的行为,以约定格式的文字记录到文档),并确认是否一致
  3. 最后,实现每个文档示例描述的行为,并从自动化测试开始以指导代码的开发

这样做的目的是使每一个更改都变得很小,并快速迭代,每次需要更多信息时都会向上移动一个级别。每次你自动化并实现一个新的示例时,便为系统添加了一些有价值的内容,并准备响应反馈。

我们称这些实践为 Discovery(发现)、Formulation(公式化)和 Automation(自动化)。

在这里插入图片描述

Cucumber 方案

理想中的 BDD 解决方案最流行的是 Cucumber**。**这个框架实现了一整套完美状态的 BDD 的开发流程。

它的协作流程是这样:

1、开发人员与产品、测试、客户等人员沟通确认需求

2、使用统一的 Gherkin 语法将功能需求转换为需求文档

用描述性自然语言定义的测试,客户、测试人员和开发人员都能看得懂,能达成共识,这种语法叫做 Gherkin Syntax,小黄瓜语法

  • 以关键字 Scenario、Feature 等来描述场景
  • 以关键字 Given、When、Then 来描述步骤
# is_it_friday__yet.feature
# 介绍测试场景
Feature: 今天是星期五吗?

  # 介绍子任务场景
  Scenario: 星期日不是星期五
    # 指定条件
    Given 今天是星期日
    # 指定操作
    When 我问今天是不是星期五
    # 指定结果
    Then 我应该被告知 "不是"

  Scenario: 星期五是星期五
    Given 今天是星期五
    When 我问今天是不是星期五
    Then 我应该被告知 "是"

Cucumber 读取使用 Gherkin 语法描述的纯文本形式的可执行规范,并验证该软件是否满足那些规范所说的内容。规范包含多个示例或方案。

每个方案都是 Cucumber 要执行的步骤的列表。 Cucumber 验证软件是否符合规范,并生成一个报告,指出每种情况的成功或失败。

3、开发人员根据 Gherkin 编写测试用例

4、编写代码使测试通过

// stepdefs.js
const assert = require('assert')
const { Given, When, Then } = require('@/cucumber/cucumber')

function isItFriday(today) {
  if (today === '星期五') {
    return '是'
  } else {
    return '不是'
  }
}

Given('今天是星期五', function() {
  this.today = '星期五'
})

Given('今天是星期日', function() {
  this.today = '星期日'
})

When('我问今天是不是星期五', function() {
  this.actualAnswer = isItFriday(this.today)
})

Then('我应该被告知 {string}', function(expectedAnswer) {
  assert.strictEqual(this.actualAnswer, expectedAnswer)
})

5、新增功能重复上述步骤

其他方案

还有一种更轻量的 BDD 方案就是以集成测试为主的开发方案。

  • 需求分析
  • 编写集成测试用例(不需要编写测试用需求文档)
  • 运行测试
  • 代码实现使测试通过
  • 重构优化
  • 增加功能重复上述步骤

BDD 的优点

  • 由于侧重于需求功能的完整度,所以能给开发人员增加更多对程序的信心;
  • 由于仅关注功能,不关注实现细节,有利于测试代码和实际代码解耦;
  • 由于大多数为编写集成测试,相比 TDD 有更好的开发效率;

BDD 的缺点

  • 因为以功能性的集成测试为主,因此不是那么关注每个函数功能,测试覆盖率比较低;
  • 没有 TDD 那么严格的保证代码质量;

TDD vs BDD

功能TDDBDD
定义测试驱动开发行为驱动开发
思想从代码角度出发,以完成高质量代码为目的。从用户角度出来,为完成功能需求为目的。
开发流程需求分析编写单元测试运行测试编写代码运行测试重构优化重复上述步骤开发人员与产品、测试、客户等人员沟通并确定功能需求使用统一格式文档描述功能需求文档根据功能需求文档建立测试用例运行测试编写代码实现功能运行测试使通过重构优化重复上述步骤
代码覆盖率一般
软件安全感一般
测试类型(相对来说)单元测试集成测试
代码解耦(测试代码和实际代码的耦合)一般
开发效率一般(测试代码专注细节)高(测试代码关注功能)
代码质量一般
测试代码量

个人推荐:

  • 建议开发功能函数库使用 TDD 方案;
  • 建议开发业务系统使用 BDD 方案;

开发者可以选择单独使用其中一种方法,也可以综合使用这几个方法以取得更好的效果。只要是编写可节省时间的、有价值的测试就可以,如何编写无关紧要。

BDD + TDD

BDD 注重的是产品功能,可能无法保证很好的代码质量和测试覆盖率,所以还有人提出一种方案就是 BDD + TDD。

在这里插入图片描述

  • 开发软件的时候,先以 BDD 的流程开始,讨论需求,指定需求文档,然后开发人员将需求文档转化成测试用例,此时不急着实现功能。

    • 需求分析
    • 描述需求定义文档
    • 编写集成测试用例
  • 接着执行 TDD 流程

    • 编写单元测试用例
    • 编写代码使单元测试通过
    • 重构优化
  • 等到 TDD 单元测试全部完成,再实现 BDD 功能测试用例。

  • 增加功能重复上述步骤。

这样既保证了功能和用户实际需求,也保证了代码的质量。

这样可以把 BDD 看作是在需求与 TDD 之间架起一座桥梁,它将需求进一步场景化,更具体的描述系统应该满足哪些行为和场景,让 TDD 的输入更优雅、更可靠。

当然实际场景中,这个方案要写大量测试代码,所以它也是一个理想中的方案,实施起来还是有一定的困难。

前端自动化测试的权衡利弊

当我们开始编写自动化测试时,可能想要测试所有的东西。因为不想再体验未经测试的应用程序带来的痛苦。但是我们又都知道测试会减缓开发速度

在编写测试时,请务必牢记编写测试的目的。通常,测试的目的是为了节省时间如果你正在进行的项目是稳定的并且会长期开发,那么测试是可以带来收益的。

但是如果测试编写与维护的时间长于它们可以节省的时间,那么你根本不应该编写测试。当然,在编写代码之前你很难知道通过测试可以节省多少时间,你会随着时间的推移去了解。但是,假设你正在一个短期项目中创建原型,或者是在一个创业公司迭代一个想法,那你可能不会从编写测试中获得收益。

凡事都有两面性,软件测试也不是银弹,好处虽然明显,却并不是所有的项目都值得引入测试框架,毕竟维护测试用例也是需要成本的。对于一些需求频繁变更、复用性较低的内容,比如活动页面,让开发专门抽出人力来写测试用例确实得不偿失。

而适合引入测试场景大概有这么几个:

  • 需要长期维护的项目。它们需要测试来保障代码可维护性、功能的稳定性
  • 较为稳定的项目、或项目中较为稳定的部分。给它们写测试用例,维护成本低
  • 被多次复用的部分,比如一些通用组件和库函数。因为多处复用,更要保障质量

测试确实会带给你相当多的好处,但不是立刻体验出来。正如买重疾保险,交了很多保费,没病没痛,十几年甚至几十年都用不上,最好就是一辈子用不上理赔,身体健康最重要。测试也一样,写了可以买个放心,对代码的一种保障,有 bug 尽快测出来,没 bug 就最好,但不能说“写那么多测试,结果测不出 bug,浪费时间”吧。

其它术语

  • 黑盒测试:不考虑程序内部结构和逻辑结构,主要是测试系统的功能是否满足“需求规格说明书”。一般会有一个输入值和一个输出值,和期望值做比较。黑盒测试也被称为功能测试或数据驱动测试,它是通过测试来检测每个功能是否都能正常使用。
  • 白盒测试:主要应用于单元测试阶段,主要是对代码级别的测试,针对程序内部的逻辑结构。测试的手段有:语句覆盖、判定覆盖、条件覆盖、路径覆盖和条件组合覆盖。

参考

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值