C#の単体テストは「Chaining Assertion」が便利


はじめに

C#の単体テストで期待値の確認は「Chaining Assertion」というライブラリがとても便利でお勧めです。
Chaining Assertion の公式サイトはこちら

他のテスト記述ライブラリとの比較

値が等しいことの確認

テスト記述ライブラリで有名なものとして「Fluent Assertions」があります。
Fluent Assertions の公式サイトはこちら
それを用いてテストする場合、以下のように書きます。
(標準のAssertクラスよりも書きやすいので、こちらのライブラリもとても素晴らしいと思います)

// Fluent Assertions で文字列が空文字であることを確認する例
theString.Should().Be(string.Empty);

これに対して、Chaining Assertion は、Should().Be() でなく Is() だけで確認できるため、よりシンプルに記述できます。

// Chaining Assertion で文字列が空文字であることを確認する例
theString.Is(string.Empty);

値が等しい以外(値が範囲内かなど)の確認

Fluent Assertions は期待値を確認するためのメソッド(BeNull、HaveLengthなど)をたくさん提供しています。これは一見すごく多機能で便利なのですが、実際にテストコードを書くときには適切なメソッドを調べることになるので意外と手間がかかります。
例えば、値が0以上10以下であることを確認しようと思った時に、範囲指定のメソッドがあるので、以下のように記述します。

// Fluent Assertions で対象の値が0以上10以下であることを確認する例
theInt.Should().BeInRange(0, 10);

Fluent Assertions の数値型の確認メソッドはこちら

それに対して Chaining Assertion は、シンプルに Is メソッドのみで期待値を確認します。Is の引数にラムダ式を書くことで、Is だけでどんな期待値も確認できます。以下のような感じです。

// Chaining Assertion で対象の値が0以上10以下であることを確認する例
theInt.Is(value => value >= 0 && value <= 10);

C#開発者は、ラムダ式を書き慣れているので、確認したい内容に合わせてメソッドを探すよりも、全部ラムダ式で書く方が楽だと思います。
(これに関しては個人の感想ですが、私のチームで両方のライブラリをそれぞれ一定期間使ってみたところ、いずれのメンバーも Chaining Assertion の書き方の方が気に入りました)

コレクションの確認

コレクションに関しては、Fluent Assertions はコレクション用のメソッドもいっぱいあります。適切なメソッドを探すのが少し手間です。Chaining Assertion 作者の河合さんが以下の記事で解説されているように、コレクションは慣れ親しんだ Linq to Objects で結果まで絞って True/Falseを判定する方が楽だと思います。
neue cc - メソッドチェーン形式のテスト記述ライブラリ

// Fluent Assertions でコレクションの要素に3より大きい値があることを確認する例
// (何十個もあるコレクションのメソッドの中から適したメソッドを使う)
collection.Should().HaveCountGreaterThan(3);

// Chaining Assertion でコレクションの要素に3より大きい値があることを確認する例
// (慣れ親しんだ Linq to Objects を使う)
collection.Any(c => c > 3).Is(true);

その他の便利機能

Chaining Assertion の便利機能を紹介します。

プライベートメンバーへのアクセスが容易

期待値を確認するときに、privateメンバーにアクセスしたい場合があります。
その場合、PrivateObjectクラスを利用してアクセスします。ただ、PrivateObjectの実装は、PrivateObjectのインスタンスを作ってからそれを通してアクセスする必要があり、少し面倒です。
PrivateObjectクラスの実装例

Chaining Assertion なら、privateメンバーへのアクセスを簡潔に実装できます。
具体的には、object型の拡張メソッドであるAsDynamic()メソッドを利用することでprivateメンバーにアクセスできます。
例を以下に示します。

    // privateメンバーを持つクラス
    public class MyClass
    {
        private string PrivateMethod(int number)
        {
            return number;
        }
    }

    [TestClass]
    public class UnitTest
    {
        [TestMethod]
        public void TestMethod()
        {
            var myClass = new MyClass();
            // AsDynamic()の戻り値は dynamic型
            // AsDynamic()に続けて呼び出したいメンバーを書くことでリフレクションで実行される
            int number = myClass.AsDynamic().PrivateMethod(3);
            number.Is(3);
        }
    }

再帰的な公開フィールド・プロパティの確認

あるインスタンスを複製するメソッドを作った場合、その複製されたインスタンスのフィールド値および、そのインスタンスが保持しているオブジェクトが複製元と一致するか確認したい場合があります。
Chaining Assertion なら IsStructualEqualメソッドを呼び出すだけで、複製したオブジェクトのフィールド値およびそれが保持するオブジェクトについて、参照をたどって再帰的に確認できます(publicなフィールドまたはプロパティを確認します)。
以下に例を示します。

    // テスト対象クラス
    public class MyClass
    {
        public string Name
        {
            get;
            set;
        }

        public MyClass Child
        {
            get;
            set;
        }

        public MyClass Clone()
        {
            // 複製する処理
        }
    }

    [TestClass]
    public class UnitTest
    {
        [TestMethod]
        public void TestMethod()
        {
            // テスト対象クラスを作成する
            var myClass = new MyClass()
            {
                Name = "Test",
                Child = new MyClass(),
            };
            // 複製する
            var clone = myClass.Clone();

            // 参照しているChildのフィールド値も含めて一致することを確認
            clone.IsStructuralEqual(myClass);
        }
    }

ただし、相互参照している場合は無限ループとなるため注意が必要です。
そのあたりの挙動を変更することも可能です。Chaining Assertion はインストールするとDLLを参照するのでなく、ソースコードを1ファイル追加するだけなので、そのソースコードを変更することで、自前で挙動をカスタマイズすることもできます。

まとめ

Chaining Assertion は便利なのでお勧めです。

ちなみにこの記事の投稿時点で、更新が止まっているライブラリですが、素晴らしいライブラリだと思うので投稿しました。

Twitterでも開発に役立つ情報を発信しています → @kojimadev