ant design pro自动化测试 jest

1.文档说明
1.1. 前言
基于React+Ant Design(以下用Antd表示)的项目,在对于自己封装的,或者基于Antd封装的公共组件的自动化测试技术的选型和实践。
1.2. 适用范围
读者:公牛智能家居前端开发者。
1.3. 相关背景
随着前端项目越来越大,业务逻辑日益繁杂,协同开发的同事也越来越多,迭代频繁,许多页面有一些相似的功能,会复用一些组件,这些组件被剥离出来,一般放在component文件夹下,大家共同维护,这时会出现一些常见问题:

  • 从代码层面看,我们必须保证当前组件的质量,即当前业务的正常使用
  • 在新需求下,旧组件如果能满足新需求50%以上的功能,应当升级旧组件满足新需求,同时兼容旧业务
  • 除该组件Owner之外第二人,在修改组件的过程中,避免因为对代码的不熟悉,改出BUG
  • 一个组件多个页面复用,修改后的测试回归任务重
    1.4. 单元测试的好处
  • 测试用例代码跟项目代码完全分离,但是能检验当前组件的质量、健壮性,保证业务能够正常使用
  • 降低项目多处使用的公共组件修改后,测试回归任务重(改一处测多处)的问题
  • 减少不必要的console.log或者debug,提升维护体验
  • 对开发人员来说,写测试用例可以适量提升对代码实现的理解

1.5. 为何选用jest
*方便的异步测试
*snapshot功能
*集成断言库,不许要引用其他第三方库
*对React天生支持
1.6. React项目单元测试框架Jest简介

  • 源自Facebook,Jest的一个理念是提供一套完整集成的零配置测试体验,开箱即用。
  • 包含单元测试Mocha,chai,jsdom,sinon,mock等功能
  • 内置代码覆盖率报告
  • 可以与TypeScript一同使用

1.7. jest的断言
jest针对为实现某些业务逻辑而封装的函数,设定多种可能的输入,判断是否准确地返回了期望值
expect({a:1}).toBe({a:1})//判断两个对象是否相等
expect(1).not.toBe(2)//判断不等
expect(n).toBeNull(); //判断是否为null
expect(n).toBeUndefined(); //判断是否为undefined
expect(n).toBeDefined(); //判断结果与toBeUndefined相反
expect(n).toBeTruthy(); //判断结果为true
expect(n).toBeFalsy(); //判断结果为false
expect(value).toBeGreaterThan(3); //大于3
expect(value).toBeGreaterThanOrEqual(3.5); //大于等于3.5
expect(value).toBeLessThan(5); //小于5
expect(value).toBeLessThanOrEqual(4.5); //小于等于4.5
expect(value).toBeCloseTo(0.3); // 浮点数判断相等
expect(‘Christoph’).toMatch(/stop/); //正则表达式判断

  1. 方法测试
    2.1. 快速开发
    在项目中安装 npm install --save-dev jest
    ant design pro自动化测试 jest

ant design pro自动化测试 jest
ant design pro自动化测试 jest

2.2. 钩子
• beforeEach(fn)
• afterEach(fn)
• beforeAll(fn)
• afterAll(fn)
ant design pro自动化测试 jest
ant design pro自动化测试 jest

2.3. mock(两种方式)
2.3.1. 什么是mock函数
Mock函数可以轻松的测试代码之间的连接——这通过查处函数的实际实现,捕获对函数的调用(以及在这些调用中传递的参数)

2.3.2. 创建Mock Function
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}

const mockCallback = jest.fn();
forEach([0, 1], mockCallback);
test(‘该模拟函数被调用了两次’, () => {
// 此模拟函数被调用了两次
expect(mockCallback.mock.calls.length).toBe(2);
})

test(‘第一次调用函数时的第一个参数是0’, () => {
// 第一次调用函数时的第一个参数是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
})

test(‘第二次调用函数时的第一次参数是1’, () => {
// 第二次调用函数时的第一个参数是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
})
这样我们就使用一个Jest的Mock函数来测试forEach函数的内部调用情况了。
3. 当前管理平台的简单示例
3.1 测试对象:
侧边菜单栏组件(./src/componets/SiderMenu.js)中使用到的数据和逻辑处理函数输入输出表现

3.2 测试前分析

  • 业务角度分析:这是一个按权限展示的多级侧边主菜单栏。(权限方面需要测试及后端人员进行)
  • 组件实现分析:根据后端提供的数据做菜单展示,如果存在多级数据就多级展示,同时可基于菜单实现路由映射。(jest单元测试)

3.3 确定测试对象用到的数据和逻辑处理函数
以下两个文件中包含并导出了我们的测试对象组件SiderMenu使用到的函数
js文件1:./src/componets/_utils/pathTools.js
js文件2:./src/componets/SiderMenu.js
…………
…………
/**

  • 递归扁平化数据
  • [{path:string},{path:string}] => [path,path2]
  • @param menu
    */
    export const getFlatMenuKeys = menu =>
    menu.reduce((keys, item) => {
    keys.push(item.path);
    if (item.children) {
    return keys.concat(getFlatMenuKeys(item.children));
    }
    return keys;
    }, []);

/**

  • 根据paths查找所有匹配的菜单的keys
  • @param flatMenuKeys: [/abc, /abc/:id, /abc/:id/info]
  • @param paths: [/abc, /abc/11, /abc/11/info]
    */
    export const getMenuMatchKeys = (flatMenuKeys, paths) =>
    paths.reduce(
    (matchKeys, path) =>
    matchKeys.concat(flatMenuKeys.filter(item => pathToRegexp(item).test(path))),
    []
    );

3.4测试用例:SlideMenu.test.js
// 导入待测试的三个函数
import { urlToList } from ‘…/_utils/pathTools’;
import { getFlatMenuKeys, getMenuMatchKeys } from ‘./SiderMenu’;

// 先mock一个测试的菜单栏数据作为参数
const menu = [
{
path: ‘/dashboard’,
children: [
{
path: ‘/dashboard/name’,
},
],
},
{
path: ‘/userinfo’,
children: [
{
path: ‘/userinfo/:id’,
children: [
{
path: ‘/userinfo/:id/info’,
},
],
},
],
},
];

// 传入mock的数据给getFlatMenuKeys得到的输出为flatMenuKeys
const flatMenuKeys = getFlatMenuKeys(menu);

// 测试getFlatMenuKeys返回的结果是否符合我们的预期
describe(‘test convert nested menu to flat menu’, () => {
it(‘simple menu’, () => {
expect(flatMenuKeys).toEqual([
‘/dashboard’,
‘/dashboard/name’,
‘/userinfo’,
‘/userinfo/:id’,
‘/userinfo/:id/info’,
]);
});
});

// 测试菜单匹配 模拟多种可能的输入是否符合期望(多种输入对应的输出是否符合预期)
describe(‘test menu match’, () => {
// 简单的一级菜单应该返回对应的一个一级菜单结果
it(‘simple path’, () => {
expect(getMenuMatchKeys(flatMenuKeys, urlToList(’/dashboard’))).toEqual([’/dashboard’]);
});
// 错误或不存在的路径应该返回[]
it(‘error path’, () => {
expect(getMenuMatchKeys(flatMenuKeys, urlToList(’/dashboardname’))).toEqual([]);
});
// 二级菜单应该返回对应的所有菜单keys
it(‘Secondary path’, () => {
expect(getMenuMatchKeys(flatMenuKeys, urlToList(’/dashboard/name’))).toEqual([
‘/dashboard’,
‘/dashboard/name’,
]);
});
// 存在参数的路径应该返回对应的预期
it(‘Parameter path’, () => {
expect(getMenuMatchKeys(flatMenuKeys, urlToList(’/userinfo/2144’))).toEqual([
‘/userinfo’,
‘/userinfo/:id’,
]);
});
// 三级带参数路径
it(‘three parameter path’, () => {
expect(getMenuMatchKeys(flatMenuKeys, urlToList(’/userinfo/2144/info’))).toEqual([
‘/userinfo’,
‘/userinfo/:id’,
‘/userinfo/:id/info’,
]);
});
});

3.5 运行命令、结果
ant design pro自动化测试 jest

3.6 puppeteer (操纵木偶的人)
可以随意操控Chrome或Chromeium,缺点就是只有node的API。
代码运行时,你可能什么也没有感觉到,因为它在后台启动了一个Chromeium进程,打开了百度首页,接着就关闭了,当然我们可以在前台打开Chromeium,这里就需要配置一下,所配置参数只需传入launch()即可,示例如下:
const browser = await puppeteer.launch({headless:false, args: ["–no-sandbox",]})

headless: 是否打开浏览器,默认为true;
args:设置浏览器的相关参数,比如是否启动沙箱模式“–no-sandbox”,是否更换代理“–proxy-server”

3.6.1测试用例: home.e2e.js
ant design pro自动化测试 jest

3.6.2测试用例: login.e2e.js
ant design pro自动化测试 jest

ant design pro自动化测试 jest