4、Mocha测试框架深入:describe与it块、钩子函数、异步测试与超时设置
Mocha 这个框架,说实话,是我用得最顺手的测试工具之一。它足够灵活,又不失强大。今天咱们就把它最核心的几个功能掰开揉碎了讲清楚。
4.1 describe 与 it:测试的组织单元
先聊聊最基础的结构。describe 和 it,说白了就是用来「分组」和「写用例」的。
describe 用来定义一个「测试套件」。你可以把它想象成一个文件夹,里面装着一堆相关的测试用例。它可以嵌套,用来表达更复杂的层级关系。
it 用来定义一个具体的「测试用例」。每个 it 块里只测一件事,这是原则。
describe('用户模块', function() {
describe('登录功能', function() {
it('应该能正确登录', function() {
// 测试逻辑
});
it('密码错误时应该返回提示', function() {
// 测试逻辑
});
});
});
我个人习惯,describe 的命名用名词短语,it 的命名用「应该...」这种句式。这样跑测试的时候,输出结果读起来就像一份需求文档,一目了然。
4.2 钩子函数:before、after、beforeEach、afterEach
钩子函数是 Mocha 里非常实用的机制。它们会在测试执行的不同阶段自动运行。
| 钩子 | 执行时机 | 典型用途 |
|---|---|---|
| before() | 当前 describe 块内所有测试执行之前,只跑一次 | 初始化数据库连接、启动服务器 |
| after() | 当前 describe 块内所有测试执行之后,只跑一次 | 关闭数据库连接、清理全局资源 |
| beforeEach() | 当前 describe 块内每个 it 执行之前,都会跑一次 | 重置测试数据、创建新对象 |
| afterEach() | 当前 describe 块内每个 it 执行之后,都会跑一次 | 清理临时文件、记录测试日志 |
举个例子,假设我们要测试一个「用户注册」功能:
describe('用户注册', function() {
let db;
before(async function() {
// 只做一次:连接数据库
db = await connectToDatabase();
});
after(async function() {
// 只做一次:断开连接
await db.close();
});
beforeEach(async function() {
// 每个用例前:清空用户表
await db.collection('users').deleteMany({});
});
it('应该能注册新用户', async function() {
const result = await registerUser({ name: '张三', email: 'test@test.com' });
// 断言...
});
it('重复邮箱应该报错', async function() {
await registerUser({ name: '李四', email: 'test@test.com' });
// 再次注册相同邮箱,应该抛出异常
// 断言...
});
});
嗯,这里要注意一点:钩子函数的执行顺序是跟 describe 的嵌套层级有关的。外层钩子先执行,内层后执行。我曾经因为没搞清楚这个顺序,调试了一个下午才发现是钩子执行时机不对。
4.3 异步测试:三种写法
Node.js 里异步操作是家常便饭。Mocha 对异步测试的支持很到位,主要有三种方式。
4.3.1 回调风格
这是最传统的方式。在 it 的回调函数里加一个参数,通常叫 done。Mocha 会等到 done 被调用才结束这个用例。
it('应该能异步读取文件', function(done) {
fs.readFile('/path/to/file', 'utf8', function(err, data) {
if (err) {
done(err); // 测试失败
return;
}
// 断言...
done(); // 测试通过
});
});
注意,如果 done 被调用了多次,Mocha 会报错。所以记得加 return 或者用 if-else 控制好流程。
4.3.2 Promise 风格
如果你的函数返回 Promise,那就更简单了。直接 return 这个 Promise 就行。
it('应该能异步获取用户信息', function() {
return fetchUser('123').then(function(user) {
// 断言...
});
});
Mocha 会等 Promise 状态变成 resolved 或 rejected。如果 rejected 了,测试就算失败。
4.3.3 async/await 风格
这是我最推荐的方式。代码写起来像同步一样,可读性最好。
it('应该能异步更新用户信息', async function() {
const user = await fetchUser('123');
const updatedUser = await updateUser('123', { name: '新名字' });
// 断言...
});
说白了,async/await 就是 Promise 的语法糖。但用起来确实舒服很多。我个人现在写测试,90% 的情况都用 async/await。
4.4 测试超时设置
默认情况下,Mocha 的测试超时时间是 2000 毫秒(2 秒)。如果某个测试用例执行时间超过了这个值,Mocha 就会认为它失败了。
但有些操作确实比较慢,比如文件上传、调用外部 API。这时候就需要调整超时时间。
有三种方式可以设置:
- 全局设置:在命令行加参数
--timeout 10000,所有用例的超时都变成 10 秒。 - 套件级别:在 describe 块里用
this.timeout(5000),影响当前套件下的所有用例。 - 用例级别:在 it 块里用
this.timeout(3000),只影响当前用例。
describe('文件上传', function() {
// 这个套件下的所有用例,超时设为 10 秒
this.timeout(10000);
it('应该能上传大文件', function(done) {
// 这个用例单独设置超时 30 秒
this.timeout(30000);
// 上传逻辑...
});
it('应该能上传小文件', function() {
// 使用套件的 10 秒超时
// 上传逻辑...
});
});
这里有个坑:this.timeout() 在箭头函数里不能用。因为箭头函数不绑定自己的 this,它会继承外层作用域的 this。所以记得用普通函数。
好了,Mocha 的这些核心功能就讲到这里。总结一下:describe 和 it 用来组织测试结构,钩子函数帮你管理测试生命周期,异步测试有三种写法任你选择,超时设置让你能灵活应对各种场景。把这些用熟了,写测试就不再是件痛苦的事了。