Mockを使った呼び出し回数テストについて

ずいぶんブログを閉鎖していましたが、不定期にプログラミングの事などをまた書きたいと思います。

今回は C# の UT(ユニットテスト)で、Mock を使った呼び出し回数テストに関して、書きたいと思います。まずテストするコードからです。

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Sample aSample = new Sample();
            aSample.DoSomething();
        }
    }
}

簡単なコンソールアプリです。Main にコードをたくさん書く人がいますが、クラスメソッドだらけになるので激しく推奨しません。
行いたい処理は別クラスに書き、はやく Main から離れましょう。

namespace ConsoleApplication1
{
    public class Sample
    {
        OutputLog _log;

        public Sample()
        {
            _log = new OutputLog();
        }

        public bool DoSomething()
        {
            _log.Error("debug error");
            return true;
        }
    }
}

Sample クラスは、実際に処理を行うクラスです。今回は DoSomethig メソッド内で OutputLog.Error() の呼び出し回数をテストしたいと思います。

using System;

namespace ConsoleApplication1
{
    public class OutputLog
    {
        public void Error(string msg)
        {
            Console.Error.WriteLine(msg);
        }
    }
}

OutputLog クラスです。今回はコンソールアプリなので、ログはコンソールにエラー出力するだけの簡単なコードです。
以上で準備は完了です。

テストプロジェクトを作って、Mock をダウンロードしてテストプロジェクトの参照に設定します。詳細は省きます。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ConsoleApplication1.Tests
{
    [TestClass()]
    public class SampleTests
    {
        [TestMethod()]
        public void DoSomethingTest()
        {
            Mock<OutputLog> mock = new Mock<OutputLog>();
            mock.Setup(m => m.Error("debug error"));

            Sample aSample = new Sample();
            aSample.DoSomething();

            mock.Verify(m => m.Error("debug error"), Times.Once());
        }
    }
}

OutputLog クラスの Error メソッドが、引数 "debug error" で一度だけ呼び出されているかテストするコードはこのような風に書けます。
1点目の注意として、呼び出し回数をテストしたいメソッドは virtual にする必要(virtual にするのが簡単です)があります。

using System;

namespace ConsoleApplication1
{
    public class OutputLog
    {
        public virtual void Error(string msg)   // <--- virtual 追加
        {
            Console.Error.WriteLine(msg);
        }
    }
}

2点目の注意として、mock に対して Error メソッドを呼ばないと実行回数は検査できません。
mock.Object を Sample のコンストラクタに渡し、OutputLog を差し替えます。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ConsoleApplication1.Tests
{
    [TestClass()]
    public class SampleTests
    {
        [TestMethod()]
        public void DoSomethingTest()
        {
            Mock<OutputLog> mock = new Mock<OutputLog>();
            mock.Setup(m => m.Error("debug error"));

            Sample aSample = new Sample(mock.Object);  // <--- mock.Object を渡す
            aSample.DoSomething();

            mock.Verify(m => m.Error("debug error"), Times.Once());
        }
    }
}

ですので Sample クラスは OutputLog を引数に受けるコンストラクタが必要になり、
引数で受け取った OutputLog を _log に設定します。

namespace ConsoleApplication1
{
    public class Sample
    {
        OutputLog _log;

        public Sample()
        {
            _log = new OutputLog();
        }

        public Sample(OutputLog log)    // <--- 追加
        {
            _log = log; 
        }

        public bool DoSomething()
        {
            _log.Error("debug error");
            return true;
        }
    }
}

DoSomethingTest() を実行してみるとテストが成功します。
OutputLog.Error(string) が引数 "debug error" で一度だけ呼び出されている事が確認できました。

なお、実際の業務で UT を書く場合は、いろんな複雑な事情があって、こんなに簡単には行かない事をお断りしておきますw


今回のポイント

・Setup するメソッドは virtual にする。
・Verify するには Mock 用に生成したインスタンスで差し替えて、テストメソッドを実行する必要がある。

以上です。

しかしブログで「わかりやすい説明になるように文章を書く」というのは、
「きれいでわかりやすいコードを書く」技術にも繋がると思うので良いですね。
言語が違うだけで本質は同じだと思います。

ではまた。