Jest学习

163 阅读2分钟

jest 底层原理代码

function add(a, b) {
    return a + b
}

function mutil(c, d) {
    return c * d
}

function expect(result) {
    return {
        toBe: function (actual) {
            if (result !== actual) {
                throw new Error('预期值与实际值不符!')
            }
        }
    }
}

function test(desc, fn) {
    try {
        fn()
        console.log(`测试结果${desc},测试通过`)
    } catch (e) {
        console.log(`测试结果 ${desc} 不通过 ${e}`)
    }
}

test('3 + 4 等于 7', () => {
    expect(add(3, 4)).toBe(7)
})  // 测试结果3 + 4 等于 7,测试通过

test('3 * 4 等于 12', () => {
    expect(mutil(3, 4)).toBe(14)
})  // 测试结果 3 * 4 等于 12 不通过 Error: 预期值与实际值不符!

测试框架 Jest

优点

速度快、API简单、易配置、隔离性好、监控模式、IDE整合、Snapshot(快照)、多项目并行、覆盖率、Mock丰富等。

jest 分为单元测试,也就是模块测试;集成测试,是多个模块测试。

Jest 使用方式 CommonJs

1、首先安装jest,例如:npm install jest@24.8.0 -D

2、main.js 文件代码:

function add(a, b) {
  return a + b
}

function mutil(c, d) {
  return c * d
}

// 使用 commonjs 的方式导出模块
module.exports = {
  add, 
  mutil
}

3、main.test.js 文件代码:

const {add, mutil} = main

test('3 + 4 等于 7', () => {
    expect(add(3, 4)).toBe(7)
})

test('3 * 4 等于 12', () => {
    expect(mutil(3, 4)).toBe(12)
})

4、在 package.json文件中配置运行的测试命令

"scripts": { "test": "jest" },

5、命令行运行 npm run test

image.png

Jest 的简单配置

运行命令 npx jest --init, 自动生成 jest.config.js

image.png

jest.config.js

// https://jestjs.io/docs/en/configuration.html

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // Respect "browser" field in package.json when resolving modules
  // browser: false,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "/private/var/folders/ct/797qh2lx05v_hgtjn068rmq80000gn/T/jest_dx",

  // Automatically clear mock calls and instances between every test
  clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  // collectCoverage: false,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: null,

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: null,

  // A path to a custom dependency extractor
  // dependencyExtractor: null,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: null,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: null,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  // maxWorkers: "50%",

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "json",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "node"
  // ],

  // A map from regular expressions to module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: null,

  // Run tests from one or more projects
  // projects: null,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state between every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: null,

  // Automatically restore mock state between every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: null,

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  // setupFiles: [],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  // testEnvironment: "jest-environment-jsdom",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: null,

  // This option allows use of a custom test runner
  // testRunner: "jasmine2",

  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
  // testURL: "http://localhost",

  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
  // timers: "real",

  // A map from regular expressions to paths to transformers
  // transform: null,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "/node_modules/"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: null,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};

生成 测试覆盖率 报告命令 npx jest --coverage, 生成 coverage 文件夹。

image.png

Jest 使用方式 ES Module

由于 jest 使用的是 node 环境,所以不支持 importexport 的方式导入导出文件,因此我们需要配置一下babel让其把 ESModule 语法转为 CommonJS 语法。

1、首先安装babel: npm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D

2、在项目根目录下创建 .babelrc文件

{
  // babel 使用哪些插件的集合, 帮助我们对代码进行转化
  // preset的参数是一个数组,数组中也可以是preset
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        // 根据当前机器的node的版本号,结合@babel/preset-env,对代码进行转化
        // 当前 node 不支持 import/export语法,转为CommonJS的模块语法
        "node": "current"  
      } 
    }]
  ]
}

3、main.js 文件代码:

export function add(a, b) {
  return a + b
}

export function mutil(c, d) {
  return c * d
}

// module.exports = {
//   add, 
//   mutil
// }

4、main.test.js 文件语法

// const main = require('./main')
import {add, mutil} from './main'

test('3 + 4 等于 7', () => {
    expect(add(3, 4)).toBe(7)
})

test('3 * 4 等于 12', () => {
    expect(mutil(3, 4)).toBe(12)
})

5、运行 npm run test

image.png

当运行 npm run jest 时,jest 会调用内部的插件 babel-jest, 监测当前环境下是否安装了 babel-core, 如果安装了 babel-core,就去取 .babelrc 的配置,在运行测试之前,结合babel, 先把你的代码做一次转化,例如(把 import 转化为 require),最后运行转化过的测试用例代码。

Jest 中的匹配器

每次修改测试用例,都要手动运行 npm run test很麻烦,可以在 package.json 中配置监听。

"scripts": {
    "test": "jest --watchAll"
  },

>和真假相关的匹配器

toBe

test('测试 1010 匹配', () => {
    // toBe 匹配器 matchers
    expect(10).toBe(10)
})

toEqual

test('测试内容匹配', () => {
    // toEqual 匹配器 matchers
    const obj = {a: 10}
    expect(obj).toEqual({a: 10})
})

匹配内容是否相等

toBeNull

test('测试toBeNull匹配', () => {
    // toEqual 匹配器 matchers
    const obj = null
    expect(obj).toBeNull()
})

toBeUndefined

test('测试toBeUndefined匹配', () => {
    const obj = undefined
    expect(obj).toBeUndefined()
})

toBeDefined

test('测试toBeDefined匹配', () => {
    const obj = null
    expect(obj).toBeDefined()
})

toBeTruthy

test('测试toBeTruthy匹配', () => {
    const obj = 1 
    expect(obj).toBeTruthy()
})

toBeFalsy

test('测试toBeFalsy匹配', () => {
    const obj = 0 
    expect(obj).toBeFalsy()
})

not

test('测试toBeFalsy匹配', () => {
    const obj = 0 
    expect(obj).not.toBeFalsy()
})

toBeGreaterThan

test('测试toBeGreaterThan', () => {
    const a = 10
    expect(a).toBeGreaterThan(9)
})

toBeGreaterThanOrEqual

test('测试toBeGreaterThanOrEqual', () => {
    const a = 10
    expect(a).toBeGreaterThanOrEqual(9)
})

>和数字相关的匹配器

toBeLessThan

test('测试toBeLessThan', () => {
    const a = 10
    expect(a).toBeLessThan(19)
})

toBeLessThanOrEqual

test('测试toBeLessThanOrEqual', () => {
    const a = 10
    expect(a).toBeLessThanOrEqual(11)
})

toBeCloseTo

test('测试toBeCloseTo', () => {
    const a = 0.1
    const b = 0.2
    expect(a + b).toBeCloseTo(0.3)
})

>和字符串相关的匹配器

toMatch

test('测试toMatch', () => {
    const str = 'abc'
    expect(str).toMatch(/ab/)
})

>和集合相关的匹配器

toContain

test('测试toContain', () => {
    // Array
    const arr = ['a','b','c']
    // Set
    const data = new Set(...arr)
    expect(data).toContain('a')
})

>和异常相关的匹配器

toThrow

const throwNewError = ()=> {
    throw new Error('this is a new error')
}
test('测试toThrow', () => {
    expect(throwNewError).toThrow()
})

Jest 命令行工具的使用

"scripts": {
    "test": "jest --watchAll"
  },

image.png

all 模式: 任何一个测试用例发生了改变,都会把所有的测试用例重新测试一次

"scripts": {
    "test": "jest --watch"
  },

image.png

f 模式: 当修改一个测试文件的时候,只会对之前没有通过的测试用例再次测试

o 模式: 当更改一个文件的时候,只去测试该文件里修改的测试用例,注意要和 git 管理的项目一起使用,因为用了git 才会知道上次修改和这次修改的内容的差异,对有差异的文件中的测试用例进行测试。

p 模式: 过滤文件名,包含(例如: demo.test.js、demo2.test.js、)这样文件名中的测试用例。

t 模式: 根据测试用例的名字,过滤一下我们想要执行的测试用例,也叫filter模式。

q 模式: 退出测试环境。

enter模式: 重新执行一次被改变文件中的测试用例。

异步代码测试方法

回调类型异步函数的测试

import axios from 'axios'

export const fetchData = (fn) => {
  axios.get('http://www.dell-lee.com/react/api/demo.json').then(response => {
    fn(response.data)
  })
}
import { fetchData } from "./fetchData";

test('fetchData 接口返回对象 { success: true }', (done) => {
    fetchData(data => {
        expect(data).toEqual({
            success: true
        })
        done()
    })
})

方式一

import axios from 'axios'

export const fetchData = (fn) => {
  axios.get('http://www.dell-lee.com/react/api/demo.json')
}
import { fetchData } from "./fetchData";

// 测试成功的情况
test('fetchData 接口返回对象 { success: true }', () => {
    return fetchData().then(response => {
        expect(response.data).toEqual({
            success: true
        })
    })
})
// 测试失败的时候
test('fetchData 返回结果为 404', () => {
    // expect至少执行一次
    expect.assertions(1); 
    
    // 执行 catch时,需要加上 expect.assertions(次数)
    return fetchData().catch(e => {
        expect(e.toString().indexOf('404') > -1).toBe(true)
    })
})

方式二

import axios from 'axios'

export const fetchData = () => {
 return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
// 测试成功的情况
test('fetchData 返回结果为 { success: true }', () => {
    return expect(fetchData()).resolves.toMatchObject({
        data: {
            success: true
        }
    })
})
// 测试失败的时候
test('fetchData 返回结果为 404', () => {
    return expect(fetchData()).rejects.toThrow();
}) 

方式三

import axios from 'axios'

export const fetchData = () => {
 return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
// 测试成功的情况
test('fetchData 返回结果为 { success: true }', async () => {
    await expect(fetchData()).resolves.toMatchObject({
        data: {
            success: true
        }
    })
})
// 测试失败的时候
test('fetchData 返回结果为 404', async () => {
    await expect(fetchData()).rejects.toThrow();
}) 

方式四

import axios from 'axios'

export const fetchData = () => {
 return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
// 测试成功的情况
test('fetchData 返回结果为 { success: true }', async () => {
    const response = await fetchData();
    expect(response.data).toEqual({
        success: true
    })
})
test('fetchData 返回结果为 404', async () => {
    expect.assertions(1);
    try {
        await fetchData();
    } catch(e) {
        expect(e.toString()).toEqual('Error: Request fail with status code')
    }
}) 

Jest 钩子函数

beforeAll: 执行测试用例之前

beforeEach: 每个测试用例执行之前,都会执行一次

afterEach: 每个测试用例执行之后,都会执行一次

afterAll: 所有的测试用例结束之后执行

describe 分组

import Counter from "./Counter";

describe('Counter 的测试代码', () => {
  let counter = null;

  // 执行测试用例之前
  beforeAll(() => {
    console.log('beforeAll')
  });
  
  // 每个测试用例执行之前,都会执行一次
  beforeEach(() => {
    counter = new Counter();
    console.log('beforeEach')
  })
  
  // 每个测试用例执行之后,都会执行一次
  afterEach(() => {
    console.log('afterEach')
  })
  
  // 所有的测试用例结束之后执行
  afterAll(()=> {
    console.log('afterAll')
  })
  
  describe('测试增加相关的代码', () => {
    test('测试 Counter 中的 addOne 方法', () => {
      counter.addOne();
      expect(counter.number).toBe(1);
    });
  
    test('测试 Counter 中的 addTwo 方法', () => {
      counter.addTwo();
      expect(counter.number).toBe(2);
    });
  })
  
  describe('测试减少相关的代码', () => {
    test('测试 Counter 中的 minusOne 方法', () => {
      counter.minusOne();
      expect(counter.number).toBe(-1);
    });
    
    test('测试 Counter 中的 minusTwo 方法', () => {
      counter.minusTwo();
      expect(counter.number).toBe(-2);
    });
  })
})

export default class Counter {
  constructor() {
    this.number = 0
  }
  addOne() {
    this.number += 1;
  }
  addTwo() {
    this.number += 2;
  }
  minusOne() {
    this.number -= 1;
  }
  minusTwo() {
    this.number -= 2;
  }
}

钩子函数的作用域

先执行外部 describe 中的钩子,然后再执行内部 describe 中的钩子;

只执行指定的test,使用 test.only

image.png

Jest 中的 Mock

mock函数作用一: 捕获函数的调用和返回结果,以及this指向和调用顺序

export const runCallback = (callback) => {
  callback();
}
import { runCallback } from "./demo";

test('测试 runCallBack', () => {
  // mock 函数,捕获函数的调用
  const func = jest.fn();

  runCallback(func);
  // func 函数是否被调用
  expect(func).toBeCalled();
})

jest.fn(): mock函数属性解释

通过jest.fn()生成的函数,会有一个 mock属性 ,属性里有一个

calls: 指被调用多少次,以及每次调用它时传递的参数。

results: 指被调用多少次,以及每次它执行的返回的结果。

instances: 指被调用多少次,以及每次调用this的指向。

invocationCallOrder: 该函数可以被传入同一个方法,或者传入不同的方法中。

Demo2: 函数有参数使用

export const runCallback = (callback) => {
  callback('abc');
}
import { runCallback } from "./demo";

test('测试 runCallBack', () => {
  // mock 函数,捕获函数的调用
  const func = jest.fn();
  
  // 函数被调用了三次
  runCallback(func)
  runCallback(func);
  runCallback(func);
  
  // 判断函数参数的长度是否为3
  expect(func.mock.calls.length).toBe(3);
  
  console.log(func.mock)
})

image.png

mock函数的作用二: 自由设置返回结果

import { runCallback } from "./demo";

test('测试 runCallBack', () => {
   // mock 函数,捕获函数的调用
  const func = jest.fn();
  
  // 自由改变设置返回结果
  func.mockReturnValue('测试1') 
  
  runCallback(func)
  
  // 判断函数的参数是否是 'abc'
  expect(func.mock.calls[0]).toEqual(['abc'])
  
  console.log(func.mock)
})

image.png

Demo4: 创建对象

export const createObject = (classItem) => {
    new classItem()
}
test.only('测试 createObject', () => {
    const func = jest.fn();
    createObject(func);
    console.log(func.mock);
})

image.png

mock函数作用三: 改变函数的内部实现

export const getData = () => {
    return axios.get('/api').then(res => res.data);
}
import axios from 'axios'
jest.mock('axios')

test('模拟axios请求', async () => {
    // 设置 axios 请求数据为 {data: 'hello'}, 不发送真实的请求
    axios.get.mockResolvedValue({data: 'hello'})
    await getData().then(data => {
        expect(data).toBe('hello');
    })
})

mock 其他语法

设置返回值

// mock 函数,捕获函数的调用
    const func = jest.fn(() => {
        console.log('测试')
        return 'Lee'
    });

等价于:

// func.mockReturnValue('测试1') 
func.mockImplementation(() => {
        console.log('测试')
        return 'Lee'
    })

image.png

// 自由改变设置返回结果
    func.mockReturnValue('测试1') 
    func.mockReturnValueOnce('测试2') 
    func.mockImplementation(() => {
        console.log('测试3')
        return 'Lee'
    })
    func.mockImplementationOnce(() => {
        console.log('测试4')
        return 'Bee'
    })

mockReturnValue: 设置返回值

mockReturnValueOnce: 只设置一次返回值

mockImplementation: 设置返回值

mockImplementationOnce: 只设置一次返回值

Snapshot 快照测试

u 命令更新所有快照

export const getData = () => {
    return {
        status: 400,
        name: 'Lisa'
    }
}
import {getData} from './demo'
test('测试快照', () => {
    expect(getData()).toMatchSnapshot();
})

执行jest命令后,测试成功,在目录下生成一个文件夹 __snapshots__, 里面保存的快照

image.png

当我修改getData中的参数时:

    return {
        status: 400,
        name: 'Lisa',
        age: 19
    }
}

测试报错:是因为快照没有被更新导致的

image.png

image.png

红框中的u命令,可以解决报错,更新快照

image.png

i 命令一步一步更新快照, s 命令跳过


export const getData = () => {
    return {
        status: 400,
        name: 'Lisa',
        age: 19
    }
}
export const getData2 = () => {
    return {
        status: 400,
        name: 'Lisa',
        age: 19
    }
}

import {getData, getData2} from './demo'
test('测试快照', () => {
    expect(getData()).toMatchSnapshot();
})

test('测试快照2', () => {
    expect(getData2()).toMatchSnapshot();
})

修改数据后报错:

image.png

按 w 选择命令

image.png

i 命令:一个一个解决报错

行内快照

安装命令: npm install prettier@1.18.2 --save

回车之后会把快照的内容放在测试用例的第二个参数中:

test("行内测试快照", () => {
  expect(getData2()).toMatchInlineSnapshot();
});
test("行内测试快照", () => {
  expect(getData2()).toMatchInlineSnapshot(`
    Object {
      "age": 22,
      "name": "Lisa",
      "status": 400,
    }
  `);
});

Vue 中的 TDD 与 单元测试

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

先编写测试用例,然后编写测试代码

image.png

image.png

创建vue-jest项目

使用脚手架创建项目:vue create jest-vue

image.png

配置后安装打开项目,生成项目目录如下: image.png

jest.config.js 配置文件

module.exports = {
  moduleFileExtensions: [ // 测试的文件类型
    'js',
    'jsx',
    'json',
    // tell Jest to handle *.vue files
    'vue',
    'ts',
    'tsx'
  ],
  transform: { // 转化方式
    // process *.vue files with vue-jest
    '^.+\\.vue$': require.resolve('vue-jest'),
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
    require.resolve('jest-transform-stub'),
    '^.+\\.jsx?$': require.resolve('babel-jest'),
    '^.+\\.tsx?$': require.resolve('ts-jest'),
  },
  transformIgnorePatterns: ['/node_modules/'],  // 转化时忽略 node_modules
  // support the same @ -> src alias mapping in source code
  moduleNameMapper: { // @符号 表示当前项目下的src
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  testEnvironment: 'jest-environment-jsdom-fifteen',
  // serializer for snapshots
  snapshotSerializers: [ // 快照的配置
    'jest-serializer-vue'
  ],
  testMatch: [ // 默认测试文件
    '**/tests/unit/**/*.spec.[jt]s?(x)',
    '**/__tests__/*.[jt]s?(x)'
  ],
  // https://github.com/facebook/jest/issues/6766
  testURL: 'http://localhost/',
  watchPlugins: [
    require.resolve('jest-watch-typeahead/filename'),
    require.resolve('jest-watch-typeahead/testname')
  ],
  globals: {
    'ts-jest': {
      babelConfig: true
    }
  }
}

不使用 @vue/test-utils

import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    // 创建根节点
    const root = document.createElement('div')
    root.className = 'root'
    document.body.appendChild(root)
    // 创建Vue对象
    new Vue({
      render: h => h(HelloWorld, {
        props: {
          msg: 'hello world !!!'
        }
      })
    }).$mount('.root')
    console.log(document.body.innerHTML)
    expect(document.getElementsByClassName('hello').length).toBe(1)
  })
})

image.png

代码写法比较复杂,而且功能多的时候,测试不全面,代码复杂不好写,所以使用 @vue/test-utils, 简化测试代码。

使用 @vue/test-utils

import HelloWorld from '@/components/HelloWorld'
import { shallowMount } from '@vue/test-utils'

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    // shallowMount:浅渲染,适合单元测试,只渲染HelloWorld组件,不渲染他得子组件
    // mount: 深度渲染,适合集成测试,连同子组件一起渲染
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

shallowMount:浅渲染,适合单元测试,只渲染HelloWorld组件,不渲染他得子组件

mount: 深度渲染,适合集成测试,连同子组件一起渲染

除此之外,还有 renderrenderToString等方法,更多的方法可以去查看官网💁

input 框测试用例

逻辑: 没输入 inputValue 之前 input 为空,输入inputValue 后提交内容,然后inputVlaue清空。

image.png

<template>
  <div class="header">
    <input
    data-test='input'
    v-model="inputValue"
    @keyup.enter="addTodoItem"
    >

  </div>
</template>

<script>
export default {
  name: 'Header',
  data () {
    return {
      inputValue: ''
    }
  },
  methods: {
    addTodoItem () {
      if (this.inputValue) {
        this.$emit('add', this.inputValue)
        this.inputValue = ''
      }
    }
  }
}
</script>

/* eslint-disable no-undef */
import { shallowMount } from '@vue/test-utils'
import Header from '../../components/Header.vue'

describe('测试input', () => {
  it('Header是否存在input框', () => {
    const wrappper = shallowMount(Header)
    const input = wrappper.find('[data-test="input"]')
    expect(input.exists()).toBe(true)
  })

  it('Header中input 初始内容为空', () => {
    const wrappper = shallowMount(Header)
    const inputValue = wrappper.vm.$data.inputValue
    expect(inputValue).toBe('')
  })

  it('Header中input 框中值发生变化,数据应该跟着改变', () => {
    const wrappper = shallowMount(Header)
    const input = wrappper.find('[data-test="input"]')
    input.setValue('dell lee')
    const inputValue = wrappper.vm.$data.inputValue
    expect(inputValue).toBe('dell lee')
  })

  it('Header中input 框输入回车,无内容时,无反应', () => {
    const wrappper = shallowMount(Header)
    const input = wrappper.find('[data-test="input"]')
    input.setValue('')
    input.trigger('keyup.enter')
    expect(wrappper.emitted().add).toBeFalsy()
  })

  it('Header中input 框输入回车,有内容时,向外触发事件,同时清空 inputValue', () => {
    const wrappper = shallowMount(Header)
    const input = wrappper.find('[data-test="input"]')
    input.setValue('dell lee')
    input.trigger('keyup.enter')
    expect(wrappper.emitted().add).toBeTruthy()
    expect(wrappper.vm.$data.inputValue).toBe('')
  })
})

TodoList 测试用例

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <Header @add = 'addItem'/>
    <ul>
      <li v-for="(item, index) in undoList" :key="index">
        {{item}}
      </li>
    </ul>
  </div>
</template>

<script>
import Header from './components/Header.vue'
export default {
  name: 'TodoList',
  components: {
    Header
  },
  props: ['msg'],
  data () {
    return {
      undoList: []
    }
  },
  methods: {
    addItem (val) {
      this.undoList.push(val)
      console.log(this.undoList)
    }
  }
}
</script>
/* eslint-disable no-undef */

import TodoList from '../../TodoList.vue'
import { shallowMount } from '@vue/test-utils'
import Header from '../../components/Header.vue'

describe('测试todoList分组', () => {
  it('todoList 初始化有一个 undoList', () => {
    const wrappper = shallowMount(TodoList)
    const undoList = wrappper.vm.$data.undoList
    expect(undoList).toEqual([])
  })

  it('todoList 监听到 Header 的 add 事件时,会增加一个内容', () => {
    const content = 'dell lee'
    const wrappper = shallowMount(TodoList)
    const header = wrappper.find(Header)
    header.vm.$emit('add', content)
    const undoList = wrappper.vm.$data.undoList
    expect(undoList).toEqual([content])
  })
})

CodeCoverage 测试覆盖率

参考官网配置, 在 jest.config.js 文件中配置这两行代码:

  collectCoverage: true,
  collectCoverageFrom: ['**/*.{vue}'] // 只针对.vue后缀的文件生成测试报告

package.json 中的 scripts 中配置命令:

  "test:cov": "vue-cli-service test:unit --coverage",

在 控制台输入命令: npm run test:cov, 在根目录下生成 coverage 文件夹,打开 index.html 文件即可看到测试覆盖率:

image.png

总结

image.png

Vue 中的 BDD 与 集成测试

BDD (Behavior Driven development)

先写代码,后编写测试用例

前端自动化测试的思考总结