使用 Jasmine 进行 JavaScript 单元测试
随着互联网的飞速发展,Web 开发面临着前所未有的挑战。前端 JavaScript 代码量不断增加,多人协作以及团队成员技能差异,都要求对代码质量进行有效控制。Jasmine 作为一款流行的 JavaScript 测试框架,应运而生,为前端开发团队提供了标准化的测试解决方案。
Jasmine 概述
Jasmine 是一个独立的 JavaScript 测试框架,不依赖于任何其他 JavaScript 库。它拥有简洁直观的语法,能够让你轻松编写测试用例。当前最新版本为 2.0.0。
在 Jasmine 中,一个典型的单元测试由全局函数 describe 包裹,其中包含多个 it 函数,每个 it 函数又包含一个或多个断言(expectation)。
以下是一个基本的 Jasmine 测试示例:
describe("一个测试套件", function() {
it("包含一个期望的测试用例", function() {
expect(true).toBe(true);
});
});
获取 Jasmine
你可以从 Jasmine GitHub Releases 下载最新版本的压缩包。推荐下载 2.0.0 版本。
解压后,在 lib/jasmine-2.0.0 目录下,你会找到进行 JavaScript 测试所需的核心文件。
Jasmine 的运行依赖
Jasmine 的运行需要以下几个部分:
- 运行环境: 浏览器(如 IE, Firefox, Chrome)或 Node.js。
- 源文件: 由开发人员编写的业务逻辑 JavaScript 文件。
- 测试文件: 遵循 Jasmine API 编写的测试脚本。
- 结果输出: 测试结果会显示在网页或控制台中。
Jasmine 的使用
在你的项目中创建一个 test.html 文件,并包含以下基本结构:
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>Jasmine JavaScript 单元测试框架</title>
<link rel="stylesheet" href="jasmine/jasmine.css">
<script src="jasmine/jasmine.js"></script>
<script src="jasmine/jasmine-html.js"></script>
<script src="jasmine/boot.js"></script>
</head>
<body>
<p>JavaScript 单元测试示例</p>
<script src="src.js"></script>
<script src="test.js"></script>
</body>
</html>
在这个 HTML 文件中,我们引入了以下文件:
jasmine.js: Jasmine 框架的核心文件。jasmine-html.js: 用于在网页上显示测试结果的文件。boot.js: Jasmine 框架的启动脚本,应在jasmine.js加载完成后执行。src.js: 包含你的业务逻辑代码。test.js: 包含你的 Jasmine 测试用例。jasmine.css: 用于样式化测试结果页面的 CSS 文件。
boot.js 文件通常包含以下内容,用于初始化 Jasmine 环境并执行测试:
// report.js
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000; // 更新间隔(毫秒)
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var originalWindowOnload = window.onload;
window.onload = function() {
if (originalWindowOnload) {
originalWindowOnload();
}
runJasmineTests();
};
function runJasmineTests() {
jasmineEnv.execute();
}
})();
src.js 文件用于存放业务逻辑:
// src.js
function greet(name) {
return "Hello " + name;
}
test.js 文件则编写对应的单元测试:
// test.js
describe("一组基本函数", function() {
var personName;
it("测试 greet 函数", function() {
personName = "Alice";
var expectedGreeting = "Hello Alice";
expect(expectedGreeting).toEqual(greet(personName));
});
});
打开 test.html 文件,你将在浏览器中看到测试结果。
测试先行 (Test-Driven Development - TDD)
测试先行是一种开发实践,即在编写实际功能代码之前,先编写测试用例。这有助于确保代码的质量和设计的合理性。
假设我们有一个需求:实现一个单词反转的功能(例如,"ABCD" -> "DCBA")。
首先,在 test.js 中添加对应的测试用例:
// test.js (追加)
it("测试反转单词功能", function() {
expect("DCBA").toEqual(reverse("ABCD"));
});
此时运行测试,会因为 reverse 函数未定义而失败。
接着,在 src.js 中添加 reverse 函数的初步实现:
// src.js (追加)
function reverse(str) {
return "DCBA"; // 这是一个临时的、不正确的实现
}
再次运行测试,测试将通过,但这并不意味着功能完全正确。我们需要增加更多样化的测试用例来验证。
修改 test.js,增加更多测试场景:
// test.js (修改)
it("测试反转单词功能", function() {
expect("DCBA").toEqual(reverse("ABCD"));
expect("ecoC").toEqual(reverse("Coce")); // 添加一个新测试
});
此时测试会再次失败,因为我们期望输入"Coce"时输出"ecoC",但当前函数返回"DCBA"。
修改 src.js 中的 reverse 函数,使其具备正确的逻辑:
// src.js (修改)
function reverse(str) {
return str.split("").reverse().join("");
}
刷新浏览器,测试将再次成功。通过这种"测试先行"的方式,我们可以逐步构建健壮的代码。
Jasmine 语法实践
以下将介绍 Jasmine 的常用语法,这些都可以在 test.js 中进行实践。
断言 (Expectations)
Jasmine 提供了丰富的断言方法来验证代码的行为:
describe("Jasmine 函数套件", function() {
describe("断言", function() {
it("各种断言示例", function() {
// 值相等
expect("Hello").toEqual("Hello");
// 匹配正则表达式
expect("123.45").toMatch(/\d+\.\d{2}/);
// 是否为 null
expect(null).toBeNull();
// 字符串是否包含子串
expect("JavaScript").toContain("Script");
// 数值比较
expect(10).toBeLessThan(20);
expect(50).toBeGreaterThan(30);
// 变量定义和类型
var enabled = true;
var data;
expect(enabled).toBe(true); // 严格相等
expect(enabled).toBeDefined(); // 是否已定义
expect(data).toBeUndefined(); // 是否未定义
expect(enabled).toBeTruthy(); // 是否为真值
expect(!enabled).toBeFalsy(); // 是否为假值
// 检查函数是否抛出异常
var safeFunction = function() { return 1 + 1; };
var errorFunction = function() { throw new Error("Something went wrong"); };
expect(safeFunction).not.toThrow();
expect(errorFunction).toThrow();
});
});
});
设置与清理 (Setup and Teardown)
beforeEach 和 afterEach 可以在每个 it 块执行前后运行指定代码,常用于初始化和清理测试环境。
describe("设置与清理", function() {
var counter;
beforeEach(function() {
// 在每个 it 块执行前运行
counter = 0;
counter++;
});
afterEach(function() {
// 在每个 it 块执行后运行
counter = 0;
});
it("第一个测试用例", function() {
expect(counter).toEqual(1);
});
it("第二个测试用例", function() {
expect(counter).toEqual(1);
expect(true).toBe(true); // 可以包含多个断言
});
});
禁用测试套件
使用 xdescribe 可以临时禁用整个测试套件,使用 xit 可以禁用单个测试用例。这在调试或暂时跳过不相关测试时非常有用。
xdescribe("禁用测试套件", function() { // 这个套件及其内部的测试将不会执行
it("禁用中的测试", function() {
expect("test").toBe("test");
});
});
异步测试
Jasmine 提供了 runs, waits, 和 waitsFor 来处理异步操作的测试。
describe("异步支持", function() {
var result, isComplete;
it("异步操作测试", function() {
runs(function() {
isComplete = false;
result = null;
// 模拟一个异步操作,如 setTimeout
setTimeout(function() {
isComplete = true;
result = "Async Data";
}, 500);
});
// 等待 isComplete 变为 true,或者超时
waitsFor(function() {
return isComplete;
}, "异步操作应在 1 秒内完成", 1000); // 1000ms 超时
runs(function() {
expect(result).toBe("Async Data");
});
});
});
Jasmine API 概览
describe(description, function)`: 定义一个测试套件。it(expectation, function)`: 定义一个具体的测试用例。beforeEach(function)`: 在套件内所有it执行前运行。afterEach(function)`: 在套件内所有it执行后运行。expect(actual)`: 开始一个断言。
常用匹配器 (Matchers)
toBe(expected)`: 严格相等比较 (===)。toEqual(expected)`: 递归比较对象或数组的值。toMatch(expected)`: 使用正则表达式进行字符串匹配。toBeDefined()`: 检查值是否已定义。toBeUndefined()`: 检查值是否未定义。toBeNull()`: 检查值是否为null。toBeTruthy()`: 检查值是否为真值。toBeFalsy()`: 检查值是否为假值。toContain(expected)`: 检查数组或字符串是否包含某个元素/子串。toBeLessThan(expected)`: 数值比较,小于。toBeGreaterThan(expected)`: 数值比较,大于。toBeCloseTo(expected, precision)`: 数值比较,在指定精度下近似相等。toThrow(expected)`: 检查函数是否会抛出异常。
Jasmine 还提供了强大的 spy 功能,用于监控函数的调用情况。由于篇幅限制,此处不再详述,感兴趣的读者可以查阅 Jasmine 官方文档。