ログに対するスナップショットテスト
一般にテストというと、何らかの処理を行いその結果得られる出力が期待通りであるか検証することを指すと思います。ただ、出力がまともに検証できる状態になっていないことがあります。いろいろなものが密結合していてテストもない(テストが書けない)古のコードとか。
こういう時のセーフ ティーネットととして、出力が得られる過程つまりログに対して、スナップショットテストできたらいいじゃないという話です。ちなみに、こういう類のテストは Characterization Testing とか Golden Master Testing と呼ばれてるみたいです。
(レガシーコード改善ガイドに出てくるらしい…積んであるから読まないと…)
VerifyTests
スナップショットテスト行うためのライブラリとして、ここでは VerifyTests/Verify を使用します。このライブラリ結構よく使われていて、たとえば dotnet/BenchmarkDotNet のテストにも採用されていたりします。後いつの間にか Powered by JetBrains になってる。
以前記事を書いたこともあるので興味があれば以下もどうぞ。
C#で スナップショットテストがしたい(VerifyTests/Verify) - zzzkan.me
日付や時刻の処理がポイント
お目当ての処理をした結果以下のようなログがファイルとして得られたとします。
2025-04-24 22:31:00,000 [1] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 5
2025-04-24 22:31:10,000 [1] WARN MyApp.MyClass - The value 5 is less than or equal to 10.
2025-04-24 22:31:20,100 [1] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 15
2025-04-24 22:32:00,300 [1] INFO MyApp.MyClass - The value 15 is greater than 10.
2025-04-24 22:32:40,700 [1] DEBUG MyApp.MyClass - Calculation result: 20
当然ではあるんですが、ログには日付や時刻が含まれています。これは実行毎に変化するので、ログファイルに対してそのままスナップショットテストを実行すると、常に失敗します。スレッド ID なども含んでいても同様です。
なので、こういった実行の度に変化する要素はスナップショットテストの対象から除外したいです。VerifyTests では、こういった処理を簡単にできるよう Scrubbers というものが用意されてい ます。
たとえば、日付や時刻をスナップショットから除外するには以下のようにします。
var path = GetLogFilePath();
return VerifyFile(path).ScrubInlineDateTimes("yyyy-MM-dd HH:mm:ss,fff");
するとスナップショットは以下のようになります。
DateTime_1 [1] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 5
DateTime_2 [1] WARN MyApp.MyClass - The value 5 is less than or equal to 10.
DateTime_3 [1] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 15
DateTime_4 [1] INFO MyApp.MyClass - The value 15 is greater than 10.
DateTime_5 [1] DEBUG MyApp.MyClass - Calculation result: 20
日付と時刻が DateTime_1
などに置き換わっていますね。さらに、スレッド ID も除外したい場合はたとえば以下のようにします。
var path = Path.Combine(CurrentFile.Directory(), "./test.log");
return VerifyFile(path)
.ScrubInlineDateTimes("yyyy-MM-dd HH:mm:ss,fff")
.ScrubLinesWithReplace(line => Regex.Replace(line, @"\[\d+\]", "[ThreadId]"));
するとスナップショットは以下のようになります。
DateTime_1 [ThreadId] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 5
DateTime_2 [ThreadId] WARN MyApp.MyClass - The value 5 is less than or equal to 10.
DateTime_3 [ThreadId] DEBUG MyApp.MyClass - DoSomething method was called. Argument: 15
DateTime_4 [ThreadId] INFO MyApp.MyClass - The value 15 is greater than 10.
DateTime_5 [ThreadId] DEBUG MyApp.MyClass - Calculation result: 20
これでスナップショットから実行タイミングに依存する要素を排除できました。簡単!このスナップショットを基にすることで、ログファイルの内純粋な操作ログに対してのみスナップショットテストが実行できるようになりました。
VerifyTests めちゃくちゃ便利です。