从 TDD 到测试策略

article/2025/10/11 19:54:02

前端没法 TDD / 前端不容易做 TDD / 前端 TDD 收益不大

这是进公司后无数人给我判的“死刑”。

事实上好像的确如此?

在这个崇尚敏捷的组织里,我们有毕业生的入职前培训,入职后培训,有 TwU,有无数定期不定期的培训。TDD 这个话题贯穿始终,是几乎每一个培训的主战场。

在这个战场上我们的敌人有大兵 FizzBuzz,上尉 MarsRover,王牌 ParkingLot。但是所有的敌人好像都没有长脸,都只是一行行逻辑和命令行输出。敌人的前端好像都是新兵,从未上过战场。我们跟着大家上阵冲杀,披巾斩棘。战胜每一个敌人我心中都有一个疑问:

前端怎么 TDD?

都一样的 / 一个道理。

这是几乎每一次培训我从各个 coach 那里得到的答案,然后每一次我的反应大概都是 - 哦,一样的啊,那没事了。

但每一次上项目实践,我都会眉头一皱,发现事情并没有那么简单 - 好像所有人都忽略了前端各种逻辑和 UI 的耦合,这些耦合会让你的单测变得不那么单元。于是项目上的老人会告诉你:“前端只能 TDD 纯逻辑。”

是的,就是那些 utils 和极度类似于 utils 的玩意儿。

从 TDD 到测试方法

在计算机编程中,单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

这是一大段来自 wiki 的解释,可以看出单元测试的目标是最小逻辑单元模块,它要么是一个独立的函数,要么是对象中的一个方法。

从单元测试的角度出发,其实前端做 TDD 的方式豁然开朗 - 找出边界 - 找出这些函数和方法与 UI 的边界。

boundary

boundary

以组件为边界

不管是为了测试的合理性还是为了别的什么理由,前端框架的官方们推出了一系列 testing-library,它的思想是通过初始化一个组件的参数,然后将组件渲染到一个环境中,接着在这个环境中模拟用户的页面操作,最后选取一些特定的元素来断言组件的行为是否正确。

之前有提到过前端 TDD 的难点在于 UI 与逻辑的耦合,这种方案索性就不管耦合了。通过模拟用户操作来断言渲染的 UI 是否拥有某个行为/文字/节点,从而达到测试整个组件的目的。

此时的边界将是整个组件。

这样的方案确实解决了没法/不太好 TDD 的痛点,但是带来了一些问题:

  1. 由于要渲染组件,并且要模拟用户行为并等待组件响应,最后才去做断言。比起对于纯逻辑的测试,耗时会增加许多,在项目逐渐变大,测试数量超过 2000 个左右的时候尤为明显,这严重破环了单元测试需要快速反馈的特性;

  2. 说是单元测试,但是这样的测试方法并不“单元”,一个测试里面将所有的东西测了个遍(action / reducer / saga - 如果你有的话),甚至在测试里你需要写一些简单的逻辑去模拟用户事件。最后写出来的往往测试包含无数用户操作和断言,更像是一种集成测试甚至是 E2E 测试;

  3. 需要写选择器来选取特定的元素,这意味着在一些情况下你要为了“单元测试”在产品代码的元素上加上可供选择器选择的属性,比如 test-id / id 等;

以组件逻辑为边界

如果你使用的是三大框架,那么你的 UI 最终是由一系列 template 渲染出来的,而 template 中的数据是从其他地方计算得到的,如果我们能够找到 template 和这个地方的边界,其实我们就已经找到了 UI 和逻辑的边界,如果这时我们将 UI 和逻辑的耦合解开,其实我们就能够测试所有逻辑而不是纯逻辑了,这个时候如果做 TDD 就真的和后端一个道理了。

此时的边界将是组件内的逻辑。

很抽象?没关系,我们来举个例子:

在 React 的函数式组件中,return 语句里面的就是渲染 UI 的 template,所以 return 语句就是一个边界,return 以上为逻辑,return 以下为 template。

根据函数式的思想,任何副作用都应该有明确的标识,那么 - 任何 hooks 都是副作用,所有没有副作用的纯函数都应该以独立的方式存储于某个地方,然后通过 import 的方式导入到当前组件被调用,我们通过组合各种 hooks 和纯函数来计算出 template 所需要的数据,最后把所有的计算逻辑看作这个函数式组件的副作用放入一个 hook 中。

于是我们的组件有了一个清晰的边界 - 渲染组件时传入需要的 props,将 props 传入 hook 中作为输入值计算并导出输出回组件,将拿到的输出给 template 做渲染。

这时候对 hooks 做 TDD 那不是信手拈来?

但是这样的边界划分就没问题了吗?

  1. 这样做的前提是大家都把所有的逻辑统一放到一个地方,但是人总是会想偷懒的,这时候需要团队内认可这样的方式并严格执行 code review 以保证没有人偷懒;

  2. 有的逻辑如果单独从 template 里拎出来会很傻,比如根据某个值来决定是否渲染某个节点或者渲染不同的节点,这样的逻辑放入统一的逻辑中,那么这个函数 / 方法就得返回一个 Node 节点;

问题

有的人可能会说第二种方案遗漏了组件对用户操作的反馈。比如点击按钮时组件的行为。

但我想说的是点击按钮产生的反馈本质上是事件监听和回调,回调最终将会调用某一个函数,第二种方案保证了该函数的行为是正确的。

至于点击了按钮是否真的会调用该回调函数,只要你正确使用了框架语法,那应该是框架的工作,我选择信任框架代码。至于错误使用了语法,首先你的编辑器应该给你报错,其次这样的问题理论上不会频繁出现,再次这种情况不是单元测试应该管的问题。

至于为什么?我想从测试金字塔聊起。

测试金字塔

test-pyramid

test-pyramid

测试金字塔提倡我们将所有自动化测试分为不同的粒度,不同的测试应该关注不同的场景。

我见到过许多同学对单元测试的“执念”是想用单元测试覆盖到自己代码中的每一个角落,穷举自己代码中的每一个行为,确保每一行代码都是正确的。与其说这是在写测试不如说是在追求测试覆盖率。

测试应该是关于边界的,边界里面的行为不用关心边界外边的情况,只需要保证自己内部的行为正确即可。那么单元测试就不需要关心你这个单元之外的情况,两个或多个单元之间的合作正常与否应该交给集成测试。

集成测试

回到上一节提到的问题,如果我们将逻辑和 UI 看作两个单元,那么它们之间的集成正确与否(比如按钮的点击是否正确)应该交给集成测试而不是单元测试,这里的集成测试便是组件测试

而之前提到过的官方出品的各种 testing-library 就很适合做这件事情 - 渲染组件,然后模拟用户点击,最后断言这个点击事件被捕获后的组件行为是否正确。

不过话虽如此,但我并不提倡你使用它来做组件测试,我更推荐你用 cypress,在 cypress7.0 中它引入了组件测试的 runner,简单来说它会将组件真的渲染到浏览器中而不是传统的 jsdom based on Node,同时它也帮助开发者规避了许多组件测试的 anti-patterns。

UI 测试

到这里我们已经能够确保各个组件的逻辑是我们所预期的,我们当然也相信这些逻辑计算后通过 template 渲染出来的 UI 也是符合我们预期的。但是我们并没有相应的测试来给我们足够的信心,这时候我们需要 UI 测试来保证我们每一次渲染的页面都是统一的。

这里产生的一个问题是,如果一个页面还处于迭代阶段,那么如果此时我们给这个页面加上 UI 测试,这个测试在频繁的页面更改中就会频繁失败,我们就需要频繁修复这个测试。

如果一个项目对 UI 的正确性要求比较高,为了保证渲染的正确性,我们可以将一个页面拆开,先给已经完成的 section 加上 UI 测试,等整个页面完成后再加上最后的页面测试。如果一个项目对 UI 的正确性要求没那么高,那么只需要等这个页面开发完成再加上 UI 测试即可。

E2E测试

当一个 APP 的某一个 user journey 开发完成后(比如 user login),为了保证其完整性和在之后的开发中不被破坏,我们可以给这个 user journey 加上一个 E2E 测试,模仿用户真正使用这个 APP 时的做法。

需要注意的是,由于 E2E 测试需要联通 APP 的各个部分,是一个比较耗费资源和时间的测试,我们应该尽可能少地使用 E2E 测试,理论上每一个 user journey 只需要一条 happy path 和一条 unhappy path 即可。

写测试是为了什么

test

test

其实到这里我们自下而上的测试策略都已经有一个大概的样子了,由于篇幅的限制,我没办法在同一篇文章中去详细讨论测试金字塔中的每一类测试。在这里我想表达的思想其实是:

  1. 单元测试不应该包罗万象,请不要过分关注测试覆盖率

  2. 不同粒度的测试应该关注不同的点,它们之间是互补的关系

  3. 在一定条件下前端的 TDD 也可以像后端一样顺滑

最后,测试这件事不应该成为我们的负担,它既不该是一个 KPI,也不该是 QA 份内的事情。它是保证软件质量各个环节中不可或缺的一部分,而比测试更进一步的 TDD 给了我们足够的修改旧代码的勇气,我衷心希望每一位小伙伴都能慢慢意识到这些点并慢慢接受它们。

共勉,祝大家新年快乐~


http://chatgpt.dhexx.cn/article/QoqhtrG4.shtml

相关文章

使用ddt执行数据驱动测试

所谓数据驱动测试,简单的理解为数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。通过使用数据驱动测试的方法,可以在需要验证多组数据测试场景中,使用外部数据源实现对输入输出与期望值的参数化,避免在测…

先测试再开发?TDD测试驱动开发了解一下?

1、什么是TDD 我第一次接触TDD这个概念&#xff0c;是在<<代码整洁之道>>中&#xff0c;作者鲍勃大叔在书中&#xff0c;写了一些关于测试代码的代码规范&#xff0c;其实就提到了有关TDD三定律: - 定律一&#xff1a; 在编写不能通过的单元测试前&#xff0c;不…

测试驱动开发(TDD)前端篇

当你在写生产代码时&#xff0c;你处在高认知的状态&#xff08;obvious&#xff09;&#xff0c;你的研发流程和你的工程实践&#xff0c;有助于你一步一步的提升你的认知能力&#xff0c;把你的问题进行一个降解&#xff08;分解&#xff09;&#xff0c;只要你做到同样的事情…

TDD 开发测试

测试驱动开发(Test-Driven Development)。是敏捷开发中的一项核心实践和技术。 TDD是在开发功能代码之前&#xff0c;先编写单元测试用例代码&#xff0c;测试代码确定需要编写什么产品代码。 变红 ——> 变绿 ——> 重构 在进行 TDD 案例编写的时候&#xff0c;看一…

TDD测试驱动开发

TDD测试驱动开发 什么是测试驱动开发测试驱动开发该怎么做&#xff1f;需求一&#xff1a;输入一个非元音字符&#xff0c;并预期返回字符本身 (输入"h" 返回“h”)需求二&#xff1a;输入一个元音&#xff08;a,e,i,o,u&#xff09;&#xff0c;返回 mommy (输入&qu…

TDD测试驱动学习

gtest 和 gmock 安装 //如果不知道对应库名字可以执行这个命令查找对应库,如果没找到要去跟新对应的源sudo apt update sudo apt-cache search gtest sudo apt-cache search gmock 测试例子 #include <string> using std::string;// 定义 Soundex 类 class Soundex {pub…

TDD (test driver development)测试驱动开发

##为什么需要测试驱动/或者说需要单元测试 我们工作接触的软件项目&#xff0c;不是学生时代&#xff0c;玩一玩就不管了&#xff0c;工作的项目&#xff0c;需要长期维护&#xff0c;并且随着时间的推移需要增加新的需求&#xff0c;进行修改&#xff0c;优化。此时已经距离你…

tdd(测试驱动开发)的概述

最近的工作的项目&#xff0c;使用了tdd&#xff08;test-driven development测试驱动开发&#xff09;的开发模式。 这两几年大概听说了无数种xxx-dd, ddd, tdd, atdd, bdd, fdd, udd各种名词眼花缭乱&#xff0c;当然很多dd其实也有相互借鉴&#xff08;抄袭&#xff09;的部…

测试驱动开发(TDD)实践与技巧

文章目录 引言Google Mock测试用例结构断言经典式断言Hamcrest 断言 测试驱动开发&#xff1a;第一个示例开场白开始吧去掉不干净的代码增量性fixture 设置思索与测试驱动开发测试驱动与测试 测试驱动开发基础与单元测试单元测试的组织结构测试驱动开发周期&#xff1a;红-绿-重…

opencv配置相关的截图参考

opencv配置相关的截图参考&#xff0c;如下&#xff1a;

Anaconda3安装及opencv配置

一、Anaconda安装 1.直接百度搜anaconda&#xff0c;进入官网即可&#xff08;anaconda网站链接&#xff09; 2.点击图片上黑框&#xff08;Get Started&#xff09;即可进入下一步&#xff0c;选择下图中第四个 3.选择适合电脑类型的anaconda安装器&#xff0c;注意选好64位…

linux安装配置opencv

刚开始学习ubuntu&#xff0c;有些项目需要用到opencv&#xff0c;当我用下面的命令安装包的时候&#xff0c;总是出现“E&#xff1a;无法定位软件包 opencv”的错误。然后开始着手解决&#xff0c;网上搜集了很多的教程&#xff0c;大部分都是说要更换源&#xff0c;我也照做…

opencv安装配置测试

前面安装了pcl和qt&#xff0c;以及qt中的vtk&#xff0c;这里配置下opencv4.3 将cv复制到D盘下。 安装完成之后&#xff0c;添加环境变量&#xff0c;[计算机]->右键 [属性]->[高级系统设置]->[环境变量]->[系统环境变量]->编辑 [Path]&#xff0c;添加“D:…

Qt中配置OpenCV

Qt中配置OpenCV 1. 环境下载2. 进行编译和安装2.1 新建opencv-build文件夹&#xff0c;用于opencv的 编译和安装&#xff08;直接在opencv下新建即可&#xff09;2.2 CMake设置2.3 命令行进行编译安装 3. Qt测试4. 其它问题4.1 若电脑上安装有PyQt4.2 电脑无法访问wai网 1. 环境…

Anaconda配置OpenCV

文章目录 1.安装Anaconda2.配置OpenCV2.1打开Anaconda Prompt2.2找到里面Scripts的路径2.3下载2.4验证是否配置成功 3.出错解决办法4.参考文章 1.安装Anaconda 可以查看我的上一篇文章&#xff1a;Anaconda下载、安装和环境配置 2.配置OpenCV 2.1打开Anaconda Prompt 在开始…

Opencv学习笔记——opencv配置安装与IDE环境安装

文章目录 前言一、opencv配置安装二、IDE的安装总结 前言 既然开始对AI视觉这个方面有兴趣&#xff0c;也初步接触了一些AI视觉在嵌入式方面的应用&#xff0c;那自然少不了对Opencv的学习。到现在开始学习opencv之前对它了解不多&#xff0c;只知道opencv的全称是Open Source…

vs + python + opencv 配置

首要条件&#xff0c;在vs上安装python环境。 以Visual Studio 2019为例讲解如何配置python、opencv、及相关第三方库。&#xff08;其它vs版本只是在界面上有所区别&#xff0c;过程相同。&#xff09; 步骤一&#xff1a;安装python开发工具 按下图操作&#xff0c;勾选Pyt…

vscode配置opencv

前言 本篇文章主要用来记录使用vscode配置opencv的全过程&#xff0c;在整个过程中需要用到的工具包括vscode安装包、MinGW-w64和opencv的源码。vs studio配置opencv比较简单&#xff0c;opencv官网中已经有用vs studio编译器编译好的opencv库&#xff0c;但是对于vscode而言&a…

windows下 C++ openCV配置及x86编译(傻瓜式教程)

本傻瓜教程需要的环境如下: IDE: vs2015或vs2017 , windows 10 或 11 vs2017下载地址如下: ①百度网盘 链接&#xff1a;https://pan.baidu.com/s/1r628e9M5lv_F9IWO-h05jA 提取码&#xff1a;23a7 ②官网地址 https://my.visualstudio.com/Downloads/Featured?mktzh-cnh…

【Qt+OpenCV配置简介】

【Qt&OpenCV】QtOpenCV配置简介 文章目录 【Qt&OpenCV】QtOpenCV配置简介前言一、Qt安装二、OpenCV安装三、Qt配置OpenCV四、测试​总结 前言 越来越多的开发人员选择基于开源的Qt框架与OpenCV来实现界面和算法&#xff0c;其原因不单单是无版权问题&#xff0c;更多是…