前端单元测试到底如何落地?(下)

发表于:2022-3-17 10:02

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:莫疾    来源:三元同学

  5.持续监听
  为了提高效率,可以通过加启动参数的方式让 jest 持续监听文件的修改,而不需要每次修改完再重新执行测试用例
  改写 package.json
  "scripts": {     "test": "jest --watchAll"   },

  效果:
  6.生成测试覆盖率报告
  什么是单元测试覆盖率?
  单元测试覆盖率是一种软件测试的度量指标,指在所有功能代码中,完成了单元测试的代码所占的比例。有很多自动化测试框架工具可以提供这一统计数据,其中最基础的计算方式为:
  单元测试覆盖率 = 被测代码行数 / 参测代码总行数 * 100%

  如何生成?
  加入 jest.config.js  文件:
  module.exports = {
    // 是否显示覆盖率报告
    collectCoverage: true,
    // 告诉 jest 哪些文件需要经过单元测试测试
    collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'],
  }

  再次运行效果
  参数解读
  设置单元测试覆盖率阀值
  个人认为既然在项目中集成了单元测试,那么非常有必要关注单元测试的质量,而覆盖率则一定程度上客观的反映了单测的质量,同时我们还可以通过设置单元测试阀值的方式提示用户是否达到了预期质量。
  jest.config.js  文件
  module.exports = {
    collectCoverage: true, // 是否显示覆盖率报告
    collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'], // 告诉 jest 哪些文件需要经过单元测试测试
    coverageThreshold: {
      global: {
        statements: 90, // 保证每个语句都执行了
        functions: 90, // 保证每个函数都调用了
        branches: 90, // 保证每个 if 等分支代码都执行了
      },
    },

  上述阀值要求我们的测试用例足够充分,如果我们的用例没有足够充分,则下面的报错将会帮助你去完善。
  7.如何编写单元测试
  下面我们以 fetchEnv 方法作为案例,编写一套完整的单元测试用例供读者参考。
  编写 fetchEnv 方法
  ./src/utils/fetchEnv.ts  文件
  /**
   * 环境参数枚举
   */
   enum IEnvEnum {
    DEV = 'dev', // 开发
    TEST = 'test', // 测试
    PRE = 'pre', // 预发
    PROD = 'prod', // 生产
  }
  /**
   * 根据链接获取当前环境参数
   * @param {string?} url 资源链接
   * @returns {IEnvEnum} 环境参数
   */
  export function fetchEnv(url: string): IEnvEnum {
    const envs = [IEnvEnum.DEV, IEnvEnum.TEST, IEnvEnum.PRE];
    return envs.find((env) => url.includes(env)) || IEnvEnum.PROD;
  }

  编写对应的单元测试
  ./test/fetchEnv.test.ts  文件
  import { fetchEnv } from '../src/utils/fetchEnv';
  describe('fetchEnv', () => {
    it ('判断是否 dev 环境', () => {
      expect(fetchEnv('https://www.imooc.dev.com/')).toBe('dev');
    });
    it ('判断是否 test 环境', () => {
      expect(fetchEnv('https://www.imooc.test.com/')).toBe('test');
    });
    it ('判断是否 pre 环境', () => {
      expect(fetchEnv('https://www.imooc.pre.com/')).toBe('pre');
    });
    it ('判断是否 prod 环境', () => {
      expect(fetchEnv('https://www.imooc.prod.com/')).toBe('prod');
    });
    it ('判断是否 prod 环境', () => {
      expect(fetchEnv('https://www.imooc.com/')).toBe('prod');
    });
  });

  执行结果
  8.常用断言方法
  关于断言方法有很多,这里仅摘出常用方法,如果你想了解更多,你可以去 Jest 官网 API (https://www.jestjs.cn/docs/expect) 部分查看。
  .not 修饰符允许你测试结果不等于某个值的情况
  ./test/sum.test.js
  import { sum } from './sum';
  test('sum(2, 4) 不等于 5', () => {
    expect(sum(2, 4)).not.toBe(5);
  })

  .toEqual 匹配器会递归的检查对象所有属性和属性值是否相等,常用来检测引用类型
  ./src/utils/userInfo.js
  export const getUserInfo = () => {
    return {
      name: 'moji',
      age: 24,
    }
  }

  ./test/userInfo.test.js
  import { getUserInfo }  from '../src/userInfo.js';
  test('getUserInfo()返回的对象深度相等', () => {
    expect(getUserInfo()).toEqual(getUserInfo());
  })
  test('getUserInfo()返回的对象内存地址不同', () => {
    expect(getUserInfo()).not.toBe(getUserInfo());
  })

  .toHaveLength 可以很方便的用来测试字符串和数组类型的长度是否满足预期
  ./src/utils/getIntArray.js
  export const getIntArray = (num) => {
    if (!Number.isInteger(num)) {
      throw Error('"getIntArray"只接受整数类型的参数');
    }
    return [...new Array(num).keys()];
  };

  ./test/getIntArray.test.js
  ./test/getIntArray.test.js
  import { getIntArray }  from '../src/utils/getIntArray';
  test('getIntArray(3)返回的数组长度应该为3', () => {
    expect(getIntArray(3)).toHaveLength(3);
  })

  .toThorw 能够让我们测试被测试方法是否按照预期抛出异常
  但是需要注意的是:我们必须使用一个函数将被测试的函数做一个包装,正如下面 getIntArrayWrapFn 所做的那样,否则会因为函数抛出错误导致该断言失败。
  ./test/getIntArray.test.js
  import { getIntArray }  from '../src/utils/getIntArray';
  test('getIntArray(3.3)应该抛出错误', () => {
    function getIntArrayWrapFn() {
      getIntArray(3.3);
    }
    expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整数类型的参数');
  })

  .toMatch 传入一个正则表达式,它允许我们来进行字符串类型的正则匹配
  ./test/userInfo.test.js
  import { getUserInfo }  from '../src/utils/userInfo.js';
  test("getUserInfo().name 应该包含'mo'", () => {
    expect(getUserInfo().name).toMatch(/mo/i);
  })

  测试异步函数
  ./servers/fetchUser.js
  /**  
   * 获取用户信息
  */
  export const fetchUser = () => {
    return new Promise((resole) => {
      setTimeout(() => {
        resole({
          name: 'moji',
          age: 24,
        })
      }, 2000)
    })
  }

  ./test/fetchUser.test.js
  import { fetchUser } from '../src/fetchUser';
  test('fetchUser() 可以请求到一个用户名字为 moji', async () => {
    const data =  await fetchUser();
    expect(data.name).toBe('moji')
  })

  这里你可能看到这样一条报错。
  这是因为 @babel/preset-env 不支持 async await 导致的,这时候就需要对 babel 配置进行增强,可以安装 @babel/plugin-transform-runtime 这个插件解决
  npm install --save-dev @babel/plugin-transform-runtime

  同时改写 .babelrc
  {
    "presets": ["@babel/preset-env", "@babel/preset-typescript"],
    "plugins": ["@babel/plugin-transform-runtime"]
  }

  再次运行就不会出现报错了。
  .toContain 匹配对象中是否包含
  ./test/toContain.test.js
  const names = ['liam', 'jim', 'bart'];
  test('匹配对象是否包含', () => {
    expect(names).toContain('jim');
  })

  检查一些特殊的值(null,undefined 和 boolean)
  toBeNull 仅匹配 null
  toBeUndefined 仅匹配 undefined
  toBeDefined 与…相反 toBeUndefined
  toBeTruthy 匹配 if 语句视为 true 的任何内容
  toBeFalsy 匹配 if 语句视为 false 的任何内容
  检查数字类型(number)
  toBeGreaterThan 大于
  toBeGreaterThanOrEqual 至少(大于等于)
  toBeLessThan 小于
  toBeLessThanOrEqual 最多(小于等于)
  toBeCloseTo 用来匹配浮点数(带小数点的相等)

  总结
  以上就是文章全部内容,相信你阅读完这篇文章后,已经掌握了前端单元测试的基本知识,甚至可以按照文章教学步骤,现在就可以在你的项目中接入单元测试。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号