2019-11-21 bouzuya/beater-snapshot 0.2.0 をつくった / その設計メモ
bouzuya/beater-snapshot 0.2.0 をつくった。 2019-11-19 の 0.1.0 の続き。
beater-snapshot は snapshot testing の機能を提供する npm package 。
0.2.0 では関数のインタフェースを変えて assert
を含んだ形にしてしまった。
以下はなぜ変えたのかのメモ。
まず参考に Jest を見る。 expect(actual).toMatchSnapshot()
。 actual
が snapshot と match するかを確かめるものだと分かる。簡潔だ。
では beater-snapshot の 0.1.0 がどうか。
const expected = await snapshot('name', actual);
assert.deepStrictEqual(actual, expected);
まず snapshot()
とは何なのか。これは actual
を name
に書き込み or 読み込みして、できた snapshot を返す。
なぜ snapshot()
が読み書きを兼ねないといけないのか。書き込みに name
と actual
の両方が要るのは分かる。しかし読み込みは name
だけで良いはずだ。saveSnapshot('name', actual)
と loadSnapshot('name')
ではダメなのか。
await saveSnapshot('name', actual);
const expected = await loadSnapshot('name');
assert.deepStrictEqual(actual, expected);
こう書くと今度は saveSnapshot()
が必ずしも動かないことに違和感がある。
if (update) await saveSnapshot('name', actual);
const expected = await loadSnapshot('name');
assert.deepStrictEqual(actual, expected);
テストケースごとにこの分岐を書くのは嫌だ。
次に actual
が 2 回出てくる。 1 回目は保存するものを指定するため 2 回目は比較するものを指定するため。これは必ず同じものになる。 1 回にしたい。 'name'
も 2 回出てきてしまった。 snapshot
をバラすのは難しそうだ。
それに Jest のような賢い matcher を備える可能性を考えるともとの object に戻して完全一致を狙うのは都合が悪い。そう考えると actual
と同じ型の expected
を返すつくりも良くない。
そこで assert
を取り込んでしまう。
const assertMatchSnapshot = init(...); // assert などを設定
await assertMatchSnapshot('name', actual);
おおむね良さそうだ。
Jest と比較すると 'name'
が気になるけど。これを消すのは一長一短だと思う。フレームワークと一体になっている Jest はテストケースの名前を適用可能だけど beater-snapshot は単体なので難しい。ちょっとした魔術になりそうだ。素直に 'name'
を残して保存されている名前とすれば良さそうだ。
あと await
だ。 assert
は Error
を投げることで assert
の失敗を通知する。 Promise
を返すつくりはそれらの挙動とあっていない。行儀は良くないけど fs の Sync
系の関数を使って同期的にする。
まとめると 0.2.0 の形になる。
import { Snapshot, init } from 'beater-snapshot';
// init の引数は既定値を例示のために挙げているだけで省略可能
const assertMatchSnapshot: (name: string, actual: any) => void = init({
// snapshot を比較し不一致の場合に Error を投げる
assert: (expected: string, actual: string): void =>
assert.deepStrictEqual(JSON.parse(expected), JSON.parse(actual)),
// snapshot の保存先ディレクトリ
directory: path.resolve('__snapshots__'),
// snapshot の作成
stringify: (o: any): string =>
JSON.stringify(o, null, 2),
// save するか否かの判定
update: process.env.UPDATE_SNAPSHOT === 'true'
});
assertMatchSnapshot('name', actual);
おしまい。
書いていて気づいたけど expected と actual の引数の順序がおかしい。 0.3.0 で直す。
『 Android アプリ設計パターン入門』を読んだ。他のアプリを見るのが良いのだろうな。そんな印象。教科書的な雰囲気かと思ったけど実例集的な雰囲気だった。
歯医者に行った (2019-11-18) 。なめらかになった。落ち着いて 2020 を迎えられそうだ。