【JS】带你入门前端工程(四):测试

带你入门前端工程(四):测试

谭光志发布于 今天 07:40

什么是测试

维基百科的定义:

也可以这样理解:测试的作用是为了提高代码质量和可维护性。

  1. 提高代码质量:测试就是找 BUG,找出 BUG,然后解决它。BUG 少了,代码质量自然就高了。
  2. 可维护性:对现有代码进行修改、新增功能从而造成的成本越低,可维护性就越高。

什么时候写测试

如果你的程序非常简单,可以不用写测试。例如下面的程序,功能简单,只有十几行代码:

function add(a, b) {

return a + b

}

function sum(data = []) {

let result = 0

data.forEach(val => {

result = add(result, val)

})

return result

}

console.log(sum([1,2,3,4,5,6,7,8,9,10])) // 55

如果你的程序有数百行代码,但封装得很好,完美的践行了模块化的理念。每个模块功能单一、代码少,也可以不用写测试。

如果你的程序有成千上万行代码,数十个模块,模块与模块之间的交互错综复杂。在这种情况下,就需要写测试了。试想一下,在你对一个非常复杂的项目进行修改后,如果没有测试会是什么情况?你需要将跟这次修改有关的每个功能都手动测一边,以防止有 BUG 出现。但如果你写了测试,只需执行一条命令就能知道结果,省时省力。

测试类型与框架

测试类型有很多种:单元测试、集成测试、白盒测试...

测试框架也有很多种:Jest、Jasmine、LambdaTest...

本章将只讲解单元测试和 E2E 测试(end-to-end test 端到端测试)。其中单元测试使用的测试框架为 Jest,E2E 使用的测试框架为 Cypress。

Jest

安装

npm i -D jest

打开 package.json 文件,在 scripts 下添加测试命令:

"scripts": {

"test": "jest",

}

然后在项目根目录下新建 test 目录,作为测试目录。

单元测试

什么是单元测试?维基百科中给出的定义为:

从前端角度来看,单元测试就是对一个函数、一个组件、一个类做的测试,它针对的粒度比较小。

单元测试应该怎么写呢?

  1. 根据正确性写测试,即正确的输入应该有正常的结果。
  2. 根据错误性写测试,即错误的输入应该是错误的结果。

对一个函数做测试

例如一个取绝对值的函数 abs(),输入 1,2,结果应该与输入相同;输入 -1,-2,结果应该与输入相反。如果输入非数字,例如 "abc",应该抛出一个类型错误。

// main.js

function abs(a) {

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (a < 0) return -a

return a

}

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

expect(() => abs('abc')).toThrow(TypeError) // 类型错误

})

现在我们需要测试一下 abs() 函数:在 src 目录新建一个 main.js 文件,在 test 目录新建一个 test.spec.js 文件。然后将上面的两个函数代码写入对应的文件,执行 npm run test,就可以看到测试效果了。

【JS】带你入门前端工程(四):测试

对一个类做测试

假设有这样一个类:

class Math {

abs() {

}

sqrt() {

}

pow() {

}

...

}

我们必须把这个类的所有方法都测一遍。

test('Math.abs', () => {

// ...

})

test('Math.sqrt', () => {

// ...

})

test('Math.pow', () => {

// ...

})

对一个组件做测试

组件测试比较难,因为很多组件都涉及了 DOM 操作。

例如一个上传图片组件,它有一个将图片转成 base64 码的方法,那要怎么测试呢?一般测试都是跑在 node 环境下的,而 node 环境没有 DOM 对象。

我们先来回顾一下上传图片的过程:

  1. 点击 <input type="file" />,选择图片上传。
  2. 触发 inputchange 事件,获取 file 对象。
  3. FileReader 将图片转换成 base64 码。

这个过程和下面的代码是一样的:

document.querySelector('input').onchange = function fileChangeHandler(e) {

const file = e.target.files[0]

const reader = new FileReader()

reader.onload = (res) => {

const fileResult = res.target.result

console.log(fileResult) // 输出 base64 码

}

reader.readAsDataURL(file)

}

上面的代码只是模拟,真实情况下应该是这样使用:

document.querySelector('input').onchange = function fileChangeHandler(e) {

const file = e.target.files[0]

tobase64(file)

}

function tobase64(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader()

reader.onload = (res) => {

const fileResult = res.target.result

resolve(fileResult) // 输出 base64 码

}

reader.readAsDataURL(file)

})

}

可以看到,上面的代码出现了 window 的事件对象 eventFileReader。也就是说,只要我们能够提供这两个对象,就可以在任何环境下运行它。所以我们可以在测试环境下加上这两个对象:

// 重写 File

window.File = function () {}

// 重写 FileReader

window.FileReader = function () {

this.readAsDataURL = function () {

this.onload

&& this.onload({

target: {

result: fileData,

},

})

}

}

然后测试可以这样写:

// 提前写好文件内容

const fileData = 'data:image/test'

// 提供一个假的 file 对象给 tobase64() 函数

function test() {

const file = new File()

const event = { target: { files: [file] } }

file.type = 'image/png'

file.name = 'test.png'

file.size = 1024

it('file content', (done) => {

tobase64(file).then(base64 => {

expect(base64).toEqual(fileData) // 'data:image/test'

done()

})

})

}

// 执行测试

test()

通过这种 hack 的方式,我们就实现了对涉及 DOM 操作的组件的测试。我的 vue-upload-imgs 库就是通过这种方式写的单元测试,有兴趣可以了解一下(测试文件放在 test 目录)。

测试覆盖率

什么是测试覆盖率?用一个公式来表示:代码覆盖率 = 已执行的代码数 / 代码总数。Jest 如果要开启测试覆盖率统计,只需要在 Jest 命令后面加上 --coverage 参数:

"scripts": {

"test": "jest --coverage",

}

现在我们用刚才的测试用例再试一遍,看看测试覆盖率。

// main.js

function abs(a) {

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (a < 0) return -a

return a

}

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

expect(() => abs('abc')).toThrow(TypeError) // 类型错误

})

【JS】带你入门前端工程(四):测试

上图表示每一项覆盖率都是 100%。

现在我们把测试类型错误的那一行代码注释掉,再试试。

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

// expect(() => abs('abc')).toThrow(TypeError)

})

【JS】带你入门前端工程(四):测试

可以看到测试覆盖率下降了,为什么会这样呢?因为 abs() 函数中判断类型错误的那个分支的代码没有执行。

// 就是这一个分支语句

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

覆盖率统计项

从覆盖率的图片可以看到一共有 4 个统计项:

  1. Stmts(statements):语句覆盖率,程序中的每个语句是否都已执行。
  2. Branch:分支覆盖率,是否执行了每个分支。
  3. Funcs:函数覆盖率,是否执行了每个函数。
  4. Lines:行覆盖率,是否执行了每一行代码。

可能有人会有疑问,1 和 4 不是一样吗?其实不一样,因为一行代码可以包含好几个语句。

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (typeof a != 'number') throw new TypeError('参数必须为数值型')

例如上面两段代码,它们对应的测试覆盖率就不一样。现在把测试类型错误的那一行代码注释掉,再试试:

// expect(() => abs('abc')).toThrow(TypeError)

第一段代码对应的覆盖率:

【JS】带你入门前端工程(四):测试

第二段代码对应的覆盖率:

【JS】带你入门前端工程(四):测试

它们未执行的语句都是一样,但第一段代码 Lines 覆盖率更低,因为它有一行代码没执行。而第二段代码未执行的语句和判断语句是在同一行,所以 Lines 覆盖率为 100%。

TDD 测试驱动开发

TDD(Test-Driven Development) 就是根据需求提前把测试代码写好,然后根据测试代码实现功能。

TDD 的初衷是好的,但如果你的需求经常变(你懂的),那就不是一件好事了。很有可能你天天都在改测试代码,业务代码反而没怎么动。

所以 TDD 用不用还得取决于业务需求是否经常变更,以及你对需求是否有清晰的认识。

E2E 测试

端到端测试,主要是模拟用户对页面进行一系列操作并验证其是否符合预期。本章将使用 Cypress 讲解 E2E 测试。

Cypress 在进行 E2E 测试时,会打开 Chrome 浏览器,然后根据测试代码对页面进行操作,就像一个正常的用户在操作页面一样。

安装

npm i -D cypress

打开 package.json 文件,在 scripts 新增一条命令:

"cypress": "cypress open"

然后执行 npm run cypress 就可以打开 Cypress。首次打开会自动创建 Cypress 提供的默认测试脚本。

【JS】带你入门前端工程(四):测试

【JS】带你入门前端工程(四):测试

点击右边的 Run 19 integration specs 就会开始执行测试。

【JS】带你入门前端工程(四):测试

第一次测试

打开 cypress 目录,在 integration 目录下新建一个 e2e.spec.js 测试文件:

describe('The Home Page', () => {

it('successfully loads', () => {

cy.visit('http://localhost:8080')

})

})

运行它,如无意外应该会看到一个测试失败的提示。

【JS】带你入门前端工程(四):测试

因为测试文件要求访问 http://localhost:8080 服务器,但现在还没有。所以我们需要使用 express 创建一个服务器,新建 server.js 文件,输入以下代码:

// server.js

const express = require('express')

const app = express()

const port = 8080

app.get('/', (req, res) => {

res.send('Hello World!')

})

app.listen(port, () => {

console.log(`Example app listening at http://localhost:${port}`)

})

执行 node server.js,重新运行测试,这次就可以看到正确的结果了。

【JS】带你入门前端工程(四):测试

PS: 如果你使用了 ESlint 来校验代码,则需要下载 eslint-plugin-cypress 插件,否则 Cypress 的全局命令会报错。下载插件后,打开 .eslintrc 文件,在 plugins 选项中加上 cypress

"plugins": [

"cypress"

]

模仿用户登录

上一个测试实在是有点小儿科,这次我们来写一个稍微复杂一点的测试,模仿用户登录:

  1. 用户打开登录页 /login.html

  2. 输入账号密码(都是 admin
  3. 登录成功后,跳转到 /index.html

首先需要重写服务器,修改一下 server.js 文件的代码:

// server.js

const bodyParser = require('body-parser')

const express = require('express')

const app = express()

const port = 8080

app.use(express.static('public'))

app.use(bodyParser.urlencoded({ extended: false }))

app.use(bodyParser.json())

app.post('/login', (req, res) => {

const { account, password } = req.body

// 由于没有注册功能,所以假定账号密码都为 admin

if (account == 'admin' && password == 'admin') {

res.send({

msg: '登录成功',

code: 0,

})

} else {

res.send({

msg: '登录失败,请输入正确的账号密码',

code: 1,

})

}

})

app.listen(port, () => {

console.log(`Example app listening at http://localhost:${port}`)

})

由于没有注册功能,所以暂时在后端写死账号密码为 admin。然后新建两个 html 文件:login.htmlindex.html,放在 public 目录。

<!-- login.html  -->

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>login</title>

<style>

div {

text-align: center;

}

button {

display: inline-block;

line-height: 1;

white-space: nowrap;

cursor: pointer;

text-align: center;

box-sizing: border-box;

outline: none;

margin: 0;

transition: 0.1s;

font-weight: 500;

padding: 12px 20px;

font-size: 14px;

border-radius: 4px;

color: #fff;

background-color: #409eff;

border-color: #409eff;

border: 0;

}

button:active {

background: #3a8ee6;

border-color: #3a8ee6;

color: #fff;

}

input {

display: block;

margin: auto;

margin-bottom: 10px;

-webkit-appearance: none;

background-color: #fff;

background-image: none;

border-radius: 4px;

border: 1px solid #dcdfe6;

box-sizing: border-box;

color: #606266;

font-size: inherit;

height: 40px;

line-height: 40px;

outline: none;

padding: 0 15px;

transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);

}

</style>

</head>

<body>

<div>

<input type="text" placeholder="请输入账号" class="account">

<input type="password" placeholder="请输入密码" class="password">

<button>登录</button>

</div>

<script></script>

<script>

document.querySelector('button').onclick = () => {

axios.post('/login', {

account: document.querySelector('.account').value,

password: document.querySelector('.password').value,

})

.then(res => {

if (res.data.code == 0) {

location.href = '/index.html'

} else {

alert(res.data.msg)

}

})

}

</script>

</body>

</html>

<!-- index.html  -->

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>index</title>

</head>

<body>

Hello World!

</body>

</html>

login.html 静态页

【JS】带你入门前端工程(四):测试

index.html 静态页

【JS】带你入门前端工程(四):测试

然后把测试文件内容改一下:

describe('The Home Page', () => {

it('login', () => {

cy.visit('http://localhost:8080/login.html')

// 输入账号密码

cy.get('.account').type('admin')

cy.get('.password').type('admin')

cy.get('button').click()

// 重定向到 /index

cy.url().should('include', 'http://localhost:8080/index.html')

// 断言 index.html 页面是包含 Hello World! 文本

cy.get('body').should('contain', 'Hello World!')

})

})

现在重新运行服务器 node server.js,再执行 npm run cypress,点击右边的 Run... 开始测试。

【JS】带你入门前端工程(四):测试

测试结果正确。为了统一脚本的使用规范,最好将 node server.js 命令替换为 npm run start

"scripts": {

"test": "jest --coverage test/",

"lint": "eslint --ext .js test/ src/",

"start": "node server.js",

"cypress": "cypress open"

}

小结

本章所有的测试用例都可以在我的 github 上找到,建议把项目克隆下来,亲自运行一遍。

参考资料

  • 单元测试到底是什么?应该怎么做?- coolhappy 的回答
  • Jest
  • Cypress
  • 代码覆盖率

带你入门前端工程 全文目录:

  1. 技术选型:如何进行技术选型?
  2. 统一规范:如何制订规范并利用工具保证规范被严格执行?
  3. 前端组件化:什么是模块化、组件化?
  4. 测试:如何写单元测试和 E2E(端到端) 测试?
  5. 构建工具:构建工具有哪些?都有哪些功能和优势?
  6. 自动化部署:如何利用 Jenkins、Github Actions 自动化部署项目?
  7. 前端监控:讲解前端监控原理及如何利用 sentry 对项目实行监控。
  8. 性能优化(一):如何检测网站性能?有哪些实用的性能优化规则?
  9. 性能优化(二):如何检测网站性能?有哪些实用的性能优化规则?
  10. 重构:为什么做重构?重构有哪些手法?
  11. 微服务:微服务是什么?如何搭建微服务项目?
  12. Severless:Severless 是什么?如何使用 Severless?

javascript前端

阅读 123更新于 今天 07:40

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

谭光志

公众号:前端编程技术分享

5k 声望

7.5k 粉丝

0 条评论

得票时间

avatar

谭光志

公众号:前端编程技术分享

5k 声望

7.5k 粉丝

宣传栏

什么是测试

维基百科的定义:

也可以这样理解:测试的作用是为了提高代码质量和可维护性。

  1. 提高代码质量:测试就是找 BUG,找出 BUG,然后解决它。BUG 少了,代码质量自然就高了。
  2. 可维护性:对现有代码进行修改、新增功能从而造成的成本越低,可维护性就越高。

什么时候写测试

如果你的程序非常简单,可以不用写测试。例如下面的程序,功能简单,只有十几行代码:

function add(a, b) {

return a + b

}

function sum(data = []) {

let result = 0

data.forEach(val => {

result = add(result, val)

})

return result

}

console.log(sum([1,2,3,4,5,6,7,8,9,10])) // 55

如果你的程序有数百行代码,但封装得很好,完美的践行了模块化的理念。每个模块功能单一、代码少,也可以不用写测试。

如果你的程序有成千上万行代码,数十个模块,模块与模块之间的交互错综复杂。在这种情况下,就需要写测试了。试想一下,在你对一个非常复杂的项目进行修改后,如果没有测试会是什么情况?你需要将跟这次修改有关的每个功能都手动测一边,以防止有 BUG 出现。但如果你写了测试,只需执行一条命令就能知道结果,省时省力。

测试类型与框架

测试类型有很多种:单元测试、集成测试、白盒测试...

测试框架也有很多种:Jest、Jasmine、LambdaTest...

本章将只讲解单元测试和 E2E 测试(end-to-end test 端到端测试)。其中单元测试使用的测试框架为 Jest,E2E 使用的测试框架为 Cypress。

Jest

安装

npm i -D jest

打开 package.json 文件,在 scripts 下添加测试命令:

"scripts": {

"test": "jest",

}

然后在项目根目录下新建 test 目录,作为测试目录。

单元测试

什么是单元测试?维基百科中给出的定义为:

从前端角度来看,单元测试就是对一个函数、一个组件、一个类做的测试,它针对的粒度比较小。

单元测试应该怎么写呢?

  1. 根据正确性写测试,即正确的输入应该有正常的结果。
  2. 根据错误性写测试,即错误的输入应该是错误的结果。

对一个函数做测试

例如一个取绝对值的函数 abs(),输入 1,2,结果应该与输入相同;输入 -1,-2,结果应该与输入相反。如果输入非数字,例如 "abc",应该抛出一个类型错误。

// main.js

function abs(a) {

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (a < 0) return -a

return a

}

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

expect(() => abs('abc')).toThrow(TypeError) // 类型错误

})

现在我们需要测试一下 abs() 函数:在 src 目录新建一个 main.js 文件,在 test 目录新建一个 test.spec.js 文件。然后将上面的两个函数代码写入对应的文件,执行 npm run test,就可以看到测试效果了。

【JS】带你入门前端工程(四):测试

对一个类做测试

假设有这样一个类:

class Math {

abs() {

}

sqrt() {

}

pow() {

}

...

}

我们必须把这个类的所有方法都测一遍。

test('Math.abs', () => {

// ...

})

test('Math.sqrt', () => {

// ...

})

test('Math.pow', () => {

// ...

})

对一个组件做测试

组件测试比较难,因为很多组件都涉及了 DOM 操作。

例如一个上传图片组件,它有一个将图片转成 base64 码的方法,那要怎么测试呢?一般测试都是跑在 node 环境下的,而 node 环境没有 DOM 对象。

我们先来回顾一下上传图片的过程:

  1. 点击 <input type="file" />,选择图片上传。
  2. 触发 inputchange 事件,获取 file 对象。
  3. FileReader 将图片转换成 base64 码。

这个过程和下面的代码是一样的:

document.querySelector('input').onchange = function fileChangeHandler(e) {

const file = e.target.files[0]

const reader = new FileReader()

reader.onload = (res) => {

const fileResult = res.target.result

console.log(fileResult) // 输出 base64 码

}

reader.readAsDataURL(file)

}

上面的代码只是模拟,真实情况下应该是这样使用:

document.querySelector('input').onchange = function fileChangeHandler(e) {

const file = e.target.files[0]

tobase64(file)

}

function tobase64(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader()

reader.onload = (res) => {

const fileResult = res.target.result

resolve(fileResult) // 输出 base64 码

}

reader.readAsDataURL(file)

})

}

可以看到,上面的代码出现了 window 的事件对象 eventFileReader。也就是说,只要我们能够提供这两个对象,就可以在任何环境下运行它。所以我们可以在测试环境下加上这两个对象:

// 重写 File

window.File = function () {}

// 重写 FileReader

window.FileReader = function () {

this.readAsDataURL = function () {

this.onload

&& this.onload({

target: {

result: fileData,

},

})

}

}

然后测试可以这样写:

// 提前写好文件内容

const fileData = 'data:image/test'

// 提供一个假的 file 对象给 tobase64() 函数

function test() {

const file = new File()

const event = { target: { files: [file] } }

file.type = 'image/png'

file.name = 'test.png'

file.size = 1024

it('file content', (done) => {

tobase64(file).then(base64 => {

expect(base64).toEqual(fileData) // 'data:image/test'

done()

})

})

}

// 执行测试

test()

通过这种 hack 的方式,我们就实现了对涉及 DOM 操作的组件的测试。我的 vue-upload-imgs 库就是通过这种方式写的单元测试,有兴趣可以了解一下(测试文件放在 test 目录)。

测试覆盖率

什么是测试覆盖率?用一个公式来表示:代码覆盖率 = 已执行的代码数 / 代码总数。Jest 如果要开启测试覆盖率统计,只需要在 Jest 命令后面加上 --coverage 参数:

"scripts": {

"test": "jest --coverage",

}

现在我们用刚才的测试用例再试一遍,看看测试覆盖率。

// main.js

function abs(a) {

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (a < 0) return -a

return a

}

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

expect(() => abs('abc')).toThrow(TypeError) // 类型错误

})

【JS】带你入门前端工程(四):测试

上图表示每一项覆盖率都是 100%。

现在我们把测试类型错误的那一行代码注释掉,再试试。

// test.spec.js

test('abs', () => {

expect(abs(1)).toBe(1)

expect(abs(0)).toBe(0)

expect(abs(-1)).toBe(1)

// expect(() => abs('abc')).toThrow(TypeError)

})

【JS】带你入门前端工程(四):测试

可以看到测试覆盖率下降了,为什么会这样呢?因为 abs() 函数中判断类型错误的那个分支的代码没有执行。

// 就是这一个分支语句

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

覆盖率统计项

从覆盖率的图片可以看到一共有 4 个统计项:

  1. Stmts(statements):语句覆盖率,程序中的每个语句是否都已执行。
  2. Branch:分支覆盖率,是否执行了每个分支。
  3. Funcs:函数覆盖率,是否执行了每个函数。
  4. Lines:行覆盖率,是否执行了每一行代码。

可能有人会有疑问,1 和 4 不是一样吗?其实不一样,因为一行代码可以包含好几个语句。

if (typeof a != 'number') {

throw new TypeError('参数必须为数值型')

}

if (typeof a != 'number') throw new TypeError('参数必须为数值型')

例如上面两段代码,它们对应的测试覆盖率就不一样。现在把测试类型错误的那一行代码注释掉,再试试:

// expect(() => abs('abc')).toThrow(TypeError)

第一段代码对应的覆盖率:

【JS】带你入门前端工程(四):测试

第二段代码对应的覆盖率:

【JS】带你入门前端工程(四):测试

它们未执行的语句都是一样,但第一段代码 Lines 覆盖率更低,因为它有一行代码没执行。而第二段代码未执行的语句和判断语句是在同一行,所以 Lines 覆盖率为 100%。

TDD 测试驱动开发

TDD(Test-Driven Development) 就是根据需求提前把测试代码写好,然后根据测试代码实现功能。

TDD 的初衷是好的,但如果你的需求经常变(你懂的),那就不是一件好事了。很有可能你天天都在改测试代码,业务代码反而没怎么动。

所以 TDD 用不用还得取决于业务需求是否经常变更,以及你对需求是否有清晰的认识。

E2E 测试

端到端测试,主要是模拟用户对页面进行一系列操作并验证其是否符合预期。本章将使用 Cypress 讲解 E2E 测试。

Cypress 在进行 E2E 测试时,会打开 Chrome 浏览器,然后根据测试代码对页面进行操作,就像一个正常的用户在操作页面一样。

安装

npm i -D cypress

打开 package.json 文件,在 scripts 新增一条命令:

"cypress": "cypress open"

然后执行 npm run cypress 就可以打开 Cypress。首次打开会自动创建 Cypress 提供的默认测试脚本。

【JS】带你入门前端工程(四):测试

【JS】带你入门前端工程(四):测试

点击右边的 Run 19 integration specs 就会开始执行测试。

【JS】带你入门前端工程(四):测试

第一次测试

打开 cypress 目录,在 integration 目录下新建一个 e2e.spec.js 测试文件:

describe('The Home Page', () => {

it('successfully loads', () => {

cy.visit('http://localhost:8080')

})

})

运行它,如无意外应该会看到一个测试失败的提示。

【JS】带你入门前端工程(四):测试

因为测试文件要求访问 http://localhost:8080 服务器,但现在还没有。所以我们需要使用 express 创建一个服务器,新建 server.js 文件,输入以下代码:

// server.js

const express = require('express')

const app = express()

const port = 8080

app.get('/', (req, res) => {

res.send('Hello World!')

})

app.listen(port, () => {

console.log(`Example app listening at http://localhost:${port}`)

})

执行 node server.js,重新运行测试,这次就可以看到正确的结果了。

【JS】带你入门前端工程(四):测试

PS: 如果你使用了 ESlint 来校验代码,则需要下载 eslint-plugin-cypress 插件,否则 Cypress 的全局命令会报错。下载插件后,打开 .eslintrc 文件,在 plugins 选项中加上 cypress

"plugins": [

"cypress"

]

模仿用户登录

上一个测试实在是有点小儿科,这次我们来写一个稍微复杂一点的测试,模仿用户登录:

  1. 用户打开登录页 /login.html

  2. 输入账号密码(都是 admin
  3. 登录成功后,跳转到 /index.html

首先需要重写服务器,修改一下 server.js 文件的代码:

// server.js

const bodyParser = require('body-parser')

const express = require('express')

const app = express()

const port = 8080

app.use(express.static('public'))

app.use(bodyParser.urlencoded({ extended: false }))

app.use(bodyParser.json())

app.post('/login', (req, res) => {

const { account, password } = req.body

// 由于没有注册功能,所以假定账号密码都为 admin

if (account == 'admin' && password == 'admin') {

res.send({

msg: '登录成功',

code: 0,

})

} else {

res.send({

msg: '登录失败,请输入正确的账号密码',

code: 1,

})

}

})

app.listen(port, () => {

console.log(`Example app listening at http://localhost:${port}`)

})

由于没有注册功能,所以暂时在后端写死账号密码为 admin。然后新建两个 html 文件:login.htmlindex.html,放在 public 目录。

<!-- login.html  -->

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>login</title>

<style>

div {

text-align: center;

}

button {

display: inline-block;

line-height: 1;

white-space: nowrap;

cursor: pointer;

text-align: center;

box-sizing: border-box;

outline: none;

margin: 0;

transition: 0.1s;

font-weight: 500;

padding: 12px 20px;

font-size: 14px;

border-radius: 4px;

color: #fff;

background-color: #409eff;

border-color: #409eff;

border: 0;

}

button:active {

background: #3a8ee6;

border-color: #3a8ee6;

color: #fff;

}

input {

display: block;

margin: auto;

margin-bottom: 10px;

-webkit-appearance: none;

background-color: #fff;

background-image: none;

border-radius: 4px;

border: 1px solid #dcdfe6;

box-sizing: border-box;

color: #606266;

font-size: inherit;

height: 40px;

line-height: 40px;

outline: none;

padding: 0 15px;

transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);

}

</style>

</head>

<body>

<div>

<input type="text" placeholder="请输入账号" class="account">

<input type="password" placeholder="请输入密码" class="password">

<button>登录</button>

</div>

<script></script>

<script>

document.querySelector('button').onclick = () => {

axios.post('/login', {

account: document.querySelector('.account').value,

password: document.querySelector('.password').value,

})

.then(res => {

if (res.data.code == 0) {

location.href = '/index.html'

} else {

alert(res.data.msg)

}

})

}

</script>

</body>

</html>

<!-- index.html  -->

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>index</title>

</head>

<body>

Hello World!

</body>

</html>

login.html 静态页

【JS】带你入门前端工程(四):测试

index.html 静态页

【JS】带你入门前端工程(四):测试

然后把测试文件内容改一下:

describe('The Home Page', () => {

it('login', () => {

cy.visit('http://localhost:8080/login.html')

// 输入账号密码

cy.get('.account').type('admin')

cy.get('.password').type('admin')

cy.get('button').click()

// 重定向到 /index

cy.url().should('include', 'http://localhost:8080/index.html')

// 断言 index.html 页面是包含 Hello World! 文本

cy.get('body').should('contain', 'Hello World!')

})

})

现在重新运行服务器 node server.js,再执行 npm run cypress,点击右边的 Run... 开始测试。

【JS】带你入门前端工程(四):测试

测试结果正确。为了统一脚本的使用规范,最好将 node server.js 命令替换为 npm run start

"scripts": {

"test": "jest --coverage test/",

"lint": "eslint --ext .js test/ src/",

"start": "node server.js",

"cypress": "cypress open"

}

小结

本章所有的测试用例都可以在我的 github 上找到,建议把项目克隆下来,亲自运行一遍。

参考资料

  • 单元测试到底是什么?应该怎么做?- coolhappy 的回答
  • Jest
  • Cypress
  • 代码覆盖率

带你入门前端工程 全文目录:

  1. 技术选型:如何进行技术选型?
  2. 统一规范:如何制订规范并利用工具保证规范被严格执行?
  3. 前端组件化:什么是模块化、组件化?
  4. 测试:如何写单元测试和 E2E(端到端) 测试?
  5. 构建工具:构建工具有哪些?都有哪些功能和优势?
  6. 自动化部署:如何利用 Jenkins、Github Actions 自动化部署项目?
  7. 前端监控:讲解前端监控原理及如何利用 sentry 对项目实行监控。
  8. 性能优化(一):如何检测网站性能?有哪些实用的性能优化规则?
  9. 性能优化(二):如何检测网站性能?有哪些实用的性能优化规则?
  10. 重构:为什么做重构?重构有哪些手法?
  11. 微服务:微服务是什么?如何搭建微服务项目?
  12. Severless:Severless 是什么?如何使用 Severless?

以上是 【JS】带你入门前端工程(四):测试 的全部内容, 来源链接: utcz.com/a/108386.html

回到顶部