gutenbergdocs/docs/contributors/code/e2e/overusing-snapshots.md
2025-10-22 01:40:18 +08:00

5.6 KiB
Raw Permalink Blame History

过度使用快照测试

看看下面的代码。你第一眼能理解这个测试想要做什么吗?

await editor.insertBlock( { name: 'core/quote' } );
await page.keyboard.type( '1' );
await page.keyboard.press( 'Enter' );
await page.keyboard.press( 'Enter' );

expect( await editor.getEditedPostContent() ).toMatchSnapshot();

await page.keyboard.press( 'Backspace' );
await page.keyboard.type( '2' );

expect( await editor.getEditedPostContent() ).toMatchSnapshot();

这段代码改编自Gutenberg项目中的真实代码移除了测试标题和注释并重构为Playwright版本。理想情况下端到端测试应该具备自文档化和最终用户可读性毕竟它们试图模拟最终用户与应用程序的交互方式。然而这段代码中存在几个值得警惕的问题。

快照测试存在的问题

由Jest推广的快照测试在恰当使用时是个很好的工具。但可能因为它功能强大,开发者经常过度使用。已有许多文章讨论过这个问题。在这个具体案例中,快照测试未能清晰体现开发者的意图。如果不查看其他信息,我们无法明确断言的具体内容。这使得代码难以理解,给除原作者外的所有读者增加了认知负担。作为读者,我们不得不反复查看代码才能完全理解它们。增加的代码复杂性阻碍了贡献者根据需求修改测试的意愿。有时甚至会让原作者感到困惑,导致意外提交错误的快照

以下是包含测试标题和注释的同一测试。现在你明白这些断言的实际含义了:

it( '可以在末尾拆分', async () => {
	// ...

	// 期望在引用块外有空段落
	expect( await getEditedPostContent() ).toMatchSnapshot();

	// ...

	// 期望段落被合并到引用块中
	expect( await getEditedPostContent() ).toMatchSnapshot();
} );

开发者的意图现在稍微清晰了些,但仍感觉与测试本身脱节。你可能会想尝试内联快照,这确实解决了需要在文件间跳转的问题,但它们仍然不具备自文档化特性也不够明确。我们可以做得更好。

解决方案

与其在注释中写断言说明,不如尝试直接明确地写出来。借助editor.getBlocks方法,我们可以将其重写为更简单和原子化的断言:

// ...

// 期望在引用块外有空段落
await expect.poll( editor.getBlocks ).toMatchObject( [
	{
		name: 'core/quote',
		innerBlocks: [
			{
				name: 'core/paragraph',
				attributes: { content: '1' },
			},
		],
	},
	{
		name: 'core/paragraph',
		attributes: { content: '' },
	}
] );

// ...

// 期望段落被合并到引用块中
await expect.poll( editor.getBlocks ).toMatchObject( [ {
	name: 'core/quote',
	innerBlocks: [
		{
			name: 'core/paragraph',
			attributes: { content: '1' },
		},
		{
			name: 'core/paragraph',
			attributes: { content: '2' },
		},
	],
} ] );

这些断言更加可读和明确。你可以添加额外的断言或将现有断言拆分成多个,以突出它们的重要性。是否保留注释取决于你,但当代码本身已经足够可读时,通常可以省略注释。

快照测试的变体

由于Playwright缺乏内联快照功能一些迁移的测试使用字符串断言toBe)来模拟类似效果,避免创建大量快照文件:

expect( await editor.getEditedPostContent() ).toBe( `<!-- wp:paragraph -->
<p>段落</p>
<!-- /wp:paragraph -->` );

我们可以将这种模式视为快照测试的变体,在编写时应遵循相同的规则。通常最好使用editor.getBlocks或其他方法重写它们,以进行明确断言:

await expect.poll( editor.getBlocks ).toMatchObject( [ {
	name: 'core/paragraph',
	attributes: { content: '段落' },
} ] );

测试覆盖率怎么办?

将明确断言与快照测试比较,我们确实在这个测试中损失了一些测试覆盖率。当我们需要断言块的完整序列化内容时,快照测试仍然有用。但幸运的是,集成测试中的一些测试已经对每个核心块的完整内容进行了断言。这些测试在Node.js中运行比在Playwright中重复相同测试要快得多。在我的机器上运行273个测试用例仅需约5.7秒。这类测试在单元或集成级别效果很好,我们可以在不损失测试覆盖率的情况下更快地运行它们。

最佳实践

在端到端测试中很少需要快照测试,通常有更好的替代方案可以利用明确断言。在确实没有其他合适替代方案时,我们应遵循使用快照测试的最佳实践。

避免巨大的快照

巨大的快照难以阅读和审查。而且,当所有内容都重要时,实际上没有任何内容是重要的。巨大的快照会阻碍我们关注快照的重要部分。

避免重复的快照

如果你发现在同一个测试中创建了多个相似内容的快照,这可能表明你应该进行更原子化的断言。重新思考你想要测试什么,如果第一个快照只是第二个的参考,那么你真正需要的是快照之间的差异。将第一个结果存储在变量中,然后断言结果之间的差异。

延伸阅读