调试测试
学习目标
-
修复测试中的问题
-
找出测试失败的原因
-
应用熟悉的调试技术到测试中
-
使用Jasmine的调试功能
编写测试和编写实现代码一样费力。您经常会遇到困难,并问自己为什么测试失败,有时候也会问为什么测试通过了,明明应该失败的。
好消息是,您也可以将熟悉的调试技术应用于测试中。
测试焦点
有些测试需要一个详尽的 准备(Arrange) 阶段,执行(Act) 阶段调用了多个方法或模拟了复杂的用户输入。这些测试很难调试。
隔离问题 |
在定位错误时,逐渐缩小范围:只执行一个测试、一个测试套件、一个规范、一个期望。
默认情况下,Karma和Jasmine会在每次代码更改时重新编译和运行所有规范。当您在一个特定的规范上工作时,这会导致反馈周期变慢。在代码更改后,可能需要10-20秒才能看到测试结果。而且一个规范可能会干扰另一个规范。
缩小范围的最简单方法是对套件或规范设置一个 焦点。假设您有一个包含两个规范的测试套件:
describe('Example spec', () => {
it('one spec', () => { /* … */ });
it('another spec', () => { /* … */ });
});
fdescribe
|
如果你想让Jasmine仅运行这个测试套件并跳过其他所有测试套件,将 describe
更改为 fdescribe
。
fdescribe('Example spec', () => {
it('one spec', () => { /* … */ });
it('another spec', () => { /* … */ });
});
fit
|
如果你想让 Jasmine 只运行一个 spec(单个测试用例),将 it
更改为 fit
。
describe('Example spec', () => {
fit('one spec', () => { /* … */ });
it('another spec', () => { /* … */ });
});
这极大地改善了开发体验。
尽管只更改了一行代码,并且只有一个测试套件专注于某个测试,但Webpack模块打包工具仍然会重新生成整个捆绑包。
只打包一个文件 |
在这种情况下,你可以指示 ng test
仅考虑你当前正在工作的文件。Webpack会包含所有依赖项,如Angular框架,但不会比这更多。
例如,要仅包含名为 counter.component.spec.ts
的测试,我们使用 --include
选项调用 ng test
。
ng test --include **/counter.component.spec.ts
**/counter.component.spec.ts
表示任何子目录中名为 counter.component.spec.ts
的所有文件。
现在打包速度很快,当我们更改实现或测试代码时,反馈几乎是即时的。
在提交代码之前记得移除测试的专注点。有几种工具可以防止提交包含 fdescribe
和 fit
的代码。
开发者工具
Jasmine 测试运行器只是使用 HTML、CSS 和 JavaScript 创建的另一个网页。这意味着你可以使用开发者工具在浏览器中调试它。
熟悉的调试工具 |
将浏览器窗口聚焦并打开开发者工具。在 Chrome、Firefox 和 Edge 中,你可以使用 F12 键。
你可以使用开发者工具来:
-
使用
console.log
、console.debug
等向控制台输出调试信息。 -
使用 JavaScript 调试器。你可以在开发者工具中设置断点,或者在代码中使用
debugger
语句。 -
检查渲染组件的 DOM。
调试输出和 JavaScript 调试器
最原始的工具 console.log
实际上在调试测试时非常宝贵。你可以在测试代码和实现代码中都放置调试输出。
多用途的 console.log
|
使用调试输出来回答以下问题:
-
测试、套件、用例是否被执行?
-
测试执行是否达到了日志命令?
-
测试是否正确调用了要测试的类、方法、函数?
-
回调函数是否被正确调用?Promises 是否完成或失败?Observables 是否发出、完成或出错?
-
对于组件测试:
-
输入数据是否正确传递?
-
生命周期方法是否被正确调用?
debugger
|
有些人喜欢使用 debugger
而不是控制台输出。

虽然debugger
确实可以给你更多的控制权,但它会暂停 JavaScript 的执行。这可能会干扰异步 JavaScript 任务的处理和执行顺序。
异步日志记录 |
console
方法也有它们自己的缺点。出于性能原因,浏览器不会同步将输出写入控制台,而是异步进行。
如果你使用 console.log(object)
输出一个复杂的对象,大多数浏览器会在控制台上呈现对象的交互式表示形式。你可以点击该对象以检查其属性。
const exampleObject = { name: 'Usagi Tsukino' };
console.log(exampleObject);
重要的是要知道渲染是异步进行的。如果在 console.log 调用之后不久修改了对象,你可能会看到修改后的对象,而不是 console.log
调用时的对象。
const exampleObject = { name: 'Usagi Tsukino' };
console.log(exampleObject);
exampleObject.name = 'Sailor Moon';
在控制台上,对象的表示可能显示为 name: 'Sailor Moon'
而不是 name: 'Usagi Tsukino'
。
避免这种混淆的一种方法是创建对象的快照。你可以将对象转换为 JSON 字符串:
const exampleObject = { name: 'Usagi Tsukino' };
console.log(JSON.stringify(exampleObject, null, ' '));
exampleObject.name = 'Sailor Moon';
输出快照日志 |
如果你想在控制台上得到一个交互式的表示形式,可以通过使用 JSON.stringify
后跟 JSON.parse
来创建对象的副本:
const exampleObject = { name: 'Usagi Tsukino' };
console.log(JSON.parse(JSON.stringify(exampleObject)));
exampleObject.name = 'Sailor Moon';
显然,这仅适用于可以序列化为 JSON 的对象。
检查 DOM
在下一章中,我们将学习如何测试组件。这些测试将会将组件渲染到 Jasmine 测试运行器页面的 DOM 中。这意味着你可以在浏览器中简要地查看渲染组件的状态。

在上面的截图中,你可以在左侧看到渲染的组件,右侧是检查的 DOM。
根元素 |
组件的根元素被渲染到文档中的最后一个元素中,位于 Jasmine 报告器输出的下方。确保将焦点设置在单个测试用例上,以查看渲染的组件。
渲染的组件是可交互的。例如,你可以点击按钮,点击处理程序将被调用。但正如我们之后将学习的那样,在测试环境中没有自动变更检测。所以你可能看不到交互的效果。
Jasmine 调试运行器
位于 http://localhost:9876 的 Karma 页面会加载一个包含实际 Jasmine 实例的 iframe,即 http://localhost:9876/context.html。这个 iframe 会让调试变得复杂,因为开发者工具默认在最顶层的文档上操作。
在开发者工具中,你可以选择 iframe 窗口上下文(下图是 Chrome):

这样你就可以访问全局对象和测试运行的文档的 DOM。
没有 iframe 的调试运行器 |
Karma 的另一个有用功能是调试测试运行器。点击右上角的大“DEBUG”按钮,然后会打开一个新的标签页,地址为 http://localhost:9876/debug.html。

调试测试运行器没有使用 iframe,它直接加载 Jasmine。而且,它会自动在 shell 上记录测试的运行情况。
如果你更改了测试或实现代码,调试运行器不会重新运行测试。你需要手动重新加载页面。