前言
本人咨询了一些大众小厂工作的朋友,也在一些技术群里面交流了一番。发现目前中文前端圈对测试的态度是非常轻视的。小厂不测试,大厂靠人力,中厂只是简单的写一写。我总结了一下几条原因
- 没有时间,工时固定,如果要写好测试花费的是要比写业务的时间长的
- 测试没用,都是自导自演,大部分单测没有意义,写完了永远也不会出错,但是在重构的时候还得把对应的测试改一下
- 有 QA 团队保证交付质量,不需要自己写测试
- 写测试太麻烦了,要 mock 一大堆数据、api、事件
- 业务变化频繁,没有写测试的不要
本人所在的团队是一个非常注重写测试的团队,测试代码与业务代码行数比大约为 2:1,覆盖率常年保持在 80%以上。所以我来分享一下我们团队写测试遇到的一些问题,希望能给大家带来帮助。
先说结论
写测试非常重要,每个前端仔都要学习,理由有
- 趁着国内还没有形成写测试的风气,如果你会写好的测试,那么会是面试的一个极大的加分项。
- 写测试是为自己的代码负责,能够减少变屎山的概率
- 好的测试是可以直接当成文档来用的,如果你的项目测试覆盖率 99%,那么你就不需要写项目文档,任何业务问题,其他人都可以通过看测试 case 来找到答案,一举两得。
- 好的测试用例是可以一直跑的,不惧任何重构,降低上线负担,这是给自己减负
代码
本次教程的代码都在 github.com/alpacachen/... 中,(注:这里面的项目搭建和业务代码是我一两个小时迅速搞定的,写的粗糙,不代表本人真实水平,重点在测试代码上)
step 1: 搭建基础
vite+react+typescript+unocss+react-router+antd迅速搭建一个项目,需求为两个页面,首页和收藏页,在首页点击收藏按钮,会收藏文件到收藏页中,因为没有后端,所以我代理了一下虎扑的接口。
js
server: {
proxy: {
'/hupu': {
target: 'https://m.hupu.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/hupu/, ''),
},
}
},
页面大致如下
step 2: 引入 vitest
安装依赖 @testing-library/react
,@vitest/coverage-istanbul
,jsdom
,vitest
。 修改 vite.config.ts
在 package.json中加入两条命令
js
"test": "vitest",
"coverage": "vitest run --coverage",
运行一下
可以看到我们的测试基础设施已经搭建好了
step 3: case design
现在可以开始写测试了,我们先写doc-item组件的测试,代码在这里github.com/alpacachen/...
ts
interface Props {
data: DataType;
actions: ReactNode[];
}
export const DocItem: FC<Props> = ({ data, actions }) => {
const thumbnail = data.picList[0]?.url ?? ''
const avatar = data.userHeader
return <Card
style={{ width: 300 }}
cover={
<div className='h-145px overflow-hidden'>
{thumbnail ? <img
crossOrigin='anonymous'
alt="example"
referrerPolicy='no-referrer'
src={thumbnail}
/> : data?.lightReplyResult?.content}
</div>
}
actions={actions}
>
<Card.Meta
avatar={<img className='rounded-full w-8 h-8' referrerPolicy='no-referrer' crossOrigin='anonymous' src={avatar} />}
title={data.title}
description={data.recommendCount + '赞 ' + data.replies + '回复'}
/>
</Card>
}
可以看到业务逻辑除了基础的展示之外,有区别的点就在
- 如果有 pics,那么就展示图片,没有就展示一小段正文。
- actions支持传入一堆 react 组件
那么我们就主要测试这两点,对这两点进行 case design,写出他的 GWT 用例
(这简单介绍下 GWT,GWT 代表 GIVEN WHEN THEN,说白话就是 在特定上下文中,做了什么,会发生什么。可以没有 WHEN,直接 GIVEN THEN)
这个需求的 case 很简单
XML
GIVEN: doc-item组件,传入数据带图片
TEHN: 会展示img 标签
GIVEN: doc-item组件,传入数据不带图片
TEHN: 会展示返回数据种的文本
GIVEN: doc-item组件,actions 传入 一个 button,button 点击会弹出 alert
WHEN: 点击 button
TEHN: 会弹出 alert
step4: 写测试
在 src 同级创建一个test 目录,所有的测试代码都放在这里管理
新建文件doc-item.test.tsx
写测试代码,大致结构如下
ts
import { expect, test, describe, beforeEach, afterEach } from 'vitest'
import { render, screen, cleanup } from "@testing-library/react";
describe("G: XXXX", () => {
beforeEach(() => {
render(<DocItem data={mockDataWithoutPic} actions={[]} />);
})
afterEach(() => {
cleanup()
})
test("T: xxxx", () => {
expect(xxxx).toBeTruthy();
});
});
需要注意的点有
- react testing library每次使用必须要清理,否则会影响下一次的断言。
- 只有 THEN 用 test 方法包裹,GIVEN 和 WHEN 都要用 describe,任何行为都要写到 beforeEach 中,以便于后面继续拓展(这个不是什么规范,而是我写多年测试总结出的经验,强烈建议大家按照这个风格写)
对于我们这个 case 写出来的测试最终就是这个样子
ts
import { expect, test, describe, beforeEach, afterEach } from 'vitest'
import { render, screen, cleanup } from "@testing-library/react";
import { DocItem } from '../../component/doc-item';
import { DataType } from '../../types/type';
import { vi } from 'vitest'
const mockDataWithoutPic: DataType = {
title: 'mock title',
recommendCount: 10,
replies: 20,
topicIcon: '',
userHeader: '',
tid: 1,
picList: [],
lightReplyResult: {
content: '部分内容'
}
}
const mockDataWithPic: DataType = {
title: 'mock title',
recommendCount: 10,
replies: 20,
topicIcon: '',
userHeader: '',
tid: 1,
picList: [
{ url: 'http://www.baidu.com' }
],
lightReplyResult: {
content: '部分内容'
}
}
describe("G: 一个DocItem组件, 入参没有图片", () => {
beforeEach(() => {
render(<DocItem data={mockDataWithoutPic} actions={[]} />);
})
afterEach(() => {
cleanup()
})
test("T: 会展示部分文章内容", () => {
expect(screen.getByText("部分内容")).toBeTruthy();
});
});
describe("G: 一个DocItem组件, 入参有图片", () => {
beforeEach(() => {
render(<DocItem data={mockDataWithPic} actions={[]} />);
})
afterEach(() => {
cleanup()
})
test("T: 会展示图片,不展示正文内容", () => {
expect(screen.queryByText("部分内容")).not.toBeTruthy();
expect(screen.getByTestId("thumbnail")).toBeTruthy();
});
});
describe("G: 一个DocItem组件, 入参有图片, 并且 action 传入了一个按钮,支持点击", () => {
beforeEach(() => {
render(<DocItem data={mockDataWithPic} actions={[
<button onClick={() => alert('我是 alert')}>点我</button>
]} />);
})
afterEach(() => {
cleanup()
})
describe('W: 点击 action 按钮', () => {
beforeEach(() => {
vi.spyOn(window, 'alert');
screen.getByText('点我').click()
})
test("T: 会弹出系统提示框 alert", () => {
expect(window.alert).toBeCalledWith('我是 alert');
});
})
});
接下来运行一下 npm run test
和 npm run coverage
可以看到doc-item 组件的测试覆盖率已经达到了 100%🎉
结束
第一节测试基础就这些,主要讲了搭建 vitest,规范,对一个组件进行 case design,写测试,查看覆盖率等。
看到这可能有的看官老爷已经要准备开骂了"你这写的什么玩意,这跟着文档有手不就能吗?你这不还是单测吗?不还是自导自演吗?"
别着急,后面才是重头戏,在后面的教程中,我会带领大家搭建 fake-server fake-db,fake-ws,以及重中之重《优雅搭建集成测试》。