我在团队内部提倡禁用单元测试

先说结论: 在绝大部分的业务前端开发中,写单元测试的收益非常小,但是带来的维护成本却非常大,请不要再写单元测试感动自己,为难同事了。

现在很多单元测试的教程都是那种很简单的比如,测个 1+1=2,这需要测吗?下面这段代码已经出现过很多次了,纯纯的误导人。

js 复制代码
const sum = (a,b) => a+b
it('1+1=2',()=> {
    expect(sum(1,1)).toBe(2)
})

稍微上点复杂度,来写个组件的单测,比如一个用户信息展示组件,叫 UserInfoBlock,支持设置头像的大小,点击名字支持跳转到个人主页,组件代码大致长这样

ts 复制代码
interface UserInfoBlockProps {
    name: string
    size: 16 | 24
    id: string
    icon: string
}
export const UserInfoBlock:FC<UserInfoBlockProps> = (props) => {
    const navigate = useNavigate()
    return <div 
            class='xxxxxx' 
            style={{width: props.size}} 
            onClick={() => {navigate(`/user/${props.id}`)}}>
                <img src={props.icon}/>
                {props.name}
            </div>
}

然后开始给组件写单测,先测头像大小的功能

ts 复制代码
import { UserInfoBlock, UserInfoBlockProps } from './user-info-block'
import { fireEvent, render, screen } from '@testing-library/react'
describe('UserInfoBlock 组件单测', () => {
    const userInfo:UserInfoBlockProps = {
        name: '张三',
        icon:'https://xxx.png',
        id:'abcd1234',
        size: 16
    }
    const userInfoLarge:UserInfoBlockProps = {
        name: '张三',
        icon:'https://xxx.png',
        id:'abcd1234',
        size: 24
    }
    describe('展示默认头像大小', () => {
        const component = render(<UserInfoBlock {...userInfo}/>)
        it('img 标签的宽度为 16px', () => {
            expect(screen.getByTag('img').style.width).toBe(16)
        })
    })
    describe('展示large头像大小', () => {
        const component = render(<UserInfoBlock {...userInfoLarge}/>)
        it('img 标签的宽度为 24px', () => {
            expect(screen.getByTag('img').style.width).toBe(24)
        })
    })
})

接下来测一下跳转,因为用了 react-router,所以在渲染组件的时候必须包裹一下 RouterProvider

ts 复制代码
...
    describe('点击可以跳转', () => {
        const component = render(<MemoryRouter>
            <UserInfoBlock {...userInfoLarge}/>
        </MemoryRouter>
        )
        fireEvent.click(component.div)
        it('url 会变成 user/abcd1234', () => {
            expect(location.pathname).toBe('user/abcd1234')
        })
    })
...

这个组件的测试就写完了,看起来挺有用,但实际没啥用。

首先这个测试的收益不高,因为这是一个很简单的组件,五分钟写完,但是写这个测试需要翻倍的时间,因为需要构造数据,之前没有经验不知道要加 MemoryRouterjestlocation 对象不方便,还要 mock 一下。等把这一套搞完了才能跑通,这就好像你疯狂锻炼,练出麒麟臂,就是为了举自拍杆举的稳一点。如果组件内要发请求,要做的准备工作就更加多了。

其次,user/abcd1234是什么,断言这个没用,因为别人改了链接,你的测试也一样会过,应该断言成功的打开了用户主页,比如断言一个必定会存在的文字expect(screen.getByText('用户详情')).toBeInDocument()这才能证明真的打开了用户主页。

1+1 什么时候不等于 2。头像设置 16px,什么时候不会是 16 px。什么时候点击不跳转到用户主页。肯定是有人修改了这里的业务逻辑才会这样,只有在做产品需求,代码优化的时候才会去改以前的代码。那么这时,这段测试对接手这段代码的人就是负担。

假设我接到需求,用户主页url 变了,改成 user?id=xxx。我心想这个简单,一秒钟解决,提了 pr 发现测试挂了,然后我就得把测试也改一下。

如果一段测试代码在我每次修改需求的时候都要改,甚至有的时候还会为了便于测试在业务代码中留一些后门,那这个测试就是纯纯的副作用。

大家肯定深有体会,一般一个模块自己不会出问题,但是多个模块组合起来就很容易出问题了,所以测试的重点不应该是一个组件内部各种细节,而应该测这个组件在被使用的地方是否正确。

举个例子,比如在一个列表里面,使用头像为 24px 的用户组件,然后有一天出 bug 了,说这里变成 16 了。那这会是组件的 bug 吗,这肯定是使用的地方没有用对。所以应该要测试的是这里。如果有个测试断言了在列表中用户头像必须为 24px,即使没有组件的单测,这个组件也不会被改坏,因为上层已经覆盖到了。

什么是好的测试?

  1. 我认为好的测试是稳定的测试,不仅不需要经常修改,甚至还能扛住代码重构。比如你把项目从 react 完全迁移到了 vue,测试都能跑过。
  2. 好的测试是可以当作项目文档的,组内来新人了,不需要给他介绍过多,有问题让他自己看测试用例就能理解业务。

那么怎么写出这样的测试呢?答案就是写集成测试,集成测试说白了就是一个超级大的单测,之前测试 render 的是一个组件,现在直接 render(<App/>),将路由跳转,请求 mock,fake数据库等等都统一处理。可以将上面写的测试简单的改为

ts 复制代码
import { creatAppContext, AppContext } from './app-test-context'
describe('S: 测试头像组件', () => {
    let _:AppContext
    beforeEach(() => {
        - = creatAppContext()
    })
    describe('W: 去用户列表也', () => {
        beforeEach(() => {
            _.click(screen.getByText('查看用户列表'))
        })
        it('T: 列表页有十个用户,用户头像是 24px', ()=>{
            expect(screen.getAllByTestid('user-item').length).toBe(10)
            expect(screen.getAllByTestid('user-item')[0].img.style.width).toBe(24)
        })
        describe('W: 点击第一个用户', () => {
            beforeEach(() => {
                _.click(screen.getAllByTestid('user-item')[0])
            })
            it('T: 打开了用户主页', () => {
                expect(screen.getByText('用户详情')).toBeInDocument()
            })
        })
    })
})

关于怎么写好测试可以展开的点实在太多了,就不过多阐述,想要了解的朋友可以查看我的主页,之前专门出过一些列文章详细的讲这些事情。

相关推荐
超哥--1 小时前
B站视频内容智能分析系统(九):React 前端与管理面板
前端·react.js·前端框架
Cutecat_4 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
qq_422152575 小时前
PDF 加水印工具怎么选?2026 年文档版权保护方案对比
前端·pdf·github
kyriewen5 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
brucelee1866 小时前
OpenClaw 浏览器控制(Chrome MCP)完整教程
前端·chrome
ct9786 小时前
React 状态管理方案深度对比
开发语言·前端·react
胡志辉的博客6 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖6 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty6 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
AI焦点7 小时前
跨越协议鸿沟:Tool Use状态机从Anthropic到OpenAI兼容体系的适配要点
前端·人工智能