前端测试最强教程 (1/n)-玩转vitest

前言

本人咨询了一些大众小厂工作的朋友,也在一些技术群里面交流了一番。发现目前中文前端圈对测试的态度是非常轻视的。小厂不测试,大厂靠人力,中厂只是简单的写一写。我总结了一下几条原因

  1. 没有时间,工时固定,如果要写好测试花费的是要比写业务的时间长的
  2. 测试没用,都是自导自演,大部分单测没有意义,写完了永远也不会出错,但是在重构的时候还得把对应的测试改一下
  3. 有 QA 团队保证交付质量,不需要自己写测试
  4. 写测试太麻烦了,要 mock 一大堆数据、api、事件
  5. 业务变化频繁,没有写测试的不要

本人所在的团队是一个非常注重写测试的团队,测试代码与业务代码行数比大约为 2:1,覆盖率常年保持在 80%以上。所以我来分享一下我们团队写测试遇到的一些问题,希望能给大家带来帮助。

先说结论

写测试非常重要,每个前端仔都要学习,理由有

  1. 趁着国内还没有形成写测试的风气,如果你会写好的测试,那么会是面试的一个极大的加分项。
  2. 写测试是为自己的代码负责,能够减少变屎山的概率
  3. 好的测试是可以直接当成文档来用的,如果你的项目测试覆盖率 99%,那么你就不需要写项目文档,任何业务问题,其他人都可以通过看测试 case 来找到答案,一举两得。
  4. 好的测试用例是可以一直跑的,不惧任何重构,降低上线负担,这是给自己减负

代码

本次教程的代码都在 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-istanbuljsdomvitest。 修改 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>
}

可以看到业务逻辑除了基础的展示之外,有区别的点就在

  1. 如果有 pics,那么就展示图片,没有就展示一小段正文。
  2. 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();
    });
});

需要注意的点有

  1. react testing library每次使用必须要清理,否则会影响下一次的断言。
  2. 只有 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 testnpm run coverage

可以看到doc-item 组件的测试覆盖率已经达到了 100%🎉

结束

第一节测试基础就这些,主要讲了搭建 vitest,规范,对一个组件进行 case design,写测试,查看覆盖率等。

看到这可能有的看官老爷已经要准备开骂了"你这写的什么玩意,这跟着文档有手不就能吗?你这不还是单测吗?不还是自导自演吗?"

别着急,后面才是重头戏,在后面的教程中,我会带领大家搭建 fake-server fake-db,fake-ws,以及重中之重《优雅搭建集成测试》。

相关推荐
逐·風3 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫3 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦4 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子4 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享5 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf7 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨7 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL7 小时前
npm入门教程1:npm简介
前端·npm·node.js
小白白一枚1118 小时前
css实现div被图片撑开
前端·css