前端自动化测试 —— Jest 测试框架应用

news2025/4/6 11:56:02

目录​​​​​​​

什么是自动化测试

为什么要用前端自动化测试

前端自动化分类和思想

单元测试

集成测试

TDD 测试驱动开发(Test Driven Development)

BDD 行为驱动开发(Behavior Driven Development)

如何自己写非框架测试用例

是否能简化?

如何能清晰地看到我测的是哪个呢?

如何使用 Jest 测试框架进行自动化测试?

主流的前端自动化测试框架

Jasmine

MOCHA

Jest

准备工作 —— Jest 的配置

使用 babel 转换来使用 ES6 形式的导入和导出

如何生成一个测试用例覆盖率报告?

Jest 基础匹配器

匹配器

命令行操作

异步测试

钩子函数

分组方法 discribe

Mock

Mock 高阶用法

Mock-timers

snapshot 快照

对 dom 节点测试

VSCode 插件

常用配置解读

小结

代码地址

参考文档


什么是自动化测试

        在软件测试中,自动化测试指的是使用独立于待测软件的其他软件来自动执行测试、比较实际结果与预期并生成测试报告这一过程。在测试流程已经确定后,测试自动化可以自动执行的一些重复但必要的测试工作。也可以完成手动测试几乎不可能完成的测试。对于持续交付和持续集成的开发方式而言,测试自动化是至关重要的。   ——来自 WiKi 百科

为什么要用前端自动化测试

        随着前端项目的发展,其规模和功能日益增加。为了提高项目的稳定性和可靠性,除了需要测试工程师外,前端自动化测试也成为了不可或缺的一环。采用前端自动化测试可以有效地提高代码质量,降低出错的概率,从而使项目更加健壮和更易维护。

前端自动化分类和思想

单元测试

        又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。在前端中,一个函数、一个类、一个模块文件,都可以进行单元测试,测试时每个模块都是互不干扰的。

集成测试

        是在单元测试的基础上,测试再将所有的软件单元按照概要设计规格说明的要求组装成模块、子系统或系统的过程中各部分工作是否达到或实现相应技术指标及要求的活动。用户的开始操作到结束操作这一整个行为流程可以当作集成测试。

TDD 测试驱动开发(Test Driven Development)

开发流程:

TDD 是趋向于白盒测试,需要开发者对当前编写的模块思路足够清晰。

优势:

  1. 长期减少回归 bug

  2. 代码质量更好,可维护性高。

  3. 测试覆盖率高(先写测试用例,再实现功能)。

  4. 错误测试代码不容易出现(测试在开发之前执行)。

BDD 行为驱动开发(Behavior Driven Development)

开发流程:

BDD 趋向于黑盒测试,只关注用户的一整套行为流程下来是否会成功。

优势:

  1. 对于用户行为的整个流程把控程度较高,对于开发人员来说这样安全感高。

如何自己写非框架测试用例

        不使用测试框架,我们该如何测试自己的模块呢?如果我们想要测试下面的代码,应该需要两个值,一个是 期望值 ,另一个是函数执行的 结果值 ,我们需要对比两个值来进行判断当前函数是否通过了测试用例。

// index.js
function ZcyZooTeam(str) {
  return 'Zcy' + str;
}

        需要下面的 if / else 进行判断当前的期望值 value 和结果值 result 是否相等,如果相等说明我们的测试用例通过了。我们将这两段代码复制到浏览器中,下面的执行不会通过,并会抛出错误,只有我们将传入值改为 ZooTeam 才会成功执行。

// no-jest.js
const result = ZcyZooTeam('Zero');
const value = 'ZooTeam';
if(result !== value) {
  throw Error(`ZcyZooTeam 结果应为${value}, 但实际结果为${result}`);
}

是否能简化?

        如果我们有多个函数需要测试,你应该不想写许多个 if / else 代码块吧?所以我们要将上面的代码块进行优化成一个函数。

// no-jest.js
function expect(result) {
  return {
    // 用于判断是否为期望值
    toBe(value) {
      if(result !== value) {
        throw Error(`结果应为${value}, 但实际结果为${result}`);
      }
      console.log('测试通过!');
    }
  }
}

// 执行测试
expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam');

经过上面的封装,我们就可以只写一行代码进行测试了!

如何能清晰地看到我测的是哪个呢?

        虽然上面的封装只需要书写一行代码就可以测试了,但是我们不知道执行结果和测试用例之间的对应关系,我们需要输出的文字来告诉我们当前是哪个测试用例执行了。

// no-jest.js
// 再封装如下方法
function test(msg, fn) {
  try {
    fn();
    console.log(msg + '测试通过!');
  } catch (error) {
    console.log(msg + '测试未通过!' + error);
  }
}

test('测试ZcyZooTeam', () => {
  expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam')
})

成功和失败都会进行提示,这样我们就可以知道当前是哪个测试用例成功/失败了。

    Jest 的书写方式也是同上,如果上面的一整套代码了解了的话,你已经可以写 Jest 的测试脚本了,下面将进入 Jest 的配置。

如何使用 Jest 测试框架进行自动化测试?

主流的前端自动化测试框架

Jasmine

Jasmine 优点:易于学习和使用,支持异步测试,可以在浏览器和 Node.js 环境中运行,可以生成易于阅读的测试报告,可以与其他库和框架集成。

MOCHA

MOCHA 优点:支持异步测试和 Promise ,可以在浏览器和 Node.js 环境中运行,可以与其他库和框架集成,可以生成易于阅读的测试报告,可以使用各种插件和扩展来增强其功能。

Jest

Jest 是针对模块进行测试,单元测试对单个模块进行测试,集成测试对多个模块进行测试。

Jest 优点:速度快(单模块测试时,执行过的模块不会重复执行),API简单,易配置,隔离性好(执行环境相对隔离,每个文件单独隔离互不干扰),监控模式(更灵活的运行各种测试用例),适配编辑器多,Snapshot(快照),多项目运行(后台前台测试用例并行测试),生成可视化覆盖率简单,Mock 丰富。

准备工作 —— Jest 的配置

npm i jest --save-D

// 初始化 jest 的配置文件
npx jest --init

// 你将在那个环境进行测试,回车即可选择
// 第一个是 node 环境、第二个是浏览器环境
? Choose the test environment that will be used for testing › - Use arrow-keys. Return to submit.
    node
❯   jsdom (browser-like)

// 是否需要 jest 生成测试覆盖率报告
? Do you want Jest to add coverage reports? › (y/N)

// 是否需要在测试结束后清除模拟调用
? Automatically clear mock calls and instances between every test? › (y/N)

// 创建 jest.config.js 文件
📝  Configuration file created at /Users/zcy1/Desktop/demo/auto-test-jest-demo/jest.config.js

        以上方法执行结束后,会生成一个 jest.config.js 文件,里面包含了 Jest 的配置项,每个配置项都会带有描述,在初始化的两个配置也会体现在配置文件中

使用 babel 转换来使用 ES6 形式的导入和导出

// .babelrc
// 如果想用 es6 的形式导出,需要使用 babel 插件进行转换
// @babel/core  @babel/preset-env

// 创建 .babelrc 文件
// 为了在 node 环境下使用 es6 的导出,需要使用 babel 进行转换
{
  // 设置插件集合
  "presets": [
    // 使用当前插件,可以进行转换
    // 数组的第二项为插件的配置项
    [
      "@babel/preset-env", {
        // 根据 node 的版本号来结合插件对代码进行转换
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

        配置好后需要将 package.json 中的 test 命令的 value 改为 jest --watchAll,代表监听所有有修改的测试文件,然后控制台执行 npm run test 就可以执行测试用例了。

Jest 启动时会进行如下流程:

  1. ·npm run test

  2. ·jest (babel-jest)  检测当前环境是否安装了 babel

  3. ·如果安装了则会去 babelrc 中取配置

  4. ·取到后执行代码转换

  5. ·最后再执行转化过的测试用例代码

如何生成一个测试用例覆盖率报告?

经过上面的 Jest 配置,我们就可以通过下面的 npx 命令来生成测试覆盖率报告了

npx jest --coverage

        会生成一个名为 coverage 的文件夹,打开里面的 html 就可以看到你的覆盖率,其中 Statements 是语句覆盖率(每个语句是否执行),Branches 是分支覆盖率(每个 if块是否执行),Functions是函数覆盖率(每个函数是否执行),Lines 是行覆盖率(每行是否执行),通过修改 coverageDirectory 的值可以改变测试覆盖率生成文件夹的名字

Jest 基础匹配器

        上面我们说过了,Jest 的用法和我们封装的那几个函数是一样的,都是执行 test 函数并向函数中传递参数,第一个参数是你当前测试用例的描述,第二个参数是需要执行的匹配规则。

匹配器

toBe

toBe 匹配器,期待是否与匹配器中的值相等 相当于 object.is ===

// jest.test.js
test("测试", () => {
  expect(1).toBe(1);  // 通过
  const a = { name: 'Zero' };
  // 因为 a 的引用地址,和 toBe 中对象的引用地址不一致,会导致测试不通过,需要使用其他的匹配器
  expect(a).toBe({ name: 'Zero' });  // 失败
});

toEqual

toEqual 匹配器,只会匹配对象中的内容是否相等。

// jest.test.js
test('测试对象相等', () => {
  const a = { name: 'Zero' };
  expect(a).toEqual({ name: 'Zero' });  // 断言
})

toBeNull

toBeNull 匹配器,可以判断变量是否为 null ,只能匹配 null。

// jest.test.js
test('测试是否为null', () => {
  const a = null;
  expect(a).toBeNull();
})

toBeUndefined

toBeUndefined 匹配器,可以判断变量是否为 undefined ,只能匹配 undefined。

// jest.test.js
test('测试是否为undefined', () => {
  const a = undefined;
  expect(a).toBeUndefined();
})

toBeDefined

toBeDefined 匹配器,希望被测试的值是定义好的。

// jest.test.js
test('测试变量是否定义过', () => {
  const a = '';
  expect(a).toBeDefined();
})

toBeTruthy

toBeTruthy 匹配器,可以判断变量是否为真值,会对非 bool 值进行转换。

// jest.test.js
test('测试变量真值', () => {
  const a = '123';
  expect(a).toBeTruthy();
})

toBeFalsy

toBeFalsy 匹配器,可以判断变量是否为假值,会对非 bool 值进行转换。

// jest.test.js
test('测试变量假值', () => {
  const a = '';
  expect(a).toBeFalsy();
})

not修饰符

not 匹配器,可以将匹配后的结果进行取反。

// jest.test.js
test('测试变量不是假值', () => {
  const a = '1';
  expect(a).not.toBeFalsy();
})

toBeGreaterThan

toBeGreaterThan 匹配器,期望值是否大于匹配器的参数。

// jest.test.js
test('是否大于 a 的数字', () => {
  const a = 123;
  expect(a).toBeGreaterThan(1);
})

toBeLessThan

toBeLessThan 匹配器,期望值是否小于匹配器的参数。

// jest.test.js
test('是否小于 a 的数字', () => {
  const a = 0;
  expect(a).toBeLessThan(1);
})

toBeGreaterThanOrEqual

toBeGreaterThanOrEqual 匹配器,期望值是否大于或等于匹配器的参数。

// jest.test.js
test('是否大于等于 a 的数字', () => {
  // toBeLessOrEqual 匹配器,与之相反
  const a = 123;
  expect(a).toBeGreaterThanOrEqual(1);
})

toBeCloseTo

js 中,浮点数值在相加时不准确,使用 toBeCloseTo 匹配器解决,趋近于 0.3。

// jest.test.js
test('是否大于等于 a 的数字', () => {
  const a1 = 0.1;
  const a2 = 0.2;
  expect(a1 + a2).toBeCloseTo(0.3);
})

toMatch

toMatch 匹配器,匹配当前字符串中是否含有这个值,支持正则。

// jest.test.js
test('是否包含 day ', () => {
  const a = 'happy every day';
  expect(a).toMatch('day');
})

toContain

toContain 匹配器,判断当前数组中是否包含这个元素,Set 也可以使用。

// jest.test.js
test('数组中是否包含 zoo 这个元素', () => {
  const a = ['zoo', 'ZooTeam', 'Zero'];
  expect(a).toContain('zoo');
})

toThrow

toThrow 匹配器,可以捕捉抛出的异常,参数为抛出的 error ,可以用来判断是否为某个异常。

// jest.test.js
const error = () => {
  throw new Error('error');
}
test('是否存在异常', () => {
  expect(error).toThrow();
})

        以上就是 Jest 中比较基础的匹配器,可以结合 初始化 + 配置 + 基础匹配器 进行书写测试用例。

命令行操作

        在运行 npm run test 命令的时候,控制台执行测试用例成功或失败后都会像下面的图片一样出现几行提示,让你按对应的键进行操作。

上面几个命令行的意思如下:

1. f 只会跑测试未通过的用例,再次点击 f 会取消当前模式。

    我们使用一个失败的测试用例做一下示范

    按下 f 后,Jest 只会执行刚才失败的测试用例

2. o 只监听已改变的文件,如果存在多个测试文件,可以开启,会与当前 git 仓库中的提交进行比较,需要使用 git 来监听哪个文件修改了,也可以将 --watchAll 改为 --watch 只会运行修改的文件。

3. 根据测试用例文件的正则表达式,过滤需要执行的测试用例文件,No tests found, exiting with code 0 如果填写不对会进行提示,并不会跑任何测试用例。

4. 根据测试用例描述的正则表达式,过滤需要执行的测试用例。

5. 退出测试用例监听。

异步测试

        在正常的业务开发中,项目中不只有同步代码,还会有请求接口的异步代码,异步代码的测试与同步代码有稍许不同,我们来看一下。

编写一个接口请求

// getData.js
export const getData = (fn) => {
  axios.get('/getData').then((res) => {
    fn(res.data);
  })
}

对异步请求进行测试

// jest.test.js
// 异步调用回调函数需要添加 done 参数,是一个函数
test('getData 返回结果为 { success: true }', (done) => {
  // 此处代码无效,因为测试用例不会等待请求结束后的回调,测试用例执行完就直接结束了
  // getData1((data) => {
  //   expect(data).toEqual({
  //     success: true
  //   })
  // })
  
  getData1((data) => {
    expect(data).toEqual({
      success: true
    })
    // 需要在结束前调用 done 函数, Jest 会知道到 done 才会结束,才可以正确测试异步函数
    done();
  })
})

        需要注意的是,如果传入了形参 done,但是没有使用,这个测试用例就会处于一直执行的状态,直到执行超时。

还可以结合 promise 进行使用

// getData.js
export const getData2 = () => {
  return axios.get('http://www.dell-lee.com/react/api/demo.json')
}

// jest.test.js
test('getData 返回结果为 { success: true }', () => {
  // 使用 promise 时需要 return,在 then 中使用 done 也可以
  return getData2().then(res => {
    expect(res.data).toEqual({
      success: true
    })
  })
})

// 测试请求是否 404
test('getData 返回结果为 404', () => {
  // 由于不触发 catch 就不会走测试校验,所以会成功,我们需要做一下限制
  // 这行代码限制下面的代码中必须要执行一次 expect 方法,
  // 如果非 404 就不会走下面的 expect,则测试不会通过
  expect.assertions(1);
  // 使用 promise 时需要 return
  // 如果只想测试 404 这样写是有问题的,需要配合 assertions 使用
  return getData2().catch(err => {
    expect(err.toString().indexOf('404') > -1).toBe(true)
  })
})

// 另一种写法
test('getData 返回结果为 { success: true }', () => {
  // 会返回很多数据,其中包含 data 对象
  // getData2().then((res) => console.log(res))
  // {
  //   status: 200,
  //   statusText: 'OK',
  //   headers: {},
  //   ......
  //   data: { success: true }
  // }
  // resolves 方法会将接口返回的字段全部获取,再使用 toMatchObject 方法
  // 进行匹配大对象中是否存在 data 对象
  return expect(getData2()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})

// 还可以使用 async/await
test('getData 返回结果为 { success: true }', async () => {
  await expect(getData2()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})

钩子函数

        钩子函数可以当作一个测试用例的生命周期来看待,有 beforeAll 、beforeEach 、afterEach 、afterAll 。

以下是一些关于钩子函数的概念和场景:

        ·beforeAll:在所有测试用例执行前运行

        ·beforeEach:在每个测试用例执行前执行一次

        ·afterEach:在每个测试用例执行后执行一次

        ·afterAll:在所有测试用例结束后运行

        有时候,需要测试一个类中的多个方法,这些方法可能会反复操作同一个对象上的属性。如果使用同一个实例,就会相互干扰,导致测试用例无法通过。此时,需要使用不同的实例来进行测试。

Counter 类

// Counter.js
class Counter {
  constructor() {
    this.number = 0;
  }

  add() {
    this.number += 1;
  }

  minus() {
    this.number -= 1;
  }
}

export default Counter;

        我们想要测试里面的 add 和 minus 方法是否正确,需要实例化一个对象进行测试。但是下面的测试用例使用的永远都是同一个实例,第二个测试用例永远都不会通过。因为执行了第一个测试用例,第二个测试用例的值只能是 0。

// jest.test.js
const count = new Counter();
// 使用下方两种测试方法会互相影响,先加一后减一,结果永远是 0
test('测试加法', () => {
  count.add();
  expect(count.number).toBe(1);
})

test('测试减法', () => {
  count.minus();
  expect(count.number).toBe(-1);
})

需要使用钩子函数,在每次执行测试用例的时候,都让他重新实例化一个对象

// jest.test.js
let count = null;
// 类似于生命周期
// 会在测试用例执行前运行
beforeAll(() => {
  console.log('beforeAll')
});

// 会在每个测试用例执行前执行一次,这样就会解决上面互相影响的问题
beforeEach(() => {
  console.log('beforeEach')
  count = new Counter();
});

// 会在每个测试用例执行后执行一次
afterEach(() => {
  console.log('afterEach')
});

// 会在所有测试用例结束后运行
afterAll(() => {
  console.log('afterAll');
});

test('测试加法', () => {
  console.log('add')
  count.add();
  expect(count.number).toBe(1);
})

test('测试减法', () => {
  console.log('minus')
  count.minus();
  expect(count.number).toBe(-1);
})

分组方法 discribe

// jest.test.js
let count = null;
// describe 方法,可以将测试用例进行分组,更加好维护同类型功能的测试用例
describe('count 测试', () => {
  beforeAll(() => {
    console.log('beforeAll')
  });
  beforeEach(() => {
    console.log('beforeEach')
    count = new Counter();
  });
  afterEach(() => {
    console.log('afterEach')
  });
  afterAll(() => {
    console.log('afterAll');
  });
  
  // 将 add 类型进行分组
  describe('测试 add 类型用例', () => {
    // 在 describe 方法中,钩子函数会按照层级嵌套进行执行,
    // 先执行外部,再执行内部,不同的 describe 互不干扰
    beforeEach(() => {
      console.log('beforeEach add');
    });
    test('测试加法', () => {
      console.log('add')
      count.add();
      expect(count.number).toBe(1);
    })
  })

  // 将 minus 类型进行分组
  describe('测试 minus 类型用例', () => {
    test('测试减法', () => {
      console.log('minus')
      count.minus();
      expect(count.number).toBe(-1);
    })
  })
})

加上 describe 方法的执行效果如下图。

Mock

        在日常开发中,当前端开发差不多后,后端接口可能还没有提供,这个时候我们就要用 Mock 数据。而 Jest 也有 Mock 方法,用于模拟一些 JavaScript 的函数等。

我们先来一个比较简单的 mock.fn

// mock.js
export const runFn = (fn) => {
  fn(123);
}

// mock.test.js
test('测试 runFn', () => {
  // 通过 jest 的 fn 方法创建一个模拟函数,如果不传参数会默认生成一个函数
  // 1. 通过 func.mock 获取想要的值
  // 2. 可以自定义返回值
  // 3. 改变内部函数的实现,模拟接口请求,不请求代码中的接口
  const func = jest.fn( () => 456 );

  // 还可以使用 mockReturnValueOnce 方法进行控制输出,
  // 两种方法都使用时会覆盖 fn 方法中的返回值,支持链式调用
  // 将 Once 去掉与 fn 方法一样,多次会返回相同的值
  func.mockReturnValueOnce('zoo')

  // 返回 this 方法 mockReturnThis
  func.mockReturnThis();

  // 还可以使用 mockImplementation 方法书写函数内部,可以在函数内部写逻辑,
  // 与 jest.fn 方法的参数一样,还可以填加 Once
  func.mockImplementation(() => {
    return '123';
  })

  // 执行被测函数
  runFn(func);
  runFn(func);
  
  // console.log(func.mock)
  // 因为被调用了两次,所以长度都是 2
  // {
  //   calls: [ [123], [123] ],  // 每次的调用情况,传递的参数是什么
  //   instances: [ undefined, undefined ],  // 每次调用的 this 指向,被调用了几次
  //   invocationCallOrder: [ 1, 2 ],  // 执行顺序,
  //   可能会传入同一个或多个方法中,需要记录一下顺序
  //   results: [  // mock 函数每次执行后的返回值
  //     { type: 'return', value: 456 },
  //     { type: 'return', value: 456 }
  //   ]
  // }

  // 通过 toBeCalled 判断函数是否被调用
  expect(func).toBeCalled();

  // 判断当前函数调用了几次 被调用了两次
  expect(func.mock.calls.length).toBe(2);

  // 判断参数是什么
  expect(func.mock.calls[0]).toEqual([123]);

  // 判断每次调用的时候参数是什么
  expect(func).toBeCalledWith(123);

  // 判断返回值
  expect(func.mock.results[0].value).toBe('zoo');
})

Mock 高阶用法

        如果需要通过修改请求的方式进行测试,而不使用测试框架,我们可能需要修改请求的代码逻辑。但是,Jest 提供了一种高级的 Mock 方法。我们只需在项目根目录下创建一个名为 __mocks__ 的文件夹,然后在其中自定义文件内容并导出,就可以使用自己定义的 Mock 函数而不必修改请求代码逻辑。

书写测试用例文件,引入 __mocks__ 文件夹中的函数

// mocker.test.js
// 使用 mock 方法引用 __mocks__ 下创建的 mock.js
jest.mock("./mock");
// 执行完上面的方法,会直接寻找 __mocks__ 下的getData,而不是正常的请求文件
// 由于 mock 中没有 getCode 方法,最好只 mock 异步函数,同步函数直接测试即可
// 可以不必须创建 __mocks__ 文件夹
import {
  getData,
} from "./mock";

// 需要使用下面的 requireActual 方法来引用非 mock 文件夹下的 getCode
const { getData } = jest.requireActual("./mock");

// 高阶mock
// 此处直接使用 __mocks__ 目录下的 mock 文件中的函数
test("测试 getData", () => {
  return getData().then((data) => {
    expect(eval(data)).toEqual("123");
  });
});

Mock-timers

        在特定的业务中,需要使用到定时器,测试的时候也是需要修改代码来测试不同时间,最主要的一点是,我们需要等时间才能看到我们的执行结果,Jest 也有关于定时器的 Mock函数。

// mock.js
export const timer = (fn) => {
  setTimeout(() => {
    fn();
    setTimeout(() => {
      fn();
    }, 3000)
  }, 3000)
}
// mock-timers.test.js
import { timer } from './mock';

// 使用 useFakeTimers 方法告知 Jest 在下面的测试用例,
// 如果用到了定时器异步函数的时候,都是用假的 timers 进行模拟
jest.useFakeTimers();

test('测试 timer', () => {
  const fn = jest.fn();
  timer(fn);

  // 使用 runAllTimers 方法,让定时器立即执行,和 useFakeTimers 配合使用
  jest.runAllTimers();

  // 如果代码中有多个定时器嵌套,只想测试最外层的定时器,则需要使用 runOnlyPendingTimers 方法
  // 这个方法会只执行当前在队列中的函数,可以多次调用
  jest.runOnlyPendingTimers();
  jest.runOnlyPendingTimers();

  // advanceTimersByTime 方法,可以快进时间
  // 因为 timer 中,三秒后只执行了第一层,如果是六秒,则会执行两次 fn
  jest.advanceTimersByTime(3000);
})

snapshot 快照

        到这里我们已经可以测试一些代码了,但是我们要如何捕捉执行结果和当前做对比呢?这时候就要使用快照功能了。

// snapshot.js
export const config1 = () => {
  return {
    method: 'GET',
    url: '/api',
    time: new Date()
  }
}

export const config2 = () => {
  return {
    method: 'GET',
    url: '/api',
    time: new Date().getTime()
  }
}
// snapshot.test.js
import { config1, config2 } from "./snapshot";

test('测试 config1 返回值', () => {
  // 但如果每次函数修改的时候,当前测试用例也要不断地修改
  // expect(config()).toEqual({
  //   method: 'GET',
  //   url: '/api'
  // });

  // 需要使用快照匹配 toMatchSnapshot 方法
  // 此方法会生成一个 __snapshots__ 目录,下面的文件中,
  // 第一次执行中 config 生成的结果会存到快照文件中
  // 快照会根据 test 方法中的描述生成一个映射关系
  // 修改后的 config 的执行结果与快照中的结果不同时会报错,需要更新快照
  // 如果 config 中有的值是每次运行都会变化的,那么每次快照都不会与当前执行相同,
  // 除非执行后再更新快照
  // 需要将在 toMatchSnapshot 方法中传递一个参数,设置一下 time 为任意格式的 Date 类型
  expect(config1()).toMatchSnapshot({
    time: expect.any(Date)
  });
})

test('测试 config2 返回值', () => {
  expect(config2()).toMatchSnapshot({
    time: expect.any(Number)
  });
})

行内快照生成

// snapshot.test.js
// 需要安装 prettier
test("测试 config2 返回值", () => {
  // toMatchInlineSnapshot 方法,将执行快照放到行内中,
  // 会放到 toMatchInlineSnapshot 方法中
  expect(config2()).toMatchInlineSnapshot(
    {
      time: expect.any(Number)
    },
    `
    Object {
      "method": "GET",
      "time": Any<Number>,
      "url": "/api",
    }
    `
  );
});

对 dom 节点测试

    Jest 内部自己模拟了一套 jsDom ,可以在 node 的环境下执行需要浏览器环境 dom的测试用例。

// dom.js
import $ from 'jquery';
const addDiv = () => {
  // jQuery
  $('body').append('<div/>');
}

export default addDiv;

// dom.test.js
import addDiv from './dom';
import $ from 'jquery';

test('测试 addDiv', () => {
  addDiv();
  addDiv();
  console.log($('body').find('div').length);

  // 测试dom
  expect($('body').find('div').length).toBe(2);
  expect(document.getElementsByTagName('div').length).toBe(2);
})

VSCode 插件

    Jest Snippets 用于快速生成 Jest 代码块的工具。

    Jest 能够检测当前文件夹中的测试用例并自动运行测试,还支持可视化操作,更新、执行以及单个执行等功能,非常方便!

常用配置解读

module.exports = {
  // 检测从哪个目录开始,rootDir 代表根目录
  roots: ["<rootDir>/src"],

  // 代码测试覆盖率通过分析那些文件生成的,!代表不要分析
  collectCoverageFrom: [
    // src 下所有 js jsx ts tsx 后缀的文件
    "src/**/*.{js,jsx,ts,tsx}",

    // src 下所有 .d.ts 后缀的文件
    "!src/**/*.d.ts"
  ],

  // 运行测试之前,我们额外需要准备什么
  setupFiles: ["react-app-polyfill/jsdom"],

  // 当测试环境建立好后,需要做其他事情时可以引入对应的文件
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],

  // 哪些文件会被认为测试文件
  testMatch: [
    // src 下的所有 __tests__ 文件夹中的所有的 js jsx ts tsx 后缀的文件都会被认为是测试文件
    "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",

    // scr 下的所有一 .test/spec.js/jsx/ts/tsx 后缀的文件都会被认为是测试文件
    "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}",
  ],

  // 测试运行的环境,会模拟 dom
  testEnvironment: "jsdom",

  // 测试文件中引用一下后缀结尾的文件会使用对应的处理方式
  transform: {
    // 如果引用的是 js jsx mjs cjs ts tsx 后缀的文件会使用 /config/jest/babelTransform.js 文件进行处理
    "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",

    // 如果引用的是 css 后缀的文件,会使用 /config/jest/cssTransform.js 文件处理
    "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",

    // 不是以 js jsx mjs cjs ts tsx css json 这些为后缀的文件会使用 /config/jest/fileTransform.js 文件进行处理
    "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
      "<rootDir>/config/jest/fileTransform.js",
  },

  // 忽略 transform 配置转化的文件
  transformIgnorePatterns: [
    // node_modules 目录下的 js jsx mjs cjs ts tsx 后缀的文件都不需要转化
    "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",

    // .module.css/sass/scss 后缀的文件都不需要转化
    "^.+\\.module\\.(css|sass|scss)$",
  ],

  // 自动化测试时,应用的模块应该从哪里寻找,默认是在 node_modules
  modulePaths: [],

  // 模块名字使用哪种工具进行映射
  moduleNameMapper: {
    // 针对于 native 移动端
    // "^react-native$": "react-native-web",

    // 将 .module.css/sass/scss 模块使用 identity-obj-proxy 工具进行转化
    "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
  },

  // 引入模块时,进行自动查找模块类型,逐个匹配
  moduleFileExtensions: [
    "web.js",
    "js",
    "web.ts",
    "ts",
    "web.tsx",
    "tsx",
    "json",
    "web.jsx",
    "jsx",
    "node",
  ],

  // 监听插件
  watchPlugins: [
    "jest-watch-typeahead/filename",
    "jest-watch-typeahead/testname",
  ],

  // 重置 mock
  resetMocks: true,
};

小结

        在实际业务应用中,我们建议对可复用的组件、工具函数、工具类等一些无副作用,可预知结果的代码来进行单元测试。在前期开发过程中的投入会大于没有单元测试的投入,因为要写一些测试用例,还要执行测试用例,优化代码等。但是在长久迭代中,这种方法会比没有进行单元测试的模块更加稳定。

代码地址

  1. 前置 demo :https://github.com/Jadony/Jest-demo

  2. Jest 简单配置:https://github.com/Jadony/jest-config

  3. Jest 匹配器:https://github.com/Jadony/jest-matchers

  4. 异步代码测试:https://github.com/Jadony/jest-async

  5. Jest 钩子函数:https://github.com/Jadony/jest-hook

  6. Jest 的 mock 函数:https://github.com/Jadony/jest-mock

  7. Jest 的快照:https://github.com/Jadony/jest-snapshot

  8. Jest 对 Dom 节点的测试:https://github.com/Jadony/jest-dom

参考文档

  • 《前端要学的测试课 从Jest入门到TDD/BDD双实战》(https://coding.imooc.com/class/chapter/372.html#Anchor)

        如果想学习更多内容,请移步至 Jest(https://jestjs.io/zh-Hans/docs/getting-started) 官方文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/580381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式软件架构——SOA架构/微服务架构/无服务架构

SOA架构 Service-Oriented Architecture&#xff0c;面向服务的架构。面向服务的架构是一次具体地、系统性地成功解决分布式服务主要问题的架构模式。了解SOA架构前&#xff0c;先了解三种比较有代表性的服务拆分的架构模式&#xff0c;这些架构模式是SOA演化过程的中间产物&a…

偷偷曝光下国内这些软件外包公司!(2023 最新版)

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 我新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 根据网上的资料&#xff0c;整理出来的一份国内软件外包公司的名单。 找工作的同学都要看看&#xff0c;根据自身的情况&#…

杰理AC632N实现custom hid

1. 设备描述符修改 设备描述符主要修改的是PID、VID、设备发现版本号以及字符串描述。 static const u8 sDeviceDescriptor[] { //<Device DescriptorUSB_DT_DEVICE_SIZE, // bLength: Size of descriptorUSB_DT_DEVICE, // bDescriptorType: Device #if defi…

langchain简版教程附案例

简介 LangChain是一个开源的应用开发框架。基于该开源框架&#xff0c;我们可以把大模型与各种工具结合从而实现各种功能&#xff0c;比如基本文档的问答&#xff0c;解析网页内容、查询表格数据等。目前支持Python和TypeScript两种编程语言。当前Python框架支持的模型和功能最…

腾讯云服务器ping不通解决方法(公网IP/安全组/系统多维度)

腾讯云服务器ping不通什么原因&#xff1f;ping不通公网IP地址还是域名&#xff1f;新手站长从云服务器公网IP、安全组、Linux系统和Windows操作系统多方面来详细说明腾讯云服务器ping不通的解决方法&#xff1a; 目录 腾讯云服务器ping不通原因分析及解决方法 安全组ICMP协…

智能计价器-第14届蓝桥杯省赛Scratch中级组真题第5题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第140讲。 智能计价器&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组真题第5题&#…

楼宇租赁管理系统-什么是楼宇租赁系统

楼宇租赁管理系统是一种综合管理平台&#xff0c;它为不同规模楼宇的租赁管理提供了全面的解决方案。楼宇租赁管理系统的主要功能包括租赁管理、财务管理、维修管理、报告管理以及客户服务管理等。让我们逐一介绍每一个功能点。 一、租赁管理 楼宇租赁管理系统通过集成租户信息…

路径规划算法:基于群居蜘蛛优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于群居蜘蛛优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于群居蜘蛛优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

博学谷学习记录】超强总结,用心分享 | 架构师 MySQL调优MVCC学习总结

文章目录 1 概述2 快照读与当前读当前读快照读隔离级别&#xff1a;undo log版本链&#xff1a; 3 Read View3.1 什么是Read View?3.2 实现原理3.3 Read View规则&#xff08;可见性算法&#xff09; 4 MVCC整体流程4.1 可重复读是如何工作的&#xff1f;4.2 读提交是如何工作…

ChatGPT国内镜像,以及如何使用ChatGPT帮你制作PPT

一&#xff1a;前言 ChatGPT&#xff1a;智能AI助你畅聊天地 在现代人日益忙碌的生活中&#xff0c;难免需要一些轻松愉快的聊天来放松身心。而现在&#xff0c;有了 ChatGPT&#xff0c;轻松愉快的聊天变得更加智能、有趣且不受时间、地点限制&#xff01; 什么是 ChatGPT&…

Python中模块的使用方法4

1 模块、包和库的区别 Python中&#xff0c;模块的英文是“module”&#xff0c;是一个以py为后缀名的文件&#xff1b;包的英文是“package”&#xff0c;是一个包含了多个模块的目录&#xff1b;库的英文是“library”&#xff0c;包含了具有相关功能的包和模块。 2 模块的…

ChatGPT 在自动化测试领域的应用,我们真的要被代替了吗?

目录 前言 一、ChatGPT 简介 二、ChatGPT 的应用场景 三、ChatGPT 的优势 四、ChatGPT 的局限性 五、ChatGPT 在自动化测试领域的拓展应用 六、ChatGPT真的可以代替软件测试人员吗 七、结语 前言 人工智能技术在近年来得到了快速发展&#xff0c;不少领域都开始尝试融合…

入理解深度学习——正则化(Regularization):多任务学习

分类目录&#xff1a;《深入理解深度学习》总目录 多任务学习是通过合并几个任务中的样例&#xff08;可以视为对参数施加的软约束&#xff09;来提高泛化的一种方式。正如额外的训练样本能够将模型参数推向具有更好泛化能力的值一样&#xff0c;当模型的一部分被多个额外的任务…

redis主从复制策略的原理:主从节点间如何同步数据?

redis的主从复制原理经历了多个版本的更新。 redis2.8之前的SYNC方案 命令&#xff1a; SYNChttps://www.yuque.com/snailclimb/mf2z3k/ks9olb19hc9wse5k#5935f46a 存在的问题&#xff1a; slave加载RDB的过程中不能对外提供读服务slave和master断开连接后&#xff0c;sla…

HCIA-VLAN间通信之路由器

目录 路由信息获取方式&#xff1a; 最佳路由条目选择&#xff08;根据路由的优先级和开销选择&#xff09;&#xff1a; VLAN间通信 方案1&#xff0c;使用路由器的物理接口来实现不同vlan间的通信 方案2&#xff1a;使用路由器的子接口来实现 路由&#xff1a;指导报文转…

【Vue】二:Vue核心处理---计算属性 监视属性

文章目录 1.计算属性示例2. 监听属性3.补充 1.计算属性示例 实际上计算属性与methods中定义方法基本上没有什么区别&#xff0c;只是计算属性基于响应式依赖缓存&#xff0c;只要数据没有发生改变&#xff0c;计算属性从缓存中取值&#xff0c;只有当数据发送改变&#xff0c;才…

Linux 用户与组群管理

1 用户账户与群组概念 Linux操作系统是多用户多任务的操作系统&#xff0c;允许多个用户同时登录到系统&#xff0c;使 用系统资源。用户账户是用户的身份标识。用户通过用户账户可以登录到系统&#xff0c; 并且访问已经被授权的资源。系统依据账户来区分属于每个用户的文件…

Flutter 笔记 | Flutter 动画

Flutter中的动画抽象 为了方便开发者创建动画&#xff0c;不同的UI系统对动画都进行了一些抽象&#xff0c; Flutter中也对动画进行了抽象&#xff0c;主要涉及 Animation、Curve、Controller、Tween这四个角色&#xff0c;它们一起配合来完成一个完整动画&#xff0c;下面我们…

整数在内存中原来是这样存储的,看完表示头好痒,感觉要长脑子了!

本篇文章来介绍一下整形在内存中的存储&#xff0c;内容丰富&#xff0c;干货慢慢。 目录 1.整形家族 2.整形在内存中的存储 3.大端小端存储 4.练习 1.整形家族 在开始之前&#xff0c;我们先来简单回顾一下整形家族&#xff1a; char unsigned char signed char short u…

chatgpt赋能python:Python补0——让你的数字更规整

Python 补0——让你的数字更规整 在编写代码的过程中&#xff0c;我们经常需要将数字格式化&#xff0c;以便更好地呈现给用户。例如&#xff0c;一个价格可能需要显示为“$100.00”而不是“$100”或“$100.0”。这时候&#xff0c;我们就需要使用 Python 的补0功能。本文将为…