BEACHSIDE BLOG

Azure と GitHub と C# が好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

xUnit 単体テスト 入門 : データドリブンテスト

前回はAssert の基礎について触れましたが、今回はデータドリブンなテストがテーマです。

データドリブンとかゆーてますが(別に私が付けた名称ではないです)、
主に3つのアトリビュートを使ってのテストです。それぞれ用途が異なると(個人的に)思っているので、そこら辺を整理します。

一応書いておくと、今回これから使う3つの Attribute を使う際は、[Fact] Attribute ではなく [Theory] Attribute を使います。

まずはテストをされる側のコードサンプル。シンプルなメソッドをテストの題材にしてみます。

InlineData

InlineData を使った場合のテストコード。[Theory] Attribute の下に InlineData を付けます。説明よりもコードみると雰囲気つかめますね。

テストメソッドに引数が3つあるので、そのシンタックスに合わせて InlineData のデータを用意します。ここでは1テストデータにつき3つ(テスト対象のメソッドの引数を2つと、その結果の期待値)にしています。
メソッドのシンタックスに合わない場合は、もちろんコンパイルエラーになります。

あと、sut は System update test の略で、テストの一般的な(xUnit の?)命名規約のひとつですね。テスト対象のオブジェクトにはこの名称にすします。

テストエクスプローラーからテストを実行すると、データの分だけテストが実行されているのが確認できます。
(あとは、テストメソッド名をまともに書けば、テストエクスプローラーの結果でなんのテストやってるかみやすくなりますね。)

f:id:beachside:20181121192004p:plain

用途

テストデータの件数が少ない場合はサクッと書けるので良いですね。
データ件数が多い場合は、見た目も悪くなるし、変更するのもめんどいしデータのバグも生みそうなので向いてないと思います。


MemberData

MemberData Attribute を付けると、テストメソッドがちょっとすっきりします。
書き方は何パターンかありそうです。

static なメンバー変数をテストデータにする

テストデータには、static で IEnnumerable<object[]> 型のメンバーを用意します(10行目)。テストメソッドで2行目のように指定すればOKです。

static なメソッドからデータを取得する

まず、テストデータ取得用のメソッドは10行目です。引数付きのメソッドも可能です。

テストメソッドですが、
2行目のようにパラメーターを付けることで、10行目のメソッドに引数を渡して呼び出すことができます。今回はコレクションに対して単純な実装をしましたが、共通のデータでシンプルにフィルターをかけたいとかなら良さそうですね。

テストエクスプローラーで実行結果を確認すると、実装の通り、テストデータの先頭2つのみのテストになります。

f:id:beachside:20181121202356p:plain

class 名も指定する

MemberData Attribute でクラス名を指定して呼び出すこともできますが、それは後述します。

用途

テストデータの数が多い場合は、InlineData よりもデータドリブンなデータを生成できます。 (2つ目にして用途の説明が雑になった...)


ClassData

MemberData のクラス版です。クラス自体に IEnumerable<object[] を implement します。あとは必要な interface を実装して、データを書けばよいです。

テストは、ClassData attribute を装備するだけです。

用途

MemberData 同様、テストケースが多い場合に使えます。

テストデータのコード化

テストケース次第では、「特定の集合は True, それ以外は False 」みたいなことはあります。 それが1責務のテストでも数千ケースになる場合も普通にあります。

PICTとかのツールを使ってオールペア法でやるのは効率的ですが、そういう話はここでは横に置いておきます。

プログラムで全パターンのテストデータを作れるならメンテも楽になるので、やるに越したことはありません。

今回のテストだと、ケースが少ないので威力がわかりにくいですが、まーやってみましょう。

まずは今回のテストデータ用の class を作ります。集合の差をとるには、object だと辛みですから。
多少ごちゃっとしてますが、XY の値が一緒なら同じオブジェクトだよって実装になります。

プロパティは、XY だけで、あとは集合の差をとるために、Equals とか GetHashCode をテキトー(正しく動くようには書いてます)に override してます。

最終的に List<object[]> にしたいので、拡張メソッドを用意しておきました。

あとは、ヘルパークラスを作りました。 今回のサンプルだと、全パターンと、Valid なパターンを用意し、「全パターン(6行目) - Validなパターン(10行目) = Invalid なパターン」を出すことでテストケースをコード化しています。

テストから呼び出すときは、IEnumerable<object[]> 型である必要があるので、16行目と17行目で用意しています。ここでSampleDataModel のコレクションから拡張メソッドを使ってIEnumerable<object[]> 型へ変換です。

あとはテストで呼び出すだけです。前述までのテストは期待値までテストデータに含めていましたが、ここでは期待値ごとにテストを分けるようになるため、期待値はテストデータに含めずテストメソッドに書いています(べた書きしてるのは、あくまでサンプルだからですよ...リアルでハードコーディングするのはクソなのでやめましょう)。

実行した結果は、もちろん想定通りです。テストデータのパターンが多く、かつコードで書けるなら生産性やメンテナンス性が高いです。

f:id:beachside:20181121215936p:plain


その他

CsvData Attribute でCSVファイルから読み込んだり、ExcelData で Excel からテストデータを読み込んだりもできます。
そうすると、テストデータを外部で作って読み込んでワイワイするっていう形式もとれます。

私はファイルうんぬんに全く興味がわかないのでここでは書きません。ググればすぐ出てきますし、便利だとは思うので用途に応じて利用するのは良いと思います。

終わりに

最近、6千以上のパターンのテストをこんなノリでサクッとやって自己満足度が高かったのが、今更になって xUnit シリーズネタを整理しておこうというモチベーション再燃の瞬間でした。本家のドキュメントも薄いですしね。
最近やった実装では ClassData で書きましたが...なので、今回は MemberData で書いてみて...どっちがいいとかの判断要素は特に思いつかず。。。。

ということで、次回に続きます。