zzzkan.me

C#でスナップショットテストがしたい(Verify)

  • 8min
騎士がソフトウェアテストしているイメージ

この投稿はC#アドベントカレンダー 2023(シリーズ 2)の 25 日目 🎄 の記事です。C#でスナップショットテストを簡単に始められるライブラリVerifyTests/Verifyについて紹介します。

スナップショットテストとは

スナップショットテストとは、ある時点のプログラムの出力をスナップショットとして保存しておき現在の出力と比較することで、予期せぬ変更が起こっていないか検証するテスト手法のことです。

(現在のところ)スナップショットテストと検索するとJest のスナップショットテストが最初のほうにヒットしますが、ここでは以下のように UI コンポーネントに対するテストとしてとても有効な手法として紹介されています。

A typical snapshot test case renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test.

もちろん UI コンポーネントに対して有効なテスト手法なのですが、別に UI コンポーネントだけのテストってわけではなく、 どうしても複雑になってしまうオブジェクトに対するリグレッションテストとして使い勝手がよいと思います。

また、UI 関連のテストとして似たようなテストに Visual Regression Testing があります。これはスナップショットを、シリアライズしたテキストではなく、スクリーンショットなどの画像として保存しておいて比較するテスト手法のことです。Jest の紹介では Snapthot Testing と Visual Regression Testing は区別しているんですが、どちらもある時点のスナップショットを作成しているという点は共通しているので、ここではどちらもスナップショットテストと呼ばせてもらことにします。

このスナップショットテストは非常に強力なテスト手法なのですが、スナップショットの作成、管理、比較などやることがたくさんあり独自で実装するのは骨が折れます。こういう処理はライブラリに任せたいものです。

そこで Verify ですよ

そこで登場するのがスナップショットテストライブラリであるVerifyTests/Verifyです。

この Verify ですが、スナップショットテストに必要な基本的な機能はもれなく抑えているとても便利なライブラリです。xUnit などのテストフレームワークを組み合わせて使うのですが、以下のように C#で使われるたいていのフレームワークに対応しています。

スターも(現在のところ)2.2k ついていて NuGet 関連のレポジトリではかなりついているほうではないでしょうか。また、2023 年 9 月に AWS の FOSS fund にも選ばれています。ちなみにライセンスは MIT です。

実際に使ってみる

それでは実際に使ってみます。今回は Xunit をベースに使ってみます。

オブジェクトをテストする

とりあえず検証用に以下のクラスを用意してみました。

public class Post(string title, string author)
{
    public Guid Id { get; } = Guid.NewGuid();
    public DateTime Date { get; } = DateTime.Now;
    public string Title { get; } = title;
    public string Author { get; } = author;
}

このPostに対して Verify でスナップショットテストを書くと以下のようになります。

[Fact]
public Task PostTest()
{
    var post = new Post("Title A", "Author A");
    return Verify(post);
}

スナップショットを取りたい対象を引数にVerify()を呼び出すだけです。非常にシンプルですね。このテストを実行すると、初回は*.verified.txt*.received.txtの 2 つのファイルが生成されます。*.verified.txtが正解とするスナップショットで、*.received.txtが実際にいま生成されたスナップショットです。

初回は正解のスナップショットがないはずなのでテストは必ず失敗して以下のような画面が出てきます。

Postの初回のスナップショットテストの差分表示

そうなんです。この Verify 親切にもDiffEngineというものを用意してくれていて、スナップショットに差がある場合はその場で差分表示してくれるんです。おかげで簡単に差分を確認しながら現在のスナップショットを採用するか否か判断できます。今回の場合は、*.received.txt(左側)が期待通りの出力なので*.verified.txt(右側)へマージします。

ちなみに、この DiffEngine で使用されるツールはある程度自分で選択できます。上記の画像では Visual Studio Code が使用されていましたが、たとえば Visual Studio で差分を確認したい場合は以下のようにします。

DiffTools.UseOrder(DiffTool.VisualStudio);

ほかにも WinMerge や Vim などいくつかのツールが選択できます。選択可能なツールや優先度など詳細はdiff-tool.order.mdを見てみてください。また、この DiffEngine は CI サーバー上などでは起動したくないと思います。そのような場合は、環境変数DiffEngine_Disabledtrueに設定するか、DiffRunner.Disabled = true;とすることで無効化できます。

さて、次にPostのタイトルを変更してみます。

[Fact]
public Task PostTest()
{
    var post = new Post("Title B", "Author A");
    return Verify(post);
}

この状態でテスト実行すると以下のような差分が得られます。

Postの2回目のスナップショットテストの差分表示

期待通りタイトルに差分があることが確認できます。そしてもう 1 つ、あえてメンバーに含めていたGuidDateTimeの差分がないことも注目です。これは Verify がよしなにサニタイズしてくれているおかげです。こういうのありがたいですね~。

UI をテストする

ここからは画面のスナップショットテストをやってみます。Verify なんですが拡張として、たとえば Verify.BlazorVerify.Xaml、はたまたVerify.WinFormsなどが用意されていて、UI ベースのスナップショットテストも簡単に始められるようになっています。

今回は(あえて?) Windows Forms を試してみようと思います。ここでは以下のようなフォーム(MyForm)を用意して、Button2 の文字色を赤に変更することを考えます。

Windows Formsのサンプル

Verify.WinForms を使用するとテストは以下のように書けます。

[Fact]
public Task FormTest()
{
    VerifyWinForms.Initialize();
    var post = new MyForm();
    return Verify(post);
}

これを実行すると画像のスナップショットが得られ、その差分は以下のようになります。

Windows Formsの差分

簡単に UI ベースのスナップショットテストが実行できました。ただし、画像比較する場合は Comparer(スナップショットの比較方法)に注意が必要そうです。デフォルトでは単なるバイナリ比較なので、差分なしと判定して欲しい状態であっても差分ありと判定されてしまいます。このままでは非常に不安定なテストになってしまうので、テスト対象に合わせて Comparer を変更する必要があります。

Comparer の変更方法はcomparer.mdを参照してください。たとえば、Verify.ImageMagickなど準備された Comparer もいくつか存在しているので自分に合うものを選ぶとよいと思います。ちなみに、Verify.ImageMagick はたとえば以下のようにして比較方法を設定できます。

// しきい値やエラーメトリクスを変更できる
VerifyImageMagick.RegisterComparers(threshold: 0.1, metric: ImageMagick.ErrorMetric.PerceptualHash);

帳票(PDF)をテストする

C#で作られている業務系のシステムでは帳票出すものも結構多いのではないでしょうか。(ここでは詳しい使い方は省略しますが)Verify にはVerify.Asposeなど PDF を扱うための拡張も存在しています。

また、PDF を 1 ページごとの画像に変換してから画像によるスナップショットテストを行うという方法も考えられます。Verify には、このような一度変換処理をかませてからスナップショットテストしたい場合のために Converters という仕組みが用意されています。詳細はconverter.mdを見てみてください。

ちなみに 上述した Verify.ImageMagick は以下で PDF→PNG の Converters を準備してくれているようです。

VerifyImageMagick.RegisterPdfToPngConverter();

(Ghostscript を使ってると思うので)ライセンス等注意が必要だとは思いますがこちらも有力な候補になると思います。作者の方も商用ライセンスなしで PDF をテストする方法としてVerify.ImageMagick を紹介してたりします。

おわりに

今回はスナップショットテストライブラリである Verify の紹介をしました。

スナップショットテストは、 Verify のようなライブラリを使えば簡単にテストを始められるのがとても魅力的だと思っています。ただ、最初のスナップショットの妥当性は人の目で確認する必要があるし、差分が発生した場合も人の目で確認してマージする必要があるので、やりすぎ注意な面はあるなあと思ってます。

zzzkan
zzzkan

アルフォートは水色派です。

© 2023 zzzkan, Built with Gatsby