调试测试

学习目标

  • 修复测试中的问题

  • 找出测试失败的原因

  • 应用熟悉的调试技术到测试中

  • 使用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 的所有文件。

现在打包速度很快,当我们更改实现或测试代码时,反馈几乎是即时的。

在提交代码之前记得移除测试的专注点。有几种工具可以防止提交包含 fdescribefit 的代码。

开发者工具

Jasmine 测试运行器只是使用 HTML、CSS 和 JavaScript 创建的另一个网页。这意味着你可以使用开发者工具在浏览器中调试它。

熟悉的调试工具

将浏览器窗口聚焦并打开开发者工具。在 Chrome、Firefox 和 Edge 中,你可以使用 F12 键。

你可以使用开发者工具来:

  • 使用 console.logconsole.debug 等向控制台输出调试信息。

  • 使用 JavaScript 调试器。你可以在开发者工具中设置断点,或者在代码中使用 debugger 语句。

  • 检查渲染组件的 DOM。

调试输出和 JavaScript 调试器

最原始的工具 console.log 实际上在调试测试时非常宝贵。你可以在测试代码和实现代码中都放置调试输出。

多用途的 console.log

使用调试输出来回答以下问题:

  • 测试、套件、用例是否被执行?

  • 测试执行是否达到了日志命令?

  • 测试是否正确调用了要测试的类、方法、函数?

  • 回调函数是否被正确调用?Promises 是否完成或失败?Observables 是否发出、完成或出错?

  • 对于组件测试:

  • 输入数据是否正确传递?

  • 生命周期方法是否被正确调用?

debugger

有些人喜欢使用 debugger 而不是控制台输出。

jasmine 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 中。这意味着你可以在浏览器中简要地查看渲染组件的状态。

jasmine dom

在上面的截图中,你可以在左侧看到渲染的组件,右侧是检查的 DOM。

根元素

组件的根元素被渲染到文档中的最后一个元素中,位于 Jasmine 报告器输出的下方。确保将焦点设置在单个测试用例上,以查看渲染的组件。

渲染的组件是可交互的。例如,你可以点击按钮,点击处理程序将被调用。但正如我们之后将学习的那样,在测试环境中没有自动变更检测。所以你可能看不到交互的效果。

Jasmine 调试运行器

位于 http://localhost:9876 的 Karma 页面会加载一个包含实际 Jasmine 实例的 iframe,即 http://localhost:9876/context.html。这个 iframe 会让调试变得复杂,因为开发者工具默认在最顶层的文档上操作。

在开发者工具中,你可以选择 iframe 窗口上下文(下图是 Chrome):

karma select context

这样你就可以访问全局对象和测试运行的文档的 DOM。

没有 iframe 的调试运行器

Karma 的另一个有用功能是调试测试运行器。点击右上角的大“DEBUG”按钮,然后会打开一个新的标签页,地址为 http://localhost:9876/debug.html

jasmine debug runner

调试测试运行器没有使用 iframe,它直接加载 Jasmine。而且,它会自动在 shell 上记录测试的运行情况。

如果你更改了测试或实现代码,调试运行器不会重新运行测试。你需要手动重新加载页面。