前端自动化测试:测试到底在测什么?
Posted 程序员小濠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端自动化测试:测试到底在测什么?相关的知识,希望对你有一定的参考价值。
对于稍微有一些开发经验的同学在开发过程中总会经历下面类似的问题:
-
每次在版本发布上线之前,在电脑前蹲上好几个小时甚至是更长时间对你的应用进行测试,这个过程非常枯燥而痛苦。
-
当代码的复杂度达到了一定的级别,当维护者的数量不止你一个,你应该会逐渐察觉到你在开发新功能或修复 bug 的时候,会变得越发小心翼翼,即使代码看起来没什么问题,但你心里还是会犯嘀咕:这个 Feature 会不会带来其他 Bug ?这个 Fix 会不会引入其他"Feature" ?
-
当你想要对项目中的代码进行重构的时候,你会花费大量的时间进行回归测试。
以上这些问题都是由于大多数开发者所使用最基本的手动测试的方式所带来的问题,解决它的根本举措就是引入自动化测试方案。
测试的流程
在实际开发过程中,编写自动化测试代码通常是开发人员不太喜欢的一个环节。大多数情况下,前端开发人员在开发完一项功能后,只是打开浏览器手动点击,查看效果是否正确,之后就很少对该块代码进行管理。
造成这种情况的原因主要有两个:
-
一个是业务繁忙,没有时间进行测试的编写。
-
另一个是不知道如何编写测试。
但这些问题不应该作为我们掌握前端自动化测试的绊脚石。而且,一旦掌握了前端自动化测试方案,无论是对大型项目的开发,还是升职加薪,都是有益的。
提到测试的时候,即使是最简单的一个代码块可能都让初学者不知所措。最常问的问题的是“我怎么知道要测试什么?”。如果你正在写一个 Web 应用,那么你每个页面每个页面的测试用户交互的方式,就是一个很好的开端了。但 Web 应用也是由很多个函数和模块组成的代码单元,也是需要测试的。通常有两种情况:
-
你接手的遗留代码没有写测试用例
-
你必须从无到有的实现一个新功能
该怎么办呢?对于上面两种场景,你可以把测试视为代码的一部分来编写。我所说的这些代码,是用来检查给定的函数是否产生预期输出结果的。一个典型的测试流程如下:
1. 引入要测试的函数
2. 给函数一个输入
3. 定义预期输出
4. 检查函数是否返回了预期的输出结果
就这么多。这样看测试也没那么可怕的嘛:输入 —— 预期输出 —— 验证结果。
在这里分享一波资料
包括,软件学习路线图,50多天的上课视频、16个突击实战项目,80余个软件测试用软件,37份测试文档,70个软件测试相关问题,40篇测试经验级文章,上千份测试真题分享,还有2021软件测试面试宝典,还有软件测试求职的各类精选简历,希望对大家有所帮助……
关注我公众号:【程序员小濠】即可获取这份资料了!
一个测试案例
下面来看一个例子:
// math.js
functionadd (a, b) {
return a + b
}
functionsubtract (x, y) {
return x - y
}
module.exports= {
add,
subtract
}
复制代码
如何保证上面代码的正确性?
下面来写一段测试代码:
// test.js
const { add, subtract } =require('./math')
const result =add(1,2)
const expected =3
if (result !== expected) {
thrownewError(`1 + 2 应该等于${expected},但是结果却是${result}`)
}
const result2 =subtract(2,1)
const expected2 =1
if (result2 !== expected2) {
thrownewError(`2 - 1 应该等于${expected2},但是结果却是${result2}`)
}
复制代码
命令行执行 node test.js 后,会看到错误信息:
Error: 1 + 2 应该等于 3,但是结果却是 2
复制代码
通过测试代码可以很方便的帮助验证代码的正确性。
封装测试工具函数
之前示例的测试代码太过繁琐,可以思考一下能否封装的更简便一些,比如下面这样:
expect(add(1,2)).toBe(3)
expect(subtract(2,1)).toBe(-1)
复制代码
上面的测试代码就像自然语言说话一样,很舒服。
实现 expect 方法:
// test.js
const { add, subtract } =require('./math')
expect(add(1,2)).toBe(3)
expect(subtract(2,1)).toBe(1)
functionexpect (result) {
return {
toBe (actual) {
if (result !== actual) {
thrownewError(`预期值和实际值不相等,预期结果: ${actual},实际结果: ${result}`)
}
}
}
}
复制代码
增加错误提示信息:
// test.js
const { add, subtract } =require('./math')
test('测试加法', () => {
expect(add(1,2)).toBe(3)
})
test('测试减法', () => {
expect(subtract(2,1)).toBe(1)
})
functiontest (description, callback) {
try {
callback()
console.log(`${description}通过测试`)
} catch (err) {
console.error(`${description}没有通过测试:${err}`)
}
}
functionexpect (result) {
return {
toBe (actual) {
if (result !== actual) {
thrownewError(`预期值和实际值不相等,预期结果: ${actual},实际结果: ${result}`)
}
}
}
}
复制代码
Jest 测试框架应用
Jest : jestjs.io/zh-Hans/是 Facebook 出品的一个 JavaScript 开源测试框架。
相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如零配置、自带断言、测试覆盖率工具等功能,实现了开箱即用。
Jest 适用但不局限于使用以下技术的项目:Babel、TypeScript、 Node、 React、Angular、Vue 等。
Jest 主要特点:
-
零配置
-
自带断言
-
作为一个面向前端的测试框架, Jest 可以利用其特有的快照测试功能,通过比对UI 代码生成的快照文件,实现对 React 等常见前端框架的自动测试。
-
Jest 的测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了测试速度。
-
测试覆盖率
-
Mock 模拟
快速体验 Jest
安装 Jest 到项目中:
npm install --save-dev jest
//math.js
functionadd (a, b) {
return a * b
}
functionsubtract (x, y) {
return x - y
}
module.exports= {
add,
subtract
}
//test.js ==> math.test.js
const { add, subtract } =require('./math')
test('测试加法', () => {
expect(add(1,2)).toBe(3)
})
test('测试减法', () => {
expect(subtract(2,1)).toBe(1)
})
//package.json
{
"scripts":{
"test":"jest"
}
}
复制代码
jest 命令会运行项目中所有以 .test.js 结尾的文件
最后运行测试命令:
npm run test
复制代码
解析:
-
jest 找到项目中所有以 .test.js 结尾的文件并运行
-
jest 会给测试文件提供 test、expect 等全局函数,所以在测试文件中可以直接使用
-
jest 为测试结果提供了良好的日志输出
解决 vscode 中 jest 代码提示问题
npm i -D @types/jest
复制代码
注意:@types/jest 必须安装到项目的根目录,并且以根目录的方式在 vscode 中打开,否则不生效。或者说只要是 vscode 打开的项目根目录有 @types/jest 这个包就可以了。
配置文件
npx jest--init
复制代码
配置文件生成选项:
是否使用 ts ;
使用哪种测试环境;
使用 jest 收集测试覆盖率报告;
使用那种引擎检测覆盖率:v8 处于实验性阶段,建议 Node v14 后使用,babel 较为成熟
每次测试时,是否自动清除 mock 实例;
详细配置信息参考:jestjs.io/docs/zh-Han…。
//jest.config.js
/*
* For a detailedexplanation regarding each configuration property, visit:
*https://jestjs.io/docs/en/configuration.html
*/
module.exports= {
// 自动 mock 所有导入的外部模块
// automock: false,
// 在指定次数失败后停止运行测试
// bail: 0,
// The directory where Jest should store its cached dependencyinformation
// cacheDirectory:"/private/var/folders/5h/_98rffpj1z95b_0dm76lvzm40000gn/T/jest_dx",
// 在每个测试之间自动清除 mock 调用和实例
clearMocks:true,
// 是否收集测试覆盖率信息
// collectCoverage: false,
// 一个 glob 模式数组,指示应该为其收集覆盖率信息的一组文件
// collectCoverageFrom: undefined,
// 测试覆盖率报错文件输出的目录
coverageDirectory:"coverage",
// 忽略测试覆盖率统计
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// 指示应该使用哪个提供程序检测代码的覆盖率,默认是 babel,可选 v8,但是 v8 不太稳定,建议Node 14 以上版本使用
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writingcoverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement forcoverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an arrayof glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that istriggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that istriggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in alltest environments
// globals: {},
// The maximum amount of workers used to run your tests. Canbe specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPUamount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively upfrom 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 or to arraysof module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against allmodule 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: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters toJest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individualtest
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests andmodules within
// rootDir: undefined,
// A list of paths to directories that Jest should use tosearch for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's defaulttest runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or setup the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configureor set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered asslow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest shoulduse 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 againstall test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses todetect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It isreflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use offake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched againstall source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\\\.pnp\\\\.[^\\\\/]+$"
// ],
// An array of regexp pattern strings that are matched againstall modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reportedduring the run
// verbose: undefined,
// An array of regexp patterns that are matched against allsource file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
复制代码
Jest CLI Options
指定测试文件运行
"scripts":{
"test":"jest ./math.test.js"
},
复制代码
Jest 监视模式
--watchAll 选项:监视文件的更改并在任何更改时重新运行所有测试。
"scripts": {
"test": "jest --watchAll"
},
复制代码
Jest API
在测试文件中,Jest 将所有这些方法和对象放入全局环境中。无需要求或导入任何内容即可使用它们。但是,如果喜欢显式导入,则可以:
import { describe, expect, test } from'@jest/globals'
复制代码
Test 函数
test 函数别名:it(name, fn, timeout)。
-
test(name,fn, timeout)
-
test.concurrent(name, fn, timeout)
-
test.concurrent.each(table)(name, fn, timeout)
-
test.concurrent.only.each(table)(name, fn)
-
test.concurrent.skip.each(table)(name, fn)
-
test.each(table)(name, fn, timeout)
-
test.only(name, fn, timeout)
只运行当前测试用例
-
test.only.each(table)(name, fn)
-
test.skip(name,fn)
-
test.skip.each(table)(name, fn)
-
test.todo(name)
创建 global-api.test.js 测试文件,注意,测试文件中必须有一个测试用例,如果没有则直接报错。
test('should ', () => {
console.log('test--api')
})
test('should ', () => {
console.log('test--api')
})
test('should1 ', () => {
console.log('test--api1')
})
// 上面两个不运行
test.only('should2 ', () => {
console.log('test--api2')
})
复制代码
Expect 匹配器
在编写测试时,通常需要检查值是否满足某些条件。Expect 让我们可以访问许多“匹配器”,以验证不同的内容。
test('two plus two is four', () => {
expect(2+2).toBe(6)
expect({ name:'jack' }).toEqual({ name:'jack' })
expect('Christoph').toMatch(/stop/)
expect(4).toBeGreaterThan(3)
expect(4).toBeLessThan(5)
})
复制代码
完整的匹配器列表查看:jestjs.io/zh-Hans/doc…
describe 函数
describe 创建一个将几个相关测试组合在一起的块。
const myBeverage = {
delicious:true,
sour:false,
};
describe('my beverage', () => {
test('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
});
test('is not sour', () => {
expect(myBeverage.sour).toBeFalsy();
});
});
复制代码
分组最直观的就是在测试提示中,有更加友好的信息输出。
-
describe(name,fn)
-
describe.each(table)(name, fn, timeout)
-
describe.only(name,fn)
-
describe.only.each(table)(name, fn)
-
describe.skip(name,fn)
-
describe.skip.each(table)(name, fn)
未完待续。
最后为方便大家学习测试,特意给大家准备了一份13G的超实用干货学习资源,涉及的内容非常全面。
包括,软件学习路线图,50多天的上课视频、16个突击实战项目,80余个软件测试用软件,37份测试文档,70个软件测试相关问题,40篇测试经验级文章,上千份测试真题分享,还有2021软件测试面试宝典,还有软件测试求职的各类精选简历,希望对大家有所帮助……
关注我公众号:【程序员小濠】即可获取这份资料了!
我的软件测试交流群:175317069 欢迎各位大佬来群里交流~我也会不定期的发放软件测试资料
如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!
以上是关于前端自动化测试:测试到底在测什么?的主要内容,如果未能解决你的问题,请参考以下文章