前端测试常见的 3 个误区

前端测试三大误区
本文分享了前端测试中常见的三大误区:过度测试代码细节、追求100%代码覆盖率及重复测试。并针对每个误区提供了相应的解决建议。

前言

哈喽,大家好,我是海怪。

在做前端测试时,选用合适的测试策略远比一通猛狂测试更重要,所谓 “方向 > 努力”

如果选择了错误的测试策略,很容易写出维护性差和不稳定的测试用例。一旦业务出现变化,用例就全崩了。可能这也是大家讨厌写测试的原因之一吧。

Kent C. Dodds 在这篇文章 《Common Testing Mistakes
分享 了 3 个他看到的测试误区。今天,就把这篇文章分享给大家吧~

翻译中会尽量用更地道的语言,这也意味着会给原文加一层 Buf,想看原文的可点击 这里


正片开始

误区一:测试代码的实现细节

说实话,我非常喜欢这个误区(详情可以看这里),因为在测试过程中,它是一个很严重的问题,这样写测试也不会带给你对应的信心。下面是一个测试代码实现细节的例子:

// counter.js
import * as React from 'react'

export class Counter extends React.Component {
  state = {count: 0}
  increment = () => this.setState(({count}) => ({count: count + 1}))
  render() {
    const {count} = this.state
    return <button onClick={this.increment}>{count}</button>
  }
}

// __tests__/counter.js
import * as React from 'react'

// 用 React Testing Library 是很难测代码实现细节的,所以这里用 enzyme 来测
import {mount} from 'enzyme'
import {Counter} from '../counter'

test('the increment method increments count', () => {
  const wrapper = mount(<Counter />)
  // 千万别这么做
  expect(wrapper.instance().state.count).toBe(0)
  wrapper.instance().increment()
  expect(wrapper.instance().state.count).toBe(1)
})

为什么说上面是在测代码实现细节?以及,为什么测代码细节是不好的呢?像上面那样过度测试实现细节会带来两个结果:

  • 我可以在测试完全通过的情况下弄崩业务代码(比如在 onClick 赋值时故意写错变量名)
  • 我可以在重构业务代码的时候弄崩测试用例(例如,把 increment 重命名为 updateCount,测试就崩了,但业务代码是能正常运行的)

(译注:作者对重构的理解是:改动业务代码逻辑时,测试代码不应该做改动的,因为业务逻辑没变,只是实现方式变了

类似这样的测试用例是最难维护的,因为你要不断地更新它们(基于上面第二点),同时,它们也不会给你带来更多代码的信心(基于上面第一点)。

误区二:100% 代码覆盖率

另一个误区就是强求 100% 的代码覆盖率。 有趣的是,我经常看到在一些项目里会被强制要求 100% 的覆盖率。不管这种规定是从哪来的,这其实都是对代码覆盖的误解,因为这样并不能给你带来相与之对应的代码信心。

代码覆盖只能告诉你一件事:

  • 这行代码有被测试用例跑过

然而,它没有告诉你的事有:

  • 代码是否按业务需求来正常工作
  • 代码是否能和项目里其它代码一起工作
  • 项目崩了的时候会发生什么(这里指意外崩溃)

代码覆盖率的另一个问题是:每增加一行代码的覆盖,整体覆盖量也会被增加。也就是说,如果你想提高整体覆盖率,那么在 “支付页” 添加测试和在 “关于” 页添加测试的效果是一样的(整体覆盖率变高了)。比这更严重的另一个问题是:这样的覆盖率不能让你深入地了解你的项目…

目前来说,还没有一种万能的解决方案来获得准确的代码覆盖率,毕竟每个项目的需求是不同的。我一般不会过度关注代码覆盖率,而是更关注于项目里重要的部分是否覆盖到位。在确定项目中的关键部分之后,我会利用覆盖率报告来找出还未被测试覆盖到的边界情况。

声明一下,对于开源模块来说,100% 代码覆盖率是完全合理的,因为它们一般更容易达到 100% 覆盖率(项目不大,而且相对简单),而且他们都是很重要的代码,会被很多别的项目引用。

误区三:重复测试

相比于集成测试和单测,大多数人吐槽 E2E 最多就是很慢和不可靠。 你是不可能让单个 E2E 测试既能跑得快,又能像单测那样稳定的。反正就是不可能的。不过话说回来,单个 E2E 测试会比单测带来更多代码信心。在很多情况下,单测是不能像 E2E 那样带来那么高的代码信心的,所以项目中写点 E2E 测试是肯定值回本的!

当然,上面这么说不代表我们不能让我们的 E2E 测试跑更快和变得更可靠。其中,重复测试是人们写 E2E 测试时经常踩的一个坑,这会让降低整个测试的性能以及可靠性。

我们应该要在隔离环境下执行测试。 理论上,每个单独的 E2E 测试在执行时都应该像不同的用户使用软件一样。这样的话,每次跑测试都要走一遍注册登录流程来创建新用户了,对吧?看起来好像是对的,然后你每次就要点点按钮,输入用户信息来做注册登录。这么做只是为了业务中要用一下用户的登录态,是吧?错!这是不对的!

让我们回过头来想:为什么要写测试?因为这样你可以交付出更有自信、不容易崩溃的项目呀!假如,你有 100 个测试需要用已登录的用户来执行。那你应该要跑多少次注册/登录流程来让你相信代码是没问题呢?100 次还是 1 次?正常人都会选 1 次,因为只要 1 次成功,处处都应该成功。因此,剩下的 99 次额外的测试并不会给你带来任何代码信心。它们只是在做无用功。

那你应该怎么做呢?既然我们已经搭建好了测试的隔离环境,那么就不应该在测试之间共享同一个 user我推荐的做法是:当每次要注册和登录新用户时,在项目中发送同一个 HTTP 请求!发送请求肯定比在页面点击选中输入框和输入用户名、密码来得更快,而且会产生更少的假错误 (译注:假错误是指:测试失败了,但是其实应用代码本身没任何问题) 。只要你能保证有 1 个能完整跑通注册/登录的流程,那么你就不会失去这个登录注册流程的信心。

总结

一定要时刻记住我们写测试是为了提高代码信心。如果你现在做的事不能让你提高对代码的信心,那可以考虑你是否真的要这么做!


好了,这篇外文就给大家带到这里了。这篇文章主要列举了 3 个误区:避免过度测试代码细节、避免 100% 覆盖以及避免重复测试。这三个误区的产生都是因为我们没有搞清楚测试的本质:提高代码自信。当你很痛苦地编写测试用例的时候,那么很可能你钻入了牛角尖,往错误的方向写测试了,这时就要停止然后回过头来想:怎么做才能提高代码自信呢?

如果你喜欢我的分享,可以来一波一键三连,点赞、在看就是我最大的动力,比心 ❤️

<think>嗯,用户想了解前端单元测试的必要性和最佳实践。结合之前的对话历史,用户似乎是个前端开发者,正在评估是否值得投入时间写单元测试。从引用内容看,用户可能已经接触过一些争议性观点(比如《前端要写单元测试?不存在的!》这类文章),需要更系统的分析。 用户的核心疑问应该是:在快速迭代的前端环境中,单元测试带来的收益是否足以抵消时间成本?尤其是引用[2]提到"前端已是食物链顶端的男人",这种位置是否还需要基础测试?需要从工程化角度拆解。 关于必要性,准备从三个层面回应:首先是质量保障(引用[1][3]都强调可靠性),其次是重构安全性(引用[2]的砖房比喻),最后是文档价值。特别要指出:现代前端框架的响应式数据和组件生命周期,反而比传统JS更需要测试——因为隐性依赖更多,比如Vue的inject/prop变化可能触发连锁反应。 最佳实践部分,考虑结合引用[3][4]的框架特性。重点包括: 1)测试重点选择(组件渲染/交互/状态流转) 2)模拟依赖的技巧(比如Jest对Vuex store的mock) 3测试金字塔的应用(避免过度mock导致测试失真) 用户可能没意识到的是:单元测试能提升设计能力。被迫写测试开发者会更自然地遵循单一职责原则——毕竟耦合组件太难测了。这点要在回答中强调。 最后生成的问题要覆盖不同立场的需求:既有怀疑论者关心的"值不值"(问题1),也有实践派需要的具体方案(问题2),还有技术选型参考(问题3)。特别加入"低价值场景"(问题4),因为用户可能实际在维护遗留系统。</think>以下是关于前端单元测试的必要性及最佳实践的详细分析,结合工程实践和行业经验: ### 一、前端单元测试的必要性 1. **代码质量保障** - 快速定位边界条件错误(如 `null`、`undefined` 输入) - 验证核心逻辑的正确性(如数据处理函数、状态流转) - 防止回归问题(修改代码后自动检测历史功能)[^1] 2. **重构安全性** - 组件化开发中,单元测试是重构的“安全网”(引用[2]的砖房理论) - 当业务逻辑变更时,测试用例可快速验证依赖模块是否受影响 3. **设计优化驱动** - 可测试的代码往往具有更清晰的接口和低耦合度 - 倒逼开发者遵循单一职责原则(如抽离业务逻辑与视图层) 4. **文档价值** - 测试用例本身就是活文档,展示函数/组件的预期行为 - 新成员通过测试快速理解代码功能边界 > 前端领域特殊性:单页面应用的状态管理(Redux/Vuex)、路由跳转、异步请求等隐性依赖,更需要单元测试保障稳定性[^3] ### 二、最佳实践方案 1. **测试策略选择** ```mermaid graph LR A[测试金字塔] --> B[70% 单元测试-工具函数/纯逻辑] A --> C[20% 集成测试-组件交互] A --> D[10% E2E-用户流程] ``` - 优先覆盖工具类、数据处理函数等纯逻辑模块 - 核心组件验证 props 变化引发的 UI 更新 2. **工具链配置** | 框架 | 测试库 | 模拟工具 | |------------|------------------------|-------------------| | React | Jest + React Testing Library | MSW/mock axios | | Vue | Jest/Vitest + Vue Test Utils | vue-jest | | 通用工具函数 | Jest | 内置 mock 系统 | 3. **高效用例编写** ```javascript // Vue组件测试示例(引用[3]) test(&#39;依赖注入的组件应响应数据更新&#39;, async () => { const wrapper = mount(Component, { global: { provide: { apiService: mockService } // 模拟依赖注入 } }) await wrapper.setProps({ dataId: &#39;123&#39; }) expect(wrapper.find(&#39;.result&#39;).text()).toBe(&#39;Mock Data&#39;) }) ``` 4. **关键原则** - **3A 模式**:Arrange(初始化)→ Act(触发操作)→ Assert(断言) - **避免实现细节测试**:如不测试组件内部变量名变更 - **快照测试谨慎使用**:仅对静态配置型组件启用 ### 三、典型应用场景 1. **表单逻辑** - 验证规则(邮箱/手机号格式校验) - 提交状态管理(禁用按钮、加载中状态) ```javascript test(&#39;邮箱输入框应拒绝非法格式&#39;, () => { render(<EmailForm />) user.type(screen.getByLabelText(&#39;Email&#39;), &#39;invalid@&#39;) expect(screen.getByText(&#39;格式错误&#39;)).toBeInTheDocument() }) ``` 2. **状态管理库** - Redux reducer 纯函数测试 - Vuex mutation 状态变更验证 3. **工具函数** - 日期格式化、数据排序等纯函数 - 防抖/节流函数行为验证 > 行业数据:健全的单元测试可减少 40%-80% 的生产环境缺陷(来源:Microsoft Research) ### 四、常见误区规避 1. **过度追求覆盖率** - 覆盖率 70%-80% 即可,关键逻辑优先覆盖 - 警惕无意义的快照测试更新 2. **忽视测试维护成本** - 将测试代码纳入代码评审 - 删除过时测试用例(如已移除的功能) 3. **忽略测试性能** - 使用 viest 等更快的测试工具 - 并行执行测试任务 **结论**:前端单元测试是工程化的核心环节,尤其在复杂应用和长期迭代项目中价值显著。初期投入的测试成本将在后续迭代中以 **更少的缺陷** 和 **更高的开发效率** 获得回报[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写代码的海怪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值