Jasmine 基础测试 #

测试的基本结构 #

describe 和 it #

Jasmine 使用 describeit 函数来组织测试,这是 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 #

使用 xdescribexit 跳过测试:

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 #

使用 fdescribefit 只运行特定测试:

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