Jasmine 基础测试 #
测试的基本结构 #
describe 和 it #
Jasmine 使用 describe 和 it 函数来组织测试,这是 BDD 风格的核心语法:
javascript
describe('测试套件名称', function() {
it('测试用例描述', function() {
expect(实际值).toBe(期望值);
});
});
第一个测试 #
javascript
function sum(a, b) {
return a + b;
}
describe('sum 函数', function() {
it('应该正确计算两个数的和', function() {
expect(sum(1, 2)).toBe(3);
});
it('应该处理负数', function() {
expect(sum(-1, -2)).toBe(-3);
});
it('应该处理零', function() {
expect(sum(0, 0)).toBe(0);
});
});
测试输出结构 #
text
sum 函数
✓ 应该正确计算两个数的和
✓ 应该处理负数
✓ 应该处理零
3 specs, 0 failures
组织测试 #
嵌套 describe #
使用嵌套的 describe 来组织相关测试:
javascript
describe('Calculator', function() {
describe('add', function() {
it('should add positive numbers', function() {
expect(add(1, 2)).toBe(3);
});
it('should add negative numbers', function() {
expect(add(-1, -2)).toBe(-3);
});
});
describe('subtract', function() {
it('should subtract positive numbers', function() {
expect(subtract(5, 3)).toBe(2);
});
it('should subtract negative numbers', function() {
expect(subtract(-5, -3)).toBe(-2);
});
});
describe('multiply', function() {
it('should multiply two numbers', function() {
expect(multiply(2, 3)).toBe(6);
});
});
describe('divide', function() {
it('should divide two numbers', function() {
expect(divide(6, 2)).toBe(3);
});
it('should throw error when dividing by zero', function() {
expect(function() { divide(1, 0); }).toThrow();
});
});
});
测试输出层级 #
text
Calculator
add
✓ should add positive numbers
✓ should add negative numbers
subtract
✓ should subtract positive numbers
✓ should subtract negative numbers
multiply
✓ should multiply two numbers
divide
✓ should divide two numbers
✓ should throw error when dividing by zero
8 specs, 0 failures
测试命名规范 #
好的命名 #
javascript
// ✅ 描述行为
it('should return sum of two numbers', function() {});
it('should throw error when input is invalid', function() {});
it('should return empty array when no items found', function() {});
// ✅ 描述预期结果
it('returns true when user is logged in', function() {});
it('returns false when password is incorrect', function() {});
// ✅ 使用中文描述(团队约定)
it('用户登录成功后应该返回 token', function() {});
it('密码错误时应该抛出异常', function() {});
不好的命名 #
javascript
// ❌ 太模糊
it('test1', function() {});
it('works', function() {});
// ❌ 技术细节而非行为
it('calls function', function() {});
it('returns value', function() {});
// ❌ 太长
it('should return the correct sum when adding two positive numbers together', function() {});
跳过测试 #
xdescribe 和 xit #
使用 xdescribe 和 xit 跳过测试:
javascript
// 跳过整个测试套件
xdescribe('Calculator', function() {
it('should add numbers', function() {
expect(add(1, 2)).toBe(3);
});
});
// 跳过单个测试
describe('Calculator', function() {
it('should add numbers', function() {
expect(add(1, 2)).toBe(3);
});
xit('这个测试被跳过', function() {
expect(true).toBe(false);
});
});
pending 函数 #
使用 pending() 标记测试为待定:
javascript
describe('Calculator', function() {
it('should multiply numbers', function() {
pending('待实现乘法功能');
expect(multiply(2, 3)).toBe(6);
});
it('should divide numbers', function() {
pending();
});
});
条件跳过 #
javascript
describe('Database tests', function() {
if (process.env.RUN_DB_TESTS !== 'true') {
pending('数据库测试已禁用');
return;
}
it('should connect to database', function() {
});
});
仅运行特定测试 #
fdescribe 和 fit #
使用 fdescribe 和 fit 只运行特定测试:
javascript
describe('Calculator', function() {
it('should add numbers', function() {
});
fit('只运行这个测试', function() {
expect(add(1, 2)).toBe(3);
});
it('这个测试会被跳过', function() {
});
});
fdescribe('只运行这个套件', function() {
it('should work', function() {
});
});
测试结构最佳实践 #
AAA 模式 #
Arrange(准备)- Act(执行)- Assert(断言):
javascript
describe('ShoppingCart', function() {
describe('addItem', function() {
it('should add item to cart', function() {
// Arrange - 准备测试数据
const cart = new ShoppingCart();
const item = { id: 1, name: 'Product', price: 100 };
// Act - 执行被测试的操作
cart.addItem(item);
// Assert - 验证结果
expect(cart.items.length).toBe(1);
expect(cart.items[0]).toEqual(item);
expect(cart.total).toBe(100);
});
});
});
单一职责 #
每个测试只验证一个行为:
javascript
// ✅ 好的做法 - 每个测试一个断言点
describe('ShoppingCart', function() {
it('should calculate total correctly', function() {
const cart = new ShoppingCart();
cart.addItem({ price: 100 });
cart.addItem({ price: 200 });
expect(cart.total).toBe(300);
});
it('should apply discount correctly', function() {
const cart = new ShoppingCart();
cart.addItem({ price: 100 });
cart.applyDiscount(0.1);
expect(cart.total).toBe(90);
});
});
// ❌ 不好的做法 - 一个测试验证多个行为
it('cart operations', function() {
const cart = new ShoppingCart();
cart.addItem({ price: 100 });
expect(cart.items.length).toBe(1);
expect(cart.total).toBe(100);
cart.applyDiscount(0.1);
expect(cart.total).toBe(90);
});
测试隔离 #
每个测试应该独立,不依赖其他测试:
javascript
// ✅ 好的做法 - 每个测试独立
describe('User', function() {
let user;
beforeEach(function() {
user = new User('John');
});
it('should have correct name', function() {
expect(user.name).toBe('John');
});
it('should update name', function() {
user.setName('Jane');
expect(user.name).toBe('Jane');
});
});
// ❌ 不好的做法 - 测试之间有依赖
describe('User', function() {
let user = new User('John');
it('should have correct name', function() {
expect(user.name).toBe('John');
});
it('should update name', function() {
user.setName('Jane');
expect(user.name).toBe('Jane');
});
});
测试文件组织 #
文件命名约定 #
text
project/
├── src/
│ ├── utils/
│ │ ├── math.js
│ │ └── string.js
│ └── services/
│ └── user.js
└── spec/
├── utils/
│ ├── math.spec.js
│ └── string.spec.js
├── services/
│ └── user.spec.js
└── support/
└── jasmine.json
测试文件结构 #
javascript
// user.spec.js
// 1. 导入依赖
const User = require('../src/services/user');
// 2. 主要测试套件
describe('User', function() {
// 3. 共享变量
let user;
// 4. 生命周期钩子
beforeEach(function() {
user = new User('John');
});
afterEach(function() {
});
// 5. 分组测试
describe('constructor', function() {
it('should create user with default values', function() {
expect(user.id).toBeDefined();
expect(user.name).toBe('John');
});
});
describe('setName', function() {
it('should update name', function() {
user.setName('Jane');
expect(user.name).toBe('Jane');
});
it('should throw error for empty name', function() {
expect(function() {
user.setName('');
}).toThrowError('Name cannot be empty');
});
});
describe('save', function() {
it('should save to database', function(done) {
user.save(function(result) {
expect(result.success).toBe(true);
done();
});
});
});
});
参数化测试 #
使用循环 #
javascript
describe('add function', function() {
const testCases = [
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 3, expected: 5 },
{ a: -1, b: -2, expected: -3 },
{ a: 0, b: 0, expected: 0 }
];
testCases.forEach(function(testCase) {
it(`add(${testCase.a}, ${testCase.b}) should return ${testCase.expected}`, function() {
expect(add(testCase.a, testCase.b)).toBe(testCase.expected);
});
});
});
表格驱动测试 #
javascript
describe('toUpperCase', function() {
const testCases = [
{ input: 'hello', expected: 'HELLO' },
{ input: 'WORLD', expected: 'WORLD' },
{ input: 'MiXeD', expected: 'MIXED' },
{ input: '', expected: '' }
];
describe('with different inputs', function() {
testCases.forEach(function(testCase) {
it(`"${testCase.input}" should become "${testCase.expected}"`, function() {
expect(toUpperCase(testCase.input)).toBe(testCase.expected);
});
});
});
});
错误处理测试 #
测试抛出错误 #
javascript
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
describe('divide', function() {
it('should throw error when dividing by zero', function() {
expect(function() {
divide(1, 0);
}).toThrowError('Division by zero');
});
it('should throw Error type', function() {
expect(function() {
divide(1, 0);
}).toThrowError(Error);
});
it('should throw error matching pattern', function() {
expect(function() {
divide(1, 0);
}).toThrowError(/division/i);
});
});
测试异步错误 #
javascript
describe('async operations', function() {
it('should throw error for invalid input', async function() {
try {
await fetchUser(-1);
fail('should have thrown an error');
} catch (error) {
expect(error.message).toBe('Invalid user ID');
}
});
});
实用技巧 #
测试描述模板 #
javascript
describe('功能名称', function() {
describe('方法名称', function() {
describe('场景描述', function() {
it('应该 [预期行为]', function() {
});
});
});
});
// 示例
describe('ShoppingCart', function() {
describe('addItem', function() {
describe('当商品库存充足时', function() {
it('应该成功添加商品到购物车', function() {
});
});
describe('当商品库存不足时', function() {
it('应该抛出库存不足错误', function() {
});
});
});
});
使用注释分隔 #
javascript
describe('UserService', function() {
// ============================================
// 创建用户
// ============================================
describe('create', function() {
it('should create user with valid data', function() {});
it('should throw error for invalid email', function() {});
});
// ============================================
// 更新用户
// ============================================
describe('update', function() {
it('should update user name', function() {});
it('should throw error for non-existent user', function() {});
});
// ============================================
// 删除用户
// ============================================
describe('delete', function() {
it('should delete existing user', function() {});
it('should throw error for non-existent user', function() {});
});
});
下一步 #
现在你已经掌握了 Jasmine 基础测试的编写方法,接下来学习 断言匹配器 了解更多断言方式!
最后更新:2026-03-28