Snapshot report for `test/snapshot-workflow/try-skip.js`
2026/9/20 21:48:46 网站建设 项目流程

Snapshot report fortest/snapshot-workflow/try-skip.js

【免费下载链接】avaNode.js test runner that lets you develop with confidence 🚀项目地址: https://gitcode.com/gh_mirrors/ava/ava

The actual snapshot is saved intry-skip.js.snap.

Generated by AVA.

其中 "The actual snapshot is saved in ..." 说明真正的快照数据存放在同名 `.snap` 文件中,`.md` 只是可视化报告。这份报告的核心价值在于正文部分——它记录了一次"快照报告 diff",即测试运行前后报告内容的变化,用于断言特定的行为差异。 ## 核心场景:t.snapshot.skip() 嵌套在 t.try() 中 该快照报告对应的测试用例定义在 [try-skip.js](https://link.gitcode.com/i/cbcc368d4bc314fe0387bdaada79f9fd) 中,包含两个 `test.serial` 用例,分别针对 `discard` 与 `commit` 两种路径: ```js import test from '@ava/test'; import {cwd} from '../helpers/exec.js'; import {beforeAndAfter} from './helpers/macros.js'; test.serial( 't.snapshot.skip() in discarded t.try() doesn\'t copy over old value', beforeAndAfter, { cwd: cwd('discard-skip'), cli: ['--update-snapshots'], expectChanged: true, }, ); test.serial( 't.snapshot.skip() in committed t.try() does copy over old value', beforeAndAfter, { cwd: cwd('commit-skip'), cli: ['--update-snapshots'], expectChanged: false, }, );

两个用例使用了同一个测试宏beforeAndAfter,但指向不同的 fixture 目录,并且对结果有着截然相反的预期:

用例fixture 目录预期结果
t.snapshot.skip()位于被丢弃的t.try()discard-skip复制旧值,.snap.md均发生变化(expectChanged: true
t.snapshot.skip()位于被提交的t.try()commit-skip复制旧值,.snap.md均保持不变(expectChanged: false

cwdcli参数表示:测试会在临时目录中复制对应 fixture,并以--update-snapshots标志运行 AVA。

用 fixture 复现行为差异

场景一:被丢弃的 t.try() 中的 skip

discard-skipfixture 的测试代码见 discard-skip/test.js:

const {default: test} = await import(process.env.TEST_AVA_IMPORT_FROM); // This fixture is copied to a temporary directory, so import AVA through its configured path. test('discard a skipped snapshot', async t => { t.snapshot(1); const firstTry = await t.try(t => { if (process.env.TEMPLATE) { t.snapshot('before (first try)'); } else { t.snapshot.skip('after (first try)'); } }); firstTry.discard(); const secondTry = await t.try(t => { t.snapshot(process.env.TEMPLATE ? 'before (second try)' : 'after (second try)'); }); secondTry.commit(); });
  • TEMPLATE=true时,这是"模板运行"(生成基准快照):两次t.try()分别记录'before (first try)''before (second try)'
  • TEMPLATE未设置时,这是"实际运行":第一次尝试改为调用t.snapshot.skip('after (first try)'),且该尝试被firstTry.discard()丢弃;第二次尝试记录'after (second try)'commit()

模板运行产出的报告见 discard-skip/test.js.md:

## discard a skipped snapshot > Snapshot 1 1 > Snapshot 2 'before (second try)'

而实际运行后的报告与模板相比,Snapshot 2的取值发生了变化。这份变化正是快照报告 try-skip.js.md 中记录的 diff:

> Snapshot 2 - 'before (second try)' + 'after (second try)'

也就是说:第一次t.try()中的t.snapshot.skip()没有把任何值写入快照(它连旧值'before (first try)'都没有保留),第二次尝试提交的新值'after (second try)'直接覆盖了旧的'before (second try)'。这正是测试名所表达的语义——"discardedt.try()中的t.snapshot.skip()不会复制旧值"。

场景二:被提交的 t.try() 中的 skip

对比commit-skipfixture,见 commit-skip/test.js:

const {default: test} = await import(process.env.TEST_AVA_IMPORT_FROM); test('commit a skipped snapshot', async t => { t.snapshot(1); const firstTry = await t.try(t => { if (process.env.TEMPLATE) { t.snapshot('before'); } else { t.snapshot.skip('after'); } }); firstTry.commit(); });

模板运行记录'before',实际运行改为t.snapshot.skip('after')commit。此时行为反转:skip 会从旧快照中复制旧值,报告中仍然保留'before'(见 commit-skip/test.js.md),.snap.md均不发生任何变化。因此该用例的预期是expectChanged: false

结论可以概括为两条对称的规则:

  • discard(丢弃):尝试内的一切快照写入(包括 skip 的"保留旧值"行为)都被回滚,不产生任何副作用;
  • commit(提交):尝试内的快照写入被保留,t.snapshot.skip()会按正常 skip 语义保留旧快照值。

源码级原理:skipSnapshot 的延迟记录机制

上述行为并非魔法,其底层实现在 lib/snapshot-manager.js 的SnapshotManager类中。关键方法skipSnapshot接受一个deferRecording标志:

skipSnapshot({belongsTo, index, deferRecording}) { const oldBlock = this.oldBlocksByTitle.get(belongsTo); const snapshot = oldBlock?.snapshots[index] ?? {}; // Retain the label from the old snapshot, so as not to assume that the // snapshot.skip() arguments are well-formed. // Defer recording if called in a try(). if (deferRecording) { return () => { // Must be called in order! this.recordSerialized({belongsTo, index, ...snapshot}); }; } this.recordSerialized({belongsTo, index, ...snapshot}); }

要点拆解:

  1. skip 的本质是"复制旧值"skipSnapshot从旧快照块oldBlocksByTitle.get(belongsTo)中取出对应index的旧快照,并将其原样重新记录(recordSerialized)。注意注释中说明:会保留旧快照的 label,而不是假设snapshot.skip()的参数是合法、良构的——因为 skip 的参数本身并不重要,重要的是旧值。
  2. t.try()中调用时走"延迟记录"分支deferRecording: true时,skipSnapshot不立即写入,而是返回一个闭包函数,该函数被调用时才真正执行recordSerialized
  3. discard 意味着闭包永不执行:延迟记录函数只有在尝试被提交时才会被按序调用;一旦尝试被discard(),整个尝试产生的所有快照操作(包括 skip 的复制旧值动作)都会被整体放弃,因此旧值不会被复制进新的快照集——这就是discard-skip场景中'after (second try)'得以覆盖'before (second try)'的原因。
  4. commit 意味着闭包按序执行:尝试被提交后,延迟记录函数被依次调用,skip 的正常语义得以生效——旧快照值被保留,报告与数据文件均保持不变。

与之配套的还有deferRecord方法(record的延迟版本)与skipBlock(title)方法。skipBlock用于跳过整个测试块时保留旧块(this.newBlocksByTitle.set(title, block)),而skipSnapshot负责块内单个快照的跳过。save()方法则在hasChanges为真时,将新快照集分别编码写入.snap文件并生成.md报告(lib/snapshot-manager.js)。

测试宏如何验证"变了"与"没变"

beforeAndAfter宏(定义于 helpers/macros.js)完整地演示了这套验证流程:

export async function beforeAndAfter(t, { cwd, expectChanged, env = {}, cli = [], }) { const updating = process.argv.includes('--update-fixture-snapshots'); if (updating) { // Run template await fixture(['--update-snapshots'], { cwd, env: { TEMPLATE: 'true', AVA_FORCE_CI: 'not-ci', }, }); } const before = await readSnapshots(cwd); // Copy fixture to a temporary directory await withTemporaryFixture(cwd, async cwd => { // Run fixture await fixture(cli, {cwd, env: {AVA_FORCE_CI: 'not-ci', ...env}}); const after = await readSnapshots(cwd); if (expectChanged) { t.not(after.report, before.report, 'expected .md to be changed'); t.notDeepEqual(after.snapshot, before.snapshot, 'expected .snap to be changed'); t.snapshot(cleanStringDiff(before.report, after.report), 'snapshot report diff'); } else { t.is(after.report, before.report, 'expected .md to be unchanged'); t.deepEqual(after.snapshot, before.snapshot, 'expected .snap to be unchanged'); } }); }

【免费下载链接】avaNode.js test runner that lets you develop with confidence 🚀项目地址: https://gitcode.com/gh_mirrors/ava/ava

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询