前端接入单元测试(Node+React)

2023-06-14 17:42:32 浏览数 (1)

意义

假如要重构一个老前端框架,并根据其开发一个向后兼容的新框架。此时老框架针对其内部API函数,写了充分的单侧用例。在开发新框架时,直接运行老前端框架的单侧用例,如果所有测试用例都通过,则可快速保证内部api的一致性,快速验证所有功能。

  • 保障代码质量和功能的实现的完整度
  • 提升开发效率,提前发现和定位bug
  • 便于项目维护,后续重构也能快速测试保证功能正常。

主流测试工具比较

框架

断言

仿真

快照

异步测试

Mocha

默认不支持,可配置

默认不支持,可配置

默认不支持,可配置

友好

Ava

默认支持

不支持,需第三方配置

默认支持

友好

Jasmine

默认支持

默认支持

默认支持

不友好

Jest

默认支持

默认支持

默认支持

友好

Karma

不支持,需第三方配置

不支持,需第三方配置

不支持,需第三方配置

不支持,需第三方配置

Mocha

  • Mocha 是生态最好,使用最广泛的单测框架,但是他需要较多的配置来实现它的高扩展性。

Ava

  • Ava 是更轻量高效简单的单测框架,但是自身不够稳定,并发运行文件多的时候会撑爆 CPU。

Jasmine

  • Jasmine 是单测框架的“元老”,开箱即用,但是异步测试支持较弱。

Jest

  • Jest 基于 Jasmine, 做了大量修改并添加了很多特性,同样开箱即用,但异步测试支持良好。

Karma

  • Karma 能在真实的浏览器中测试,强大适配器,可配置其他单测框架,一般会配合 Mocha 或 Jasmine 等一起使用。

每个框架都有自己的优缺点,没有最好的框架,只有最适合的框架。Augular 的默认测试框架就是 Karma Jasmine,Egg默认测试框架是Mocha,而 React 的默认测试框架是 Jest。node测试框架因为egg内置Mocha,因此不额外引入jest。 Jest 被各种 React 应用推荐和使用。它基于 Jasmine,至今已经做了大量修改并添加了很多特性,同样也是开箱即用,支持断言,仿真,快照等。Create React App 新建的项目就会默认配置 Jest,我们基本不用做太多改造,就可以直接使用。

单元测试

  1. node单元测试 egg单侧语法参考
  • controller测试
代码语言:javascript复制
const { app, assert } = require('egg-mock/bootstrap');
describe('search test', () => {
 it('search test', () => {
 return app.httpRequest().get('/search').expect(200);
});
it('search test', () => {
 return app.httpRequest().get('/categories').expect(200);
 });
});
  • service测试
代码语言:javascript复制
const { app, assert } = require('egg-mock/bootstrap');
describe('product service', () => {
 	it('get product category', async () => {
 	const ctx = app.mockContext();
 	const res = await ctx.service.product.getCategoryList();
 	assert(res);
 });
});
  • extend方法测试
代码语言:javascript复制
const { app, assert } = require('egg-mock/bootstrap');
describe('helper test', () => {
  it('jsonKeysToCase',  () => {
  const ctx = app.mockContext();
  const res = ctx.helper.jsonKeysToCase({
 	List: [{
	ProductId: "25502"
 }]
 }, 3);
assert(res.list[0].productId === '25502');
});
});
  • 页面请求测试
代码语言:javascript复制
describe('页面拨测', () => {
 it('product test', async() => {
  const ctx = app.mockContext();
  let res= await Promise.all(urls.map(v=> ctx.curl(v)));
  assert(res.every(v=>v.status===200))
 });
});

React单元测试 官方文档

环境安装
  1. 安装依赖
代码语言:javascript复制
npm i --save-dev jest@27.4.3 
npm i --save-dev babel-jest@27.4.2
npm i --save-dev @testing-library/jest-dom@5.16.5  @testing-library/react@13.4.0
  1. 添加jest.config.js
代码语言:javascript复制
module.exports = {
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    "\.(css|scss)$": "identity-obj-proxy"
  },
  testMatch: ["<rootDir>/test/**/*.{spec,test}.{js,ts}"],
  transform: {
    "^. \.[t|j]sx?$": "babel-jest"
  },
  // 覆盖率设置
  coverageThreshold:{
    global:{
      statements: 50,
      branches: 50,
      functions: 50,
      lines: 50
    },
    "./src/components/button": {
      branches: 50
    },
  }
}
  1. 添加babel.config.js
代码语言:javascript复制
module.exports = {
  presets: [
  	'@babel/preset-env',
  	'@babel/preset-react',
  ],
}
  1. package.json添加scrpit "test": "jest --coverage"
单元测试编写
  • 测试业务逻辑
代码语言:javascript复制
describe('formatDate测试', ()=>{
 it('formate 0 返回 1970-1-1', () => {
 	expect(formatDate(0)).toBe('1970-1-1');
 });
 test('formate -1 返回 1969-12-31', () => {
 	expect(formatDate(0)).toBe('1970-1-1');
 });
 test('two plus two is four', () => {
 	expect(2   3).toBe(5);
 });
})
  • 测试异步方法
代码语言:javascript复制
function fetchData() {
  return new Promise((resolve) => {
 	setTimeout(() => {
 	resolve('Hello world')
 	}, 1000);
  })
}

test('Promise', () => {
  return fetchData().then(data => {
    expect(data).toBe('Hello world')
  })
})
  • 测试react组件,最开始使用Enzyme,后面从React脚手架创建的项目自带React Testing Library(RTL),官方推荐使用RTL
代码语言:javascript复制
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Todo from "../../src/components/Todo";
import "@testing-library/jest-dom/extend-expect";
it("components Todo", () => {
  const { getByTestId } = render(<Todo />);
  const todos = getByTestId("todos");
  expect(todos.children.length).toBe(2);
});
  • Mock模块
代码语言:javascript复制
// fetch.js
import axios from 'axios';
export default {
  async fetchPostsList(callback) {
	  return axios.get('https://jsonplaceholder.typicode.com/posts').then(res => {
		return callback(res.data);
	  })
  }
}
代码语言:javascript复制
import fetch from '../src/fetch.js'
test('fetchPostsList中的回调函数应该能够被调用', async () => {
  expect.assertions(1);
  let mockFn = jest.fn();
  await fetch.fetchPostsList(mockFn);
  // 断言mockFn被调用
  expect(mockFn).toBeCalled();
})
  • 测试快照用法 如果频繁修改业务代码时,对应的测试用例可能也要修改。那么如何避免这个问题呢?使用Snapshot快照可以解决。 用法:
代码语言:javascript复制
describe('Demo Basic Simple', () => {
  const childTitleText = 'This is Child Title';
  const ChildNameText = 'Jane Austen';
  test('Test Render', async () => {
  	const ins = render(<Demo />);
  	expect(ins.baseElement).toMatchSnapshot();
  })
 })

快照执行流程:

  • 第一次执行toMatchSnapshot,会将expect中的结果生成一个快照
  • 修改组件保存后,第二次执行toMatchSnapshot,会再次生成快照,和上次快照对比,如果一致,则测试通过,如果不一致,测试不通过,说明组件有改动
  • 更新快照对比结果:npm test – -u

了解测试覆盖率 Statements 语句覆盖率,它其实对应的就是js语法上的语句,js解析成ast数中类型为 statement 。 Branches 分支覆盖率,通俗点理解就是 if/else 这类条件 Functions 函数覆盖率 Lines 行数覆盖率,就是代码执行了多少行

自动化测试

对于前端来说,主要关注单元测试、集成测试、E2E测试 集成测试:测试应用中不同模块如何集成,如何一起工作。目的在于,测试经过单元测试后的各个模块组合在一起是否能正常工作。会对组合之后的代码整体暴露在外接口进行测试,查看组合后的代码工作是否符合预期。 E2E测试:端到端测试, 聚焦于用户和 web 之间的交互,把 web 当作一个黑盒,站在用户的角度,模拟用户的操作,判断每次操作的结果是否符合预期。有些人也把UI自动化测试称为E2E测试

可以看出,单元测试是整个测试组合的基石,

  • QTA自动化测试 http://qta.woa.com/marketnode/autotest/task/105307/history 优点: 可以作为任务定时去执行,可以和蓝盾配合使用 缺点:需要添加项目和任务,执行时间长,node没有对应的mocha库,需要额外安装jest库
  • TestOne DWT 前端自动化测试 http://testone.woa.com/dwt/tiyan#/docs/getStarted 可视化查询测试结果,可结合蓝盾插件和质量红线做流水线测试,整个配置比较重,耗时,目前项目缺少测试用例,可在后续集成。
  • orange-ci跑单元测试 优点:配置简单,和现有的工作流集成在一起,可以在构建前执行测试用例,执行效率高…

总结

  • node项目可以利用egg自带的测试工具,针对controller, service, extend, helper等模块编写单元测试,特别是controller重要的路由需要做单元测试;
  • 控制台和其他React项目可以利用jest工具,针对方法、组件、模块去做单元测试,特别是组件,可以利用快照功能避免多次修改测试用例。
  • 单元测试是测试流程的基础部分,粒度最高成本最低,对于提升前端质量有重要作用。
参考

使用Enzyme和React Testing Library测试React Hooks https://cloud.tencent.com/developer/article/1651156

点击加入群聊【小程序/前端交流】,一起学习交流:663077768

0 人点赞