TL;DR: 为 Angular 测试启用提前 (AOT) 编译,以获得准确的模板代码覆盖率、更快的测试执行速度、生产对称性和面向未来的能力测试。
该选项已可供 Vitest 用户使用,并且很快将可供 Karma 和 Jest(实验构建器)用户使用。
无论您使用 Karma、Jest 还是 Vitest,您都可能会使用即时 (JIT) 编译来进行 Angular 测试,因为 直到最近,它还是唯一可用的选项。
问题是 JIT 有一些显着的缺点:
自从 Angular 8 和 IVy 的引入以来,Angular 编译器已经开始将模板转换为指令。除了许多其他好处之外,这还意味着代码覆盖率工具可以将这些指令映射到模板并相应地计算代码覆盖率。
理论上,从 Angular 8 开始就可以通过使用 AOT 运行测试来生成代码覆盖率,但该选项在 Karma 或 Jest 中不可用。 自从 Analog 团队添加了 Vitest 对 Angular 的支持后,才可以启用 AOT 进行测试。
截至 2024 年 11 月:
无论您使用 JIT 还是 AOT,组件最终都会在某个时刻被编译。主要区别在于,使用 AOT 时,编译只需完成一次并可以缓存,而使用 JIT 时,每个测试模块最终可能会重新编译组件。
这意味着即使 AOT 的转换阶段有点慢,整体测试执行时间也会更快。我看到的数字表明执行速度提高了约 20%,但这在很大程度上取决于测试的结构和被测系统。
我们通常希望我们的测试与生产环境尽可能对称,以增加信心。这通常具有挑战性,因为它需要与其他属性(例如测试速度、被测系统的大小或可预测性)进行平衡。
AOT 的有趣之处在于它在不损害其他属性的情况下提高了生产对称性。 使用 AOT,您将获得更多信心并实现更接近生产的行为。
更重要的是,JIT 已经达到了极限,并且正在成为 Angular 的负担。例如,JIT 根本不支持某些 Angular 功能(例如可延迟视图)。 Angular 路线图中的其他潜在功能,例如无选择器组件,可能无法与 JIT 一起使用。
实际上,自从 Angular 的信号输入(以及类似的函数式 API) 以来,JIT 已经需要一些最小的转换才能工作。
通过切换到 AOT,您可以使您的测试面向未来,准备好从任何创新中受益,并为 JIT 的未来做好准备。
通过打开 AOT,一些依赖动态构造的技术将会被破坏。
例如,这样的用法将不再起作用:
// ? This is broken with AOT. const fixture = render(`<app-button></app-button>`, { imports: [Button] }); function render(template, { imports }) { @Component({ template, imports, }) class TestContainer {} return TestBed.createComponent(TestContainer); }
但是,绕过AOT编译仍然是可能的(⚠️暂时️⚠️):
function render(template, { imports }) { @Component({ jit: true, template, imports, }) class TestContainer {} return TestBed.createComponent(TestContainer); }
我的建议是尽可能避免此类构造,并更喜欢在需要时创建特定于测试的组件,即使它可能有点冗长。将来,Angular 团队可以提供既兼容 AOT 又更少样板的替代方案。
虽然浅层测试不应该是您的主要测试策略,因为它的生产对称性也较差,但它仍然是您工具箱中的一项有用技术。
使用 AOT,目前无法使用 TestBed#overrideComponent 覆盖组件的导入。
解决方法是使用测试框架的 API 在模块级别覆盖组件的依赖项,并用其测试替身替换组件。
例如,使用 Vitest:
// app.cmp.spec.ts vi.mock('./street-map.cmp', async () => { return { StreetMap: await import('./street-map-fake.cmp').then( (m) => m.StreetMapFake ), }; }); // street-map-fake.cmp.ts @Component({ selector: 'app-street-map', template: 'Fake Street Map', }) class StreetMapFake implements StreetMap { // ... }
虽然这个临时解决方法与 AOT 兼容,但它会产生一定的成本:
目前,我建议使用 JIT 进行浅层测试,直到 TestBed#overrideComponent 支持 AOT 或 Angular 团队提供更好的替代方案。您可以通过对浅层测试使用单独的配置来实现此目的,该配置使用 JIT 来进行与 *.jit.spec.ts 等特定模式匹配的测试。
找到 vite.config.js 文件并通过将 Angular 的插件 jit 选项设置为 false 来打开 AOT:
// ? This is broken with AOT. const fixture = render(`<app-button></app-button>`, { imports: [Button] }); function render(template, { imports }) { @Component({ template, imports, }) class TestContainer {} return TestBed.createComponent(TestContainer); }
我们可以选择使用 istanbul 或本机 v8 进行代码覆盖。由于某种原因(仍在调查中),使用 v8 时 Vitest 覆盖范围重新映射失败。解决方案是改用伊斯坦布尔。
确保安装与 Vitest 主要版本
匹配的 Vitest Istanbul 版本
function render(template, { imports }) { @Component({ jit: true, template, imports, }) class TestContainer {} return TestBed.createComponent(TestContainer); }
更新 vite.config.mts 以启用伊斯坦布尔覆盖:
// app.cmp.spec.ts vi.mock('./street-map.cmp', async () => { return { StreetMap: await import('./street-map-fake.cmp').then( (m) => m.StreetMapFake ), }; }); // street-map-fake.cmp.ts @Component({ selector: 'app-street-map', template: 'Fake Street Map', }) class StreetMapFake implements StreetMap { // ... }
您现在可以运行测试:
export default defineConfig({ ... plugins: [ angular({ jit: false }), ... ], ... });
然后单击覆盖率图标并欣赏模板的代码覆盖率。 ?
(您还可以在覆盖率文件夹中找到覆盖率报告)
请注意,覆盖率是根据编译器生成的指令计算的,这意味着:
它甚至适用于结构指令。
现在,你猜怎么着!?
覆盖范围也适用于内联模板! ?
虽然代码覆盖率是一个有用的工具,但应该明智地使用它。将其作为一个指标,而不是一个严格的目标。
任何观察到的统计规律一旦受到控制压力就会崩溃。
-- 查尔斯·古德哈特
换句话说,当一项措施成为目标时,它就不再是一个好的措施。
我想补充一点最简单的指标往往是最具误导性的。
Karma 用户很快将能够通过简单的标志启用 AOT。
Jest 用户有三个选项:
Vitest用户现在就可以享受AOT的好处。 ?
如果您厌倦了错误,或者厌倦了每次重构时都会中断的高维护测试,我的视频课程实用角度测试可以为您提供帮助!
学习实用、可靠的测试策略,以保持您的 Angular 应用程序稳定且可维护。 (现在限时50%折扣!)
以上是Angular 模板代码覆盖率和面向未来的测试所缺少的要素的详细内容。更多信息请关注PHP中文网其他相关文章!