跳到主要内容

测试

为什么要写自动化测试?​

  • 手动验证: 每改一次就在控制台跑一遍、对比预期, 靠不住;
  • 痛点: 修好 f(2) 时容易忘记回头验证 f(1), 改一处坏一处;
  • 自动化: 把测试写成独立于实现的代码, 反复用各种输入调用函数并与预期结果比对;

BDD 的 spec 由哪些块组成?​

  • describe("title", fn): 声明在描述哪块功能, 用来分组 it;
  • it("use case", fn): 用人话描述一个用例, 函数体里做断言;
  • assert.equal(a, b): 断言相等; 断言失败即抛错, 且 it 立即终止;
  • 定位: BDD 的 spec 同时是测试、文档和示例三样东西;
describe("pow", function () {
it("raises to n-th power", function () {
assert.equal(pow(2, 3), 8);
});
});

测试驱动的开发流程是怎样的?​

  • 顺序: 先写 spec (含最基础的用例), 再写实现, 跑框架看报错, 改到全绿;
  • 迭代: 补新用例 → 测试失败 → 改实现 → 再跑; 循环到功能完成, 最终同时拿到实现与测试;
  • 特征: spec 先于实现, 即先写会失败的测试, 再补上让它通过的行为;

有测试的项目有什么不同?​

  • 安全感: 函数被多处使用时, 改动后无法手查所有调用点, 跑一遍测试几秒内就能给出大量检查;
  • 替代方案都很糟: 要么硬改并把 bug 交给用户, 要么因怕出错而不敢动, 代码逐渐腐朽;
  • 架构收益: 为了可测, 每个函数必须有清晰的任务、明确的输入输出, 这本身就是好的架构;

断言失败之后发生什么?​

  • 机制: 断言不成立就抛出错误, 由测试框架捕获并记为失败用例;
  • 关联: 错误对象与捕获方式见 错误处理;

浏览器里如何搭起测试环境?​

  • 三方分工: Mocha 提供 describe/it 与运行入口; Chai 提供 assert.* 断言; Sinon 用于监视函数、伪造内置函数 (后用);
  • 页面五部分: <head> 引入库与样式 → <script> 放待测函数 → test.js 放 spec → <div id="mocha"> 作为结果输出容器 → mocha.run() 启动;
  • 更高层: karma 之类 runner 可以自动批量跑测试;
<div id="mocha"></div>
<script src="mocha.js"></script>
<script src="chai.js"></script>
<script src="pow.js"></script>
<script src="test.js"></script>
<script>
mocha.run();
</script>

如何组织和扩展测试用例?​

  • 一个用例只查一件事: 一个 it 里塞两个独立断言时, 第一个失败会掩盖第二个的结果, 应拆开;
  • 批量生成: 用例形状重复时用 for 循环加工厂函数生成 it, 标题里带上输入与预期值;
  • 分组: 用嵌套 describe 把工厂函数与循环关在一起, 形成子分组, 也不要污染外层作用域;
describe("pow", function () {
describe("raises x to power 3", function () {
function makeTest(x) {
let expected = x * x * x;
it(`${x} in the power 3 is ${expected}`, function () {
assert.equal(pow(x, 3), expected);
});
}
for (let x = 1; x <= 5; x++) makeTest(x);
});
});
  • 常用断言: assert.equal (==)、assert.strictEqual (===)、assert.notEqual、assert.notStrictEqual、assert.isTrue、assert.isFalse、assert.isNaN;
  • 失配处理: 非法输入按 JavaScript 惯例返回 NaN, 于是 assert.isNaN(pow(2, -1)) 能直接表达这个约定;

测试前后的钩子怎么用?​

  • 四个钩子: before/after 在整个分组前后各一次, beforeEach/afterEach 在每个 it 前后各一次;
  • 执行序: before → (beforeEach → it → afterEach) 重复 → after;
  • 用途: 初始化环境、清零计数器等测试间状态复位;