集成测试和单元测试的区别:核心不在“测多少代码”,而在“边界划到哪”
面试速答(30 秒版 TL;DR)
- 单元测试(Unit Test) 测的是一个尽量小、边界可控的单元,通常是函数、类、Hook、工具模块,目标是快速验证局部逻辑。
- 集成测试(Integration Test) 测的是多个模块协作后的结果,目标是验证模块之间的连接点有没有出问题,比如组件 + 状态管理、页面 + 路由、服务 + 数据库。
- 两者最核心的区别,不是代码行数,也不是测得快不快,而是:是否把依赖隔离掉,还是把真实协作链路一起纳入测试。
- 单元测试更快、更稳定、定位更准,但覆盖不了模块集成处的问题;集成测试更接近真实运行,但更重、更慢、维护成本更高。
- 更合理的工程实践不是二选一,而是:底层逻辑多写单元测试,关键业务链路补集成测试。
心智模型:看“系统边界”而不是看“文件大小”
很多人会误以为:
- 测一个文件就是单元测试
- 测多个文件就是集成测试
这个判断方式并不稳。
更准确的判断标准是:这条测试把系统边界划在哪里。
例如:
- 你测试
sum(a, b),把外部依赖都排除掉,这通常是单元测试。 - 你测试一个商品详情页,真实挂上路由、状态管理、接口 mock,再验证页面是否正确渲染,这更像集成测试。
所以本质上:
- 单元测试强调隔离
- 集成测试强调协作
一、先把区别说透
| 维度 | 单元测试 | 集成测试 |
|---|---|---|
| 测试对象 | 单个函数、类、Hook、组件的局部逻辑 | 多个模块组合后的行为 |
| 依赖处理 | 通常会 mock 掉外部依赖 | 倾向保留真实协作关系,只替换不可控外部系统 |
| 关注点 | 逻辑是否正确 | 模块连接处是否正确 |
| 执行速度 | 通常更快 | 通常更慢 |
| 定位问题 | 更精准,容易定位到具体函数 | 定位成本更高,但更接近真实场景 |
| 稳定性 | 一般更稳定 | 更容易受环境、数据、异步流程影响 |
| 适合场景 | 纯函数、工具函数、数据转换、边界分支 | 页面流程、组件协作、服务调用链、数据流转 |
如果要把这张表压缩成一句面试回答,可以直接说:
- 单元测试解决“局部逻辑对不对”,集成测试解决“模块接起来以后对不对”。
二、为什么很多人会把两者混淆
因为“测试对象”在前端里经常不是纯函数,而是组件。
例如一个 React 组件测试,可能有三种写法:
- 只测 props 计算和分支渲染,mock 掉子组件和请求层,这更偏单元测试。
- 连同真实子组件、状态管理、路由一起渲染,但把接口用 mock server 接住,这更偏集成测试。
- 浏览器里真正点按钮、跳页面、走后端链路,这已经更接近 E2E 测试。
所以它们不是按“是不是组件测试”分,而是按纳入了多少真实协作链路分。
三、用前端场景举例最容易答清楚
1. 单元测试例子
假设你有一个价格计算函数:
export function calcFinalPrice(price: number, discount: number) {
if (discount < 0 || discount > 1) {
throw new Error("discount must be between 0 and 1");
}
return Math.round(price * (1 - discount) * 100) / 100;
}
这时测试重点是:
- 折扣是否合法
- 小数精度对不对
- 边界值是否正确
这就是典型单元测试,因为它不关心页面、不关心请求、不关心状态管理。
2. 集成测试例子
再看一个下单页:
- 页面从路由参数里拿商品 ID
- 通过查询层拿商品信息
- 用户点击“加入购物车”
- store 更新购物车数量
- 页面右上角徽标同步变化
如果你把这条链路一起测出来,这个测试的价值就不只是“某个函数对不对”,而是:
- 路由参数有没有正确传递
- 查询结果有没有正确渲染
- 点击事件有没有触发业务逻辑
- store 和 UI 有没有正确联动
这就是集成测试的典型价值。
四、一张图看懂三者边界:单元测试、集成测试、E2E
这张图在面试里很好用,因为你可以顺着讲出三层关系:
- 单元测试最轻,适合打底
- 集成测试居中,适合验证关键协作链路
- E2E 最重,适合覆盖少量核心用户路径
五、二者各自擅长发现什么问题
单元测试擅长发现
- 条件分支写错
- 边界值漏掉
- 数据转换逻辑错误
- 异常处理不完整
- 重构后某个函数行为回归
集成测试擅长发现
- 模块之间接口约定不一致
- 状态没有正确同步到 UI
- 事件触发了,但链路中间断了
- 路由、权限、上下文注入有问题
- 真实组合后出现竞态、异步时序问题
这也是为什么很多 Bug 单靠单元测试抓不住。
因为现实里的问题经常不是某个函数本身错,而是:
- A 模块返回的数据结构和 B 模块预期不一致
- 状态更新了,但界面没订阅到
- 请求成功了,但副作用没触发
这些都更偏集成层问题。
六、前端项目里通常怎么分工更合理
更稳的实践不是“全写单测”或者“全写集成”,而是分层设计。
1. 适合优先写单元测试的内容
- 工具函数
- 日期、金额、权限等计算逻辑
- 自定义 Hook 的分支逻辑
- 复杂表单校验函数
- 数据格式转换和适配层
这些内容有几个特点:
- 输入输出清晰
- 外部依赖少
- 很适合高覆盖率
2. 适合补集成测试的内容
- 登录态切换
- 列表查询 + 筛选 + 分页
- 表单提交 + 成功失败反馈
- 购物车、结算、支付前确认
- 组件和 store、router、query 库之间的协作
这些内容的共同点是:
- 真正的风险在“链路是否打通”
- 单测把每一块都 mock 掉之后,反而测不出真实问题
七、一个很容易讲清楚的最小代码对比
1. 单元测试:测纯逻辑
import { describe, expect, it } from "vitest";
import { calcFinalPrice } from "./price";
describe("calcFinalPrice", () => {
it("按折扣计算最终价格", () => {
expect(calcFinalPrice(100, 0.2)).toBe(80);
});
it("非法折扣直接抛错", () => {
expect(() => calcFinalPrice(100, 1.5)).toThrow();
});
});
这里的特点是:
- 没有 UI
- 没有网络
- 没有状态容器
- 失败时基本一眼就能定位
2. 集成测试:测组件协作
下面这个例子假设页面依赖了 router、query 和接口返回数据:
import { render, screen } from "@testing-library/react";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import ProductPage from "./ProductPage";
const server = setupServer(
http.get("/api/products/1", () => {
return HttpResponse.json({
id: 1,
name: "机械键盘",
price: 399,
});
}),
);
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
it("能根据路由参数拉取并渲染商品详情", async () => {
const client = new QueryClient();
render(
<QueryClientProvider client={client}>
<MemoryRouter initialEntries={["/products/1"]}>
<Routes>
<Route path="/products/:id" element={<ProductPage />} />
</Routes>
</MemoryRouter>
</QueryClientProvider>,
);
expect(await screen.findByText("机械键盘")).toBeInTheDocument();
expect(screen.getByText("399")).toBeInTheDocument();
});
这段代码测到的不是某个函数本身,而是:
- 路由参数是否生效
- 页面是否发起请求
- 请求结果是否正确渲染
- 查询层和 UI 是否联通
这就是典型的集成测试。
八、什么时候应该 mock,什么时候不该 mock
这是区分两者时很高频的追问。
单元测试里
通常更愿意 mock 外部依赖,因为目标是把当前测试对象隔离出来。
比如:
- mock 请求模块
- mock 时间
- mock 随机数
- mock 子组件
集成测试里
不应该把“系统内部协作链路”全 mock 掉,否则就退化成了单元测试。
更常见的做法是:
- 保留真实的组件组合
- 保留真实的 store / router / context
- 保留真实的查询和状态更新逻辑
- 只 mock 不可控的外部系统,比如后端接口、第三方支付、浏览器能力
一句话总结就是:
- 单元测试通过 mock 来隔离问题。
- 集成测试通过少 mock 来验证连接。
九、面试里最容易被追问的几个问题
1. 集成测试是不是一定比单元测试高级
不是。
它们不是上下级关系,而是解决不同层次的问题。
- 单元测试负责快速、密集地兜住底层逻辑
- 集成测试负责验证真实协作链路
如果一个团队只写集成测试,通常会遇到:
- 反馈慢
- 用例重
- 问题定位困难
如果一个团队只写单元测试,又会遇到:
- 覆盖率看起来很高
- 但真实流程依然会坏
2. 组件测试到底算单元还是集成
要看边界,不看名字。
- 只测组件自身渲染分支,外部都 mock 掉,更偏单元测试
- 把组件放进真实上下文里,联通 store、router、query,更偏集成测试
3. 为什么前端更强调集成测试
因为前端很多问题都出在“协作面”:
- 组件树传参
- 状态管理同步
- 路由切换
- 异步请求和渲染时序
这些问题单测不一定覆盖得到,所以前端里常见建议是:
- 少写过度细碎、实现细节导向的组件单测
- 多写面向用户行为和模块协作的集成测试
4. 单元测试和集成测试谁该更多
一般是单元测试数量更多,集成测试数量更少但价值更高。
因为:
- 单元测试便宜,适合大面积铺开
- 集成测试贵,适合覆盖核心业务链路
这就是经典的“测试金字塔”思路。
易错点
- 把“是否跨文件”当成单元测试和集成测试的划分标准
- 在集成测试里把所有依赖都 mock 掉,结果只测到了假链路
- 在单元测试里强行引入 router、store、网络层,导致测试又慢又脆
- 只盯覆盖率数字,不看是否覆盖了真正高风险链路
- 把 E2E、集成测试、接口测试混成一类,回答时边界模糊
速记要点
- 单元测试 = 测局部逻辑,强调隔离
- 集成测试 = 测模块协作,强调连接
- 区分标准 = 测试边界划到哪,而不是测了几个文件
- 单元测试优点 = 快、稳、定位准
- 集成测试优点 = 更真实,能抓协作问题
- 工程落地 = 底层多单测,关键链路补集成测试