测试
为什么要写自动化测试?
- 手动验证: 每改一次就在控制台跑一遍、对比预期, 靠不住;
- 痛点: 修好
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; - 用途: 初始化环境、清零计数器等测试间状态复位;