使用 Chai 进行质量保证和测试
使用 Chai 进行质量保证和测试的方法如下:
- Chai 是一个用于 node.js 和浏览器的 BDD/TDD 断言库,可以与任何 JavaScript 测试框架搭配使用 12。
- Chai 提供了多种断言风格,包括 expect, should, assert 等,可以根据个人喜好和习惯选择 12。
- Chai 还支持通过插件来扩展其功能,可以使用官方提供的或者第三方开发的插件,也可以自己编写插件 12。
- Chai 的安装和使用非常简单,只需要在项目中运行
npm i chai
命令,然后在测试文件中引入const chai = require('chai')
即可 2。 - Chai 的文档和示例非常丰富和详细,可以在其官网或者 GitHub 上查看 13。
1.了解 JavaScript 断言的工作原理
assert.isNull` 或 `assert.isNotNull
JavaScript 断言的工作原理是这样的:
- 断言是一种用来检查代码是否符合预期的方法,通常用于测试和调试。
- 断言可以使用 assert 函数或者 console.assert 方法来实现,它们接受一个条件表达式和一个可选的错误信息作为参数。
- 如果条件表达式为真,断言通过,什么也不发生;如果条件表达式为假,断言失败,抛出一个错误或者在控制台输出错误信息。
- 断言可以帮助我们发现代码中的逻辑错误、边界情况、非法输入等问题,提高代码的质量和可靠性。
- 但是断言也有一些局限性,比如它不能替代正常的异常处理,它只能用于开发和测试阶段,它可能会影响代码的性能和连贯性等。
- 因此,断言应该谨慎使用,只在必要的地方进行检查,避免过度依赖或滥用。
如果你想了解更多关于 JavaScript 断言的内容,你可以参考以下链接:
- JavaScript 中的「断言」该怎么用? - 知乎
- Assertions - JavaScript | MDN - MDN Web Docs
- 了解 JavaScript 断言的工作原理 - freeCodeCamp.org
2.测试变量或函数是否已定义
assert.isDefined()` 或 `assert.isUndefined()
3.使用 Assert.isOk() 和 Assert.isNotOK()
isOk()
用来测试值是否为真值,isNotOk()
用来测试值是否为假值。
4.测试真实性
isTrue()
仅当给出的值为 Boolean 的 true
时可以通过测试;isNotTrue()
则会在给出除 true
以外的值时通过测试。
isFalse()
和 isNotFalse()
同样存在,与上面提到的两个方法类似,只不过它们针对值为 false
的布尔值。
5.用两个等号断言相等
equal()
使用 ==
比较对象。
assert.equal` 或 `assert.notEqual
assert.equal(12, '12', 'Numbers are coerced into strings with ==');
assert.notEqual({ value: 1 }, { value: 1 }, '== compares object references');
6.用三个等号断言严格相等
strictEqual()
使用 “= = =” 比较对象。
assert.strictEqual` 或 `assert.notStrictEqual
assert.strictEqual(6 * '2', 12);//由于 6 * '2' 中的 '2' 是一个字符串,因此它将被转换为数字 2。表达式 6 * '2' 的结果是 12
7.用 Assert.deepEqual() 和 Assert.notDeepEqual() 断言深度相等
deepEqual()
断言两个对象是否深度相等。
assert.deepEqual` 或 `assert.notDeepEqual
在 JavaScript 中,assert.deepEqual() 方法用于比较两个对象是否相等,包括它们的属性和属性值。如果两个对象的属性和属性值相同,则返回 true;否则返回 false
8.比较两个元素的属性
assert.isAbove:n1>n2 或 assert.isAtMost:n1<=n2
9.测试一个值是否小于或等于另一个值
assert.isBelow`:n1<n2 或 `assert.isAtLeast:n1>=n2
10.测试某个值是否在特定范围内
.approximately(actual, expected, delta, [message])
assert.approximately
assert.approximately(weirdNumbers(0.5), 1, 0.5);
assert.approximately(weirdNumbers(0.2), 1, 0.8);
断言 actual
等于 expected
,在 +/- delta
的范围内。
11.测试某个值是否为数组
assert.isArray
或 assert.isNotArray
12.测试数组是否包含项目
assert.include` 或 `assert.notInclude
13.测试某个值是否为字符串
isString
或 isNotString
断言一个值是否为字符串。
assert.isString` 或 `assert.isNotString
14.测试字符串是否包含子字符串
include()
和 notInclude()
同样可以用于字符串。 include()
用于断言字符串中包含某个子字符串。
assert.include` 或 `assert.notInclude
15.使用正则表达式测试字符串
match()
断言一个值匹配一个正则表达式(第二个参数)。
assert.match` 或 `assert.notMatch
16.测试对象是否具有某个属性
property
断言一个对象含有给定属性。
assert.property` 或 `assert.notProperty
17.测试值是否为特定数据结构类型
#typeOf
断言一个值的类型符合给定的类型,这个类型与 Object.prototype.toString
一致。
assert.typeOf` 或 `assert.notTypeOf
18.测试对象是否是构造函数的实例
#instanceOf
断言一个对象是一个构造器的实例。
assert.instanceOf` 或 `assert.notInstanceOf
assert.instanceOf(airlinePlane, Object, 'everything is an Object');
19.使用 Chai-HTTP 在 API 端上运行功能测试
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
9 | PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
Mocha 允许你使用名为 chai-http
的插件测试异步操作,例如调用 API 端点。
以下是使用 chai-http
测试名为 'GET /hello?name=[name] => "hello [name]"'
的套件的示例:
suite('GET /hello?name=[name] => "hello [name]"', function () {
test('?name=John', function (done) {
chai
.request(server)
.keepOpen()
.get('/hello?name=John')
.end(function (err, res) {
assert.equal(res.status, 200, 'Response status should be 200');
assert.equal(res.text, 'hello John', 'Response should be "hello John"');
done();
});
});
});
该测试向服务器发送一个 GET
请求,并将名称作为 URL 查询字符串(?name=John
)。 在end
方法的回调函数中,接收到响应对象(res
)并包含 status
属性。
第一个 assert.equal
检查状态是否为 200
。 第二个 assert.equal
检查响应字符串(res.text
)是否为 "hello John"
。
同时,请注意测试的回调函数中的 done
参数。 在测试结束时,调用它且不带参数,是发出异步操作完成所必需的信号。
异步操作:是一种编程模型,它允许程序在等待某个操作完成时继续执行其他任务。在 JavaScript 中,异步操作通常使用回调函数来实现。在传统的同步编程模型中,程序的执行是按照代码顺序依次执行的。而在异步编程模型中,程序的执行顺序不再与代码顺序一致,因为异步操作的结果不是立即返回的。相反,程序会继续执行其他任务,直到异步操作完成并调用回调函数。这种编程模型可以提高程序的性能和响应速度,因为它允许程序在等待某个操作完成时继续执行其他任务。如果你想了解更多关于 JavaScript 异步编程的内容,可以参考这个 菜鸟教程。
最后,请注意 request
方法后面的 keepOpen
方法。 通常,你会从命令行中运行你的测试,或者作为自动集成过程的一部分,你可以让 chai-http
自动启动和停止你的服务器。
然而,当你提交项目链接时运行的测试需要你的服务器是正常的,所以你需要使用 keepOpen
方法来防止 chai-http
停止你的服务器。
在 tests/2_functional-tests.js
中,修改 'Test GET /hello with no name'
测试(// #1
),对响应的 status
和 text
使用断言来通过测试。 不要改变传递给断言的参数。
不应该有任何 URL 查询。 如果没有名称 URL 查询,端点将使用 hello Guest
进行响应。
20.使用 Chai-HTTP II 在 API 端上运行功能测试
在
tests/2_functional-tests.js
中,修改'Test GET /hello with your name'
测试(// #2
),对响应的status
和text
使用断言来通过测试。通过将
?name=<your_name>
附加到路由,将你的姓名作为 URL 查询发送。 端点响应'hello <your_name>'
。
21.使用 Chai-HTTP III 的 PUT 方法对 API 请求运行功能测试
当你测试一个 PUT
请求时,你经常会随同它一起发送数据。 你在 PUT
请求中包含的数据被称为请求的主体。
要将 PUT
请求和 JSON 对象发送到 '/travellers'
端点,你可以使用 chai-http
插件的 put
和 send
方法:
chai
.request(server)
.keepOpen()
.put('/travellers')
.send({
"surname": [last name of a traveller of the past]
})
...
并且路由响应如下:
{
"name": [first name],
"surname": [last name],
"dates": [birth - death years]
}
请参阅服务器代码以了解对 '/travellers'
端点的不同响应。
在 tests/2_functional-tests.js
中,更改 'Send {surname: "Colombo"}'
测试(// #3
),并使用 put
和 send
方法来测试 '/travellers'
端点。
在你的 PUT 请求中发送以下 JSON 对象。
{
"surname": "Colombo"
}
在 request.end
的返回中检查以下情况:
status
应该是200
type
应该是application/json
body.name
应该是Cristoforo
body.surname
应该是Colombo
请按照以上顺序书写断言,顺序错误会影响系统判定。 此外,请确保在完成后删除 assert.fail()
。
assert.equal(res.status, 200);
assert.equal(res.type, 'application/json');
assert.equal(res.body.name, 'Cristoforo');
assert.equal(res.body.surname, 'Colombo');
22.使用 Chai-HTTP IV 的 PUT 方法对 API 响应运行功能测试
这个练习与上一个类似。
现在你知道如何测试一个 PUT
请求了,轮到你从头开始做了。
在 tests/2_functional-tests.js
中,更改 'Send {surname: "da Verrazzano"}'
测试(// #4
)并使用 put
和 send
方法来测试 '/travellers'
端点。
test('Send {surname: "da Verrazzano"}', function(done) {
chai
.request(server)
.keepOpen()
.put('/travellers')
.send({
"surname": "da Verrazzano"
})
.end(function(err,res){
assert.equal(res.status,200);
assert.equal(res.type,'application/json');
assert.equal(res.body.name,'Giovanni');
assert.equal(res.body.surname,'da Verrazzano');
done();
})
});
23.使用无头浏览器模拟操作
在接下来的挑战中,你将使用无头浏览器模拟人类与页面的交互。
无头浏览器是没有 GUI 的 Web 浏览器。 它们能够以与常规浏览器相同的方式呈现和解释 HTML、CSS 和 JavaScript,这使得它们对于测试网页特别有用。
在下面的挑战中,你将使用Zombie.js,它是一个轻量级的无头浏览器,不依赖额外的二进制文件来安装。 这一特点使其可以在 Replit 这样的有限环境中使用。 但是还有许多其他更强大的无头浏览器选项。
Mocha 允许你在任何实际测试运行之前运行一些代码。 这对做一些事情很有用,比如向数据库添加条目,这些条目将在其余测试中使用。
使用无头浏览器,在运行测试之前,你需要 访问 你要测试的页面。
suiteSetup
钩子仅在测试套件开始时执行一次。还有其他几种钩子类型,可以在每次测试前、每次测试后或测试套件结束时执行代码。 有关更多信息,请参阅 Mocha 文档。
在 tests/2_functional-tests.js
中,紧跟在 Browser
声明之后,将你的项目 URL 添加到变量的 site
属性:
Browser.site = 'https://boilerplate-mochachai.your-username.repl.co'; // Your URL here
然后在 'Functional Tests with Zombie.js'
套件的根级别,使用以下代码实例化 Browser
对象的新实例:
const browser = new Browser();
并使用 suiteSetup
钩子将 browser
定向到带有以下代码的 /
路由:
suiteSetup(function(done) {
return browser.visit('/', done);
});
24.使用无头浏览器运行功能测试
在页面上有一个输入表单。 它将数据作为 AJAX 请求发送到 PUT /travellers
端点。
当请求成功完成后,客户端代码将一个包含响应信息的 <div>
附加到 DOM 中。
下面是一个如何使用 Zombie.js 与表单互动的例子。
test('Submit the surname "Polo" in the HTML form', function (done) {
browser.fill('surname', 'Polo').then(() => {
browser.pressButton('submit', () => {
browser.assert.success();
browser.assert.text('span#name', 'Marco');
browser.assert.text('span#surname', 'Polo');
browser.assert.elements('span#dates', 1);
done();
});
});
});
首先,
browser
对象的fill
方法在表格的surname
字段中填入值'Polo'
。fill
返回一个 promise,所以then
被链接起来。在
then
回调中,browser
对象的pressButton
方法用于调用表单的submit
的事件侦听器。pressButton
方法是异步的。然后,一旦收到来自 AJAX 请求的响应,就会做出一些断言来确认:
- 响应状态是
200
<span id='name'></span>
元素的文本是'Marco'
<span id='surname'></span>
元素的文本是'Polo'
- 有
1
个<span id='dates'></span>
元素。最后,执行
done
,这是异步测试所必需的。
在
tests/2_functional-tests.js
中,在'Submit the surname "Colombo" in the HTML form'
测试(// #5
),自动执行以下操作:
- 在表格中填写姓氏
Colombo
。- 点击提交按钮
在
pressButton
回调中:
- 断言状态是正常的
200
。- 断言元素
span#name
中的文本是'Cristoforo'
。- 断言元素
span#surname
中的文本是'Colombo'
。- 断言有
span#dates
元素,它们的计数是1
。不要忘记删除
assert.fail()
调用。
test('Submit the surname "Colombo" in the HTML form', function(done) {
browser.fill('surname', 'Colombo').then(() => {
browser.pressButton('submit', () => {
browser.assert.success();
browser.assert.text('span#name', 'Cristoforo');
browser.assert.text('span#surname', 'Colombo');
browser.assert.elements('span#dates', 1);
done();
});
});
});
25.使用 无头浏览器 II 运行功能测试
在 tests/2_functional-tests.js
中,在 'Submit the surname "Vespucci" in the HTML form'
测试(// #6
),自动执行以下操作:
- 在表格中填写姓氏
Vespucci
。 - 点击提交按钮
在
pressButton
回调中:
- 断言状态是正常的
200
。- 断言元素
span#name
中的文本是'Amerigo'
。- 断言元素
span#surname
元素中的文本是'Vespucci'
。- 断言有
span#dates
元素,它们的计数是1
。
不要忘记删除 assert.fail()
调用。
test('Submit the surname "Vespucci" in the HTML form', function(done) {
browser.fill('surname', 'Vespucci').then(() => {
browser.pressButton('submit', () => {
browser.assert.success();
browser.assert.text('span#name', 'Amerigo');
browser.assert.text('span#surname', 'Vespucci');
browser.assert.elements('span#dates', 1);
done();
});
});
});