当前位置: 首页 > news >正文

前端单元测试最佳实践(一)

引言
这是一个新的小系列,跟大家聊聊怎么让前端的单元测试变得“有用”,而不是那种写了等于没写的摆设。

使用DDD与TDD结合,打造前端有意义的单元测试

很多时候,我们的测试都停留在“点个按钮,看看页面变没变”,跟业务逻辑没啥关系。但如果把**领域驱动设计(DDD)测试驱动开发(TDD)**结合起来,前端也能写出靠谱的测试,既能保证功能没问题,还能少写点没用的代码。

这篇文章,我会用一个简单的例子,带大家从业务建模开始,一步步写用例(UseCase),用TDD驱动代码实现,最后用React Hook把前端界面和业务逻辑连起来。整个过程会让前后端解耦,测试也能真正验证业务价值。

用例这个概念,其实不是DDD中的一部分,但为了方便理解,我会用这个概念。


一、为什么我要写TDD?

先说说前端开发里常见的糟心事:

  1. 测试覆盖难:组件逻辑和UI渲染混在一起,想单独测试业务逻辑都费劲。
  2. 测试价值低:测来测去都是"按钮点击了没",抓不住业务逻辑的核心。
  3. 测试维护难:需求改了,测试用例没人删,时间一长全是过时的测试。

而TDD呢,先写测试,再写业务代码,能让我们只写有用的代码——每行代码都有测试撑腰,需求没要的就不写,干净又省心。

谦卑对象模式

这就不得不提一个设计模式,谦卑对象模式。谦卑对象模式(Humble Object Pattern)是一种设计模式,用于将复杂逻辑从难以测试的组件中分离出来,以提高代码的可测试性和可维护性。其核心思想是将与用户界面、外部系统或复杂依赖相关的代码(难以测试的部分)剥离,保留一个“谦卑”的对象,只包含简单逻辑或直接调用,而将主要业务逻辑放入易于测试的独立对象中。
比如:前端的GUI展示部分难以测试,所以应尽量保持简单,只负责渲染数据。数据处理和业务逻辑则单独拆分到易于测试的模块中,这样既降低出错率,也方便单元测试。

DDD能帮我们把业务抽出来,变成独立的“领域模型”和“用例”,让前端也有自己的逻辑层,跟界面分开。


二、实践路径:从业务建模到TDD

1. 先搞清楚业务:建个领域模型

第一步,咱们得从业务角度想想,核心是什么。比如说,我们要做一个任务管理系统,里面有“任务”,任务有标题、描述、状态(待办、进行中、已完成)。这些东西可以建个模型,叫Task,只管业务规则,不管界面长啥样。

class Task {constructor(id, title, description, status) {this.id = id;this.title = title;this.description = description;this.status = status;}// 业务规则:已完成的任务不能再改状态changeStatus(newStatus) {if (this.status === 'done' && newStatus !== 'done') {throw new Error('已完成的任务不能随便改状态哦');}this.status = newStatus;}
}

这个Task就是个纯业务对象,比如“已完成的任务不能改状态”这种规则就写在里面,跟页面没半毛钱关系。

2. 写用例(UseCase):定义业务操作

接下来,把具体的业务操作封装成UseCase。比如“创建任务”是个常见的操作,咱们可以写个CreateTaskUseCase,告诉系统怎么创建任务。

class CreateTaskUseCase {constructor(taskRepository) {this.taskRepository = taskRepository; // 模拟个仓库,存任务的地方}async execute(title, description) {const task = new Task('task-001', title, description, 'todo'); // 默认待办状态await this.taskRepository.save(task); // 存起来return task;}
}

这个用例就是前端跟后端交互的“合同”,告诉大家创建任务的步骤是什么。

3. 先写测试:TDD的“红灯”阶段

TDD的核心是先写测试,再写代码。咱们以CreateTaskUseCase为例,写个测试,确保它能正常工作:

test('创建任务时应该有正确的标题和默认状态', async () => {const taskRepository = { save: jest.fn() }; // 假装有个仓库const useCase = new CreateTaskUseCase(taskRepository);const task = await useCase.execute('买牛奶', '记得买全脂的');expect(task.title).toBe('买牛奶');expect(task.status).toBe('todo');
});

这时候运行测试,肯定挂,因为taskRepository和代码细节还没写呢。

4. 写最小代码:让测试“绿灯”

然后,咱们写刚好能通过测试的代码。把taskRepository模拟一下:

class MockTaskRepository {async save(task) {// 假装保存,啥也不干}
}

再跑测试,就通过了。TDD就是要先“红”再“绿”,每步都踏实。

5. 重构:让代码更好看

测试过了,就可以优化一下。比如把硬编码的'task-001'改成随机ID生成器,但前提是测试还得过。


三、用React Hook把业务和前端连起来

传统写法里,前端组件直接调API、改数据,乱七八糟。现在业务逻辑都在UseCase里了,组件只管“调用用例”和“显示结果”。

咱们写个useCreateTask Hook,把用例塞进去:

function useCreateTask() {const taskRepository = { save: async (task) => console.log('保存任务:', task) }; // 模拟仓库const createTaskUseCase = new CreateTaskUseCase(taskRepository);const createTask = async (title, description) => {const task = await createTaskUseCase.execute(title, description);console.log('任务创建成功:', task);};return { createTask };
}

然后在组件里用:

function TaskForm() {const { createTask } = useCreateTask();const [title, setTitle] = useState('');const handleSubmit = async () => {await createTask(title, '用户输入的任务');setTitle(''); // 清空输入框};return (<div><input value={title} onChange={(e) => setTitle(e.target.value)} /><button onClick={handleSubmit}>创建任务</button></div>);
}

组件只管界面和调用,具体怎么创建任务交给Hook和UseCase,干净又解耦。

测试的时候,咱们测UseCase或者Hook就行,不用管页面渲染。比如:

test('useCreateTask应该调用useCase', async () => {const mockUseCase = { execute: jest.fn().mockResolvedValue({ title: '测试任务' }) };const taskRepository = { save: jest.fn() };const useCase = new CreateTaskUseCase(taskRepository);mockUseCase.execute = useCase.execute;const { createTask } = useCreateTask();await createTask('测试任务', '描述');expect(mockUseCase.execute).toHaveBeenCalledWith('测试任务', '描述');
});

这测的是业务逻辑,不是按钮点了啥效果。


四、DDD+TDD的好处

用了这套组合拳,开发体验和代码质量都上去了:

  • 逻辑不乱了:业务规则都在模型和用例里,组件只管展示。
  • 测试靠谱了:测的是业务行为,不是界面效果,出了问题一眼就能看出来。
  • 代码干净了:TDD保证每行代码都有用,需求没了测试一删,代码也没了。
  • 回归省心了:以前改个需求要手动测半天,现在跑测试就知道行不行。

五、总结

用DDD把业务抽象成模型和用例,再用TDD推着实现,前端也能像后端一样,写出健壮、可测试、好维护的代码。React Hook把界面和逻辑桥接起来,既模块化又好测。这不只是换个写法,更是换个思路。试试看吧,绝对有收获!

还有一件事,最近接触的工作内容是react 所以这里用这个做举例,但是说真的,vite系列的vitest的单元测试真的顶级好用,同样做单元测试,vue3的单元测试可覆盖率也会很容易更高。

http://www.lryc.cn/news/613716.html

相关文章:

  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第八天(Vue框架及其安装)(完结篇) 重点 ! ! !
  • 基于Web的交互式坐标系变换矩阵计算工具
  • 【Linux】Linux增删改查命令大全(附频率评级)
  • vue3 map和filter功能 用法
  • Odoo 18 → Odoo 19 功能改动对比表
  • Vue3 基本语法
  • day21|学习前端vue3框架和ts语言
  • pdf文件转word免费使用几个工具
  • CSS BFC
  • webrtc弱网-EncodeUsageResource类源码分析及算法原理
  • Spring Security自动处理/login请求,后端控制层没有 @PostMapping(“/login“) 这样的 Controller 方法
  • 设计模式(二)——策略模式
  • 冠雅新品 | 以“无形之光”守护双眸,以“无声之智”浸润生活
  • 基于R语言,“上百种机器学习模型”学习教程 | Mime包
  • 【昇腾】Atlas 500 A2 智能小站制卡从M.2 SATA盘启动Ubuntu22.04系统,重新上电卡死没进系统问题处理_20250808
  • 主播生活模拟器2|主播人生模拟器2 (Streamer Life Simulator 2)免安装中文版
  • 31-数据仓库与Apache Hive-Insert插入数据
  • Pinterest视觉营销自动化:亚矩阵云手机实例与多分辨率适配技术
  • 远期(Forward)交易系统全球金融市场解决方案报告
  • 32-Hive SQL DML语法之查询数据
  • 《Hive、HBase、StarRocks、MySQL、OceanBase 全面对比:架构、优缺点与使用场景详解》
  • 安装部署K8S集群环境(实测有效版本)
  • K8s 常见故障案例分析
  • ArgoCD 与 GitOps:K8S 原生持续部署的实操指南
  • hive-日期拆分为多行
  • 二、k8s 1.29 之 网络
  • 2025年城市建设与智慧交通国际会议(ICUCIT 2025)
  • Vue复习
  • 暴力解决MySQL连接失败
  • 协同进化:AIGC、Agent和MCP如何相互促进共同发展