当前位置: 首页 > 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.dtcms.com/a/321417.html

相关文章:

  • Linux系统编程Day9 -- gdb (linux)和lldb(macOS)调试工具
  • ubuntu安装gpu驱动
  • 中国的超算中心使用情况如何?是否算力过剩
  • vscode 配置
  • STM32学习笔记5-TIM定时器-1
  • windows安装Docker Desktop报错One prerequisite is not fulfilled.
  • CVPR2025-DEIM改进项目介绍
  • Linux 运维与优化的系统化思维:从内核到生产环境的全链路管理
  • 景区车辆监控调度管理平台建设方案
  • Jetpack Compose 常用控件
  • 电子EDI:MaxLinear EDI 需求分析
  • Go 的错误处理方式深度解析—— error vs panic vs recover:机制原理与实战取舍
  • vue3 遍历 map 用法
  • 密码学安全模型(Security Model):用形式化框架定义“安全“
  • Microsoft Dynamics AX 性能优化解决方案
  • 网络资源模板--基于Android Studio 实现的麻雀笔记App
  • CSS:BFC
  • 五种IO模型 阻塞IO 多路转接之select 多路转接之poll
  • 灰狼算法+四模型对比!GWO-CNN-LSTM-Attention系列四模型多变量时序预测
  • VIOO IQOO7手机 解锁BL ROOT教程
  • 光猫、路由器和交换机
  • 如何使用 pg_rman 进行 PostgreSQL 的备份与恢复
  • 解决 vscode 编辑 markdown 文件时退格键/backspace 删除卡顿问题
  • 【普中STM32精灵开发攻略】--第 14 章 动态数码管实验
  • PyQt 中 pyqtSignal 的使用
  • Orangepi5-RK3588安装ffmpeg硬编码版本
  • UE4/UE5 Android 超大(视频)文件打包/防拷贝方案
  • 【07】OpenCV C++实战篇——鼠标在图片上绘制矩形,计算矩形区域内灰度值的累加值显示在图片上,支持连续多次框选,快速计算结果,快速刷新画面不卡顿
  • Atto Round 1 (Codeforces Round 1041, Div. 1 + Div. 2) A-C
  • 【身心健康】能量管理——为你的情绪和身体注入积极力量