モック(Mock)とスタブ(Stub)の違い

洋書 The Art of Unit Testing: With Examples in C# <Link> 読みました。

この手の本、一昔前はJava版がフツーだったように思いますが、C#で出てくるようになったのがうれしい限りです。第1版読んでなかったのですが、筆者は第1版の頃とテスト対象範囲について考えが変わったそうです、私も共感するところですので本書の抜粋ではなく私の解釈に基づいてご紹介します。筆者の考えと私の考え一致していますが、表現は私独自になっていると思います、筆者の表現を読みたい方は申し訳ありませんが原著をご購入ください。

よい単体テストとは?

  • 再現性がある  (誰が、いつ、どこで、何度実行しても同じ結果が得られる)
  • 実行が速い  (速くないと頻繁にテストする癖が失われるよね)
  • 実行が簡単  (簡単じゃないと 〃 )
  • 1つのテストに1つのアサート (テスト対象がはっきりしている、SRPとも関連)
  • コードがCleanであること   (原因特定やリファクタリングしやすい状態)
  • 自己記述的であること    (ログを目で確認..でなく、Assertのような)

Robert.C.MartinさんがCleanCodeの中で記載しているのも混じったと思います。1つのテストに1つのアサートはこれにこだわる必要はないと思いますが、基本的にこうしたほうがわかりよいのは間違いないですね。

テスト範囲は?

  • 基本的にはpublicなメソッド全て。”Unit”の指すところの違いに応じて変化はしうる。

私は難しいと感じていますが、少なくともベキ論はそうでしょう。でも、「このメソッドの前提条件構築は複雑だ」とか、「テスト結果のAssertは内部状態に対してかけなくちゃならない」とかが理由であれば、設計を見直したほうがよさそうですね。前提条件構築が複雑なのであればクラスがSRPを守っていない可能性がありますし、インターフェースが分離できていない可能性もありそうです。TDDで作ることでTestabilityを確保できるので問題解決できそうです。内部状態に対するAssertがかけられない場合はおそらく、「内部状態にAssertをかけてはならない」か「その状態をpublicなInterface化する」を考えると良さそうです。実装に依存せず、Interfaceに依存するというDIPに反している可能性がありそうですね。とはいえ、確実に体言するのは難しいものです。練習あるのみですね、頑張りましょう!

さてタイトルにあるモックとスタブの違いですが、書籍に違いの記載があります。

  • モック:テスト失敗の原因になり得ます。Assertはモックに対して掛けられます。
  • スタブ:テスト失敗の原因になり得ません。Assertはスタブに対して掛かることはありません。

コードで違いを見ておきましょう。
テストのための土台たち

    public interface ICalc
    {
        double Execute(double a, double b);
    }
    public class ClassUnderTest
    {
        private ICalc _calculator;
        public double Value { get; private set; }
        public ClassUnderTest(ICalc calculator, double keepValue)
        {
            _calculator = calculator;
            Value = keepValue;
        }
        public string GetResult(double a, double b)
        {
            return _calculator.Execute(a, b).ToString();
        }
    }

なんの意味もない子たちです、テストのために生まれてきただけです。テスト対象となるその名もずばりのClassUnderTestはICalcインターフェースをコンストラクタで取るので何かしらこのインターフェースを実装するオブジェクトを渡す必要があります。ここでモックフレームワークとして最もユーザ数が多いらしいMoqを使って、”モック”としてオブジェクトを渡す、”スタブ”としてオブジェクトを渡す例を出したいと思います。

モック(Mock)の例

        [Test]
        public void MockTest()
        {
            // arrange
            Mock<ICalc> moq = new Mock<ICalc>();
            moq.Setup(m => m.Execute(1.0, 2.0)).Returns(3.0);
            ICalc mock = moq.Object;
            var cut = new ClassUnderTest(mock, 10.0);
            // act
            string result = cut.GetResult(1.0, 2.0);
            // assert
            Assert.AreEqual("3", result);
        }

スタブ(Stub)の例

        [Test]
        public void StubTest()
        {
            // arrange
            Mock<ICalc> moq = new Mock<ICalc>();
            moq.Setup(m => m.Execute(1.0, 2.0)).Returns(3.0);
            ICalc stub = moq.Object;
            var cut = new ClassUnderTest(stub, 10.0);
            // act
            cut.GetResult(1.0, 2.0);
            // assert
            Assert.AreEqual(10.0, cut.Value);
        }

重要な違いは、『何に対してAssertをかけているか』です。

前者「モックの例」では、テスト対象(ClassUnderTest)が使っているICalcの出力に対してAssertを掛けています。これはつまりICalcに対するAssertです。この場合ICalcに対してAssertが掛けられているので、ICalcはモックであるということになります。

対して後者「スタブの例」では、テスト対象(ClassUnderTest)にICalcを渡していますがClassUnderTestのICalcが直接関係しないValueに対してAssertを掛けています。これはつまりClassUnderTestに対するAssertです。この場合ICalcに対してAssertが掛けられているわけではないのでICalcはスタブであるということになります。

用語といえば、SUT(System Under Test)、CUT(Class Under Test)というのもよく使うみたいですね。チームで開発する時には意味の取り違い、認識違い(MVCとか!)がよく発生するので注意していきましょう。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中