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

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

现在很多单元测试的教程都是那种很简单的比如,测个 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 分钟前
【每日学点HarmonyOS Next知识】Web交互、列表拖拽、横屏后布局、Event序列问题、Scroll与Web组合
前端·交互·harmonyos·harmonyosnext
GISHUB16 分钟前
mapbox开发小技巧
前端·mapbox
几度泥的菜花38 分钟前
使用jQuery实现动态下划线效果的导航栏
前端·javascript·jquery
思茂信息1 小时前
CST直角反射器 --- 距离多普勒(RD图), 毫米波汽车雷达ADAS
前端·人工智能·5g·汽车·无人机·软件工程
星星不打輰1 小时前
Vue入门常见指令
前端·javascript·vue.js
好_快1 小时前
Lodash源码阅读-isNative
前端·javascript·源码阅读
好_快2 小时前
Lodash源码阅读-reIsNative
前端·javascript·源码阅读
好_快2 小时前
Lodash源码阅读-baseIsNative
前端·javascript·源码阅读
好_快2 小时前
Lodash源码阅读-toSource
前端·javascript·源码阅读
Oneforlove_twoforjob2 小时前
volta node npm yarn下载安装
前端·npm·node.js