web-dev-qa-db-ja.com

MOQフレームワークを使用してC#で静的メソッドをモックする方法は?

私は最近単体テストを行っており、MOQフレームワークとMS Testを使用してさまざまなシナリオをうまくモックしています。プライベートメソッドをテストできないことは知っていますが、MOQを使用して静的メソッドをモックできるかどうかを知りたいです。

48
Himanshu

Moq(および他の DynamicProxy ベースのモックフレームワーク)は、仮想メソッドでも抽象メソッドでもないものをモックできません。

シールド/静的クラス/メソッドは、 Typemock (商用)またはMicrosoft Moles(無料、 Fakes Visual Studio 2012 Ultimate/2013/2015)。

あるいは、静的メソッドへの呼び出しを抽象化するように設計をリファクタリングし、依存性注入を介してこの抽象化をクラスに提供できます。そうすれば、より良いデザインになるだけでなく、Moqなどの無料ツールでテスト可能になります。

テストを可能にする一般的なパターンは、ツールをまったく使用せずに適用できます。次の方法を検討してください。

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = FileUtil.ReadDataFromFile(fileName);
        return data;
    }
}

FileUtil.ReadDataFromFileをモックする代わりに、次のようにprotected virtualメソッドでラップすることができます。

public class MyClass
{
    public string[] GetMyData(string fileName)
    {
        string[] data = GetDataFromFile(fileName);
        return data;
    }

    protected virtual string[] GetDataFromFile(string fileName)
    {
        return FileUtil.ReadDataFromFile(fileName);
    }
}

次に、単体テストで、MyClassから派生し、TestableMyClassと呼びます。その後、GetDataFromFileメソッドをオーバーライドして、独自のテストデータを返すことができます。

お役に立てば幸いです。

89
Igal Tabachnik

静的メソッドを静的FuncまたはActionに変換する別のオプション。例えば。

元のコード:

    class Math
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

Addメソッドを「モック」したいのですが、できません。上記のコードをこれに変更します。

        public static Func<int, int, int> Add = (x, y) =>
        {
            return x + y;
        };

既存のクライアントコードを変更する必要はありません(おそらく再コンパイル)が、ソースは同じままです。

ここで、単体テストからメソッドの動作を変更するには、インライン関数をそれに再割り当てするだけです:

    [TestMethod]
    public static void MyTest()
    {
        Math.Add = (x, y) =>
        {
            return 11;
        };

あなたがしようとしていることに応じて、あなたが望むロジックをメソッドに入れるか、ハードコードされた値を返すだけです。

これは必ずしも毎回できることではないかもしれませんが、実際には、この手法はうまく機能することがわかりました。

[編集]次のクリーンアップコードをユニットテストクラスに追加することをお勧めします。

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Math).TypeInitializer.Invoke(null, null);
    }

静的クラスごとに個別の行を追加します。これは、単体テストの実行が完了した後、すべての静的フィールドを元の値にリセットします。そうすれば、同じプロジェクト内の他の単体テストは、モックバージョンではなく、正しいデフォルトで開始されます。

42
zumalifeguard

他の回答で述べたように、MOQは静的メソッドをモックすることはできません。一般的なルールとして、可能な場合は静的を避ける必要があります。

不可能な場合もあります。 1つは、レガシーコードまたはサードパーティのコード、または静的なBCLメソッドを使用する場合です。

可能な解決策は、モック可能なインターフェイスを使用してプロキシで静的をラップすることです

    public interface IFileProxy {
        void Delete(string path);
    }

    public class FileProxy : IFileProxy {
        public void Delete(string path) {
            System.IO.File.Delete(path);
        }
    }

    public class MyClass {

        private IFileProxy _fileProxy;

        public MyClass(IFileProxy fileProxy) {
            _fileProxy = fileProxy;
        }

        public void DoSomethingAndDeleteFile(string path) {
            // Do Something with file
            // ...
            // Delete
            System.IO.File.Delete(path);
        }

        public void DoSomethingAndDeleteFileUsingProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            _fileProxy.Delete(path);

        }
    }

欠点は、多くのプロキシがある場合、アクターが非常に乱雑になる可能性があることです(多くのプロキシがある場合、クラスはやりすぎてリファクタリングできる可能性があると主張できます)

別の可能性は、背後にあるインターフェイスの異なる実装で「静的プロキシ」を持つことです

   public static class FileServices {

        static FileServices() {
            Reset();
        }

        internal static IFileProxy FileProxy { private get; set; }

        public static void Reset(){
           FileProxy = new FileProxy();
        }

        public static void Delete(string path) {
            FileProxy.Delete(path);
        }

    }

私たちの方法は今

    public void DoSomethingAndDeleteFileUsingStaticProxy(string path) {
            // Do Something with file
            // ...
            // Delete
            FileServices.Delete(path);

    }

テストのために、FileProxyプロパティをモックに設定できます。このスタイルを使用すると、注入するインターフェースの数が減りますが、依存関係が少しわかりにくくなります(ただし、元の静的呼び出しほどではありません)。

7
AlanT

Moqはクラスの静的メンバーをモックできません。

テスト容易性のためにコードを設計するときは、静的メンバー(およびシングルトン)を避けることが重要です。テスト容易性のためにコードをリファクタリングするのに役立つ設計パターンは、依存性注入です。

これはこれを変更することを意味します:

public class Foo
{
    public Foo()
    {
        Bar = new Bar();
    }
}

public Foo(IBar bar)
{
    Bar = bar;
}

これにより、単体テストのモックを使用できます。実稼働環境では、 Ninject または nity などの依存性注入ツールを使用して、すべてを一緒に配線できます。

しばらく前にこれについてブログを書きました。より良いテスト可能なコードに使用するパターンを説明します。多分それはあなたを助けることができます: 単体テスト、地獄か天国?

別の解決策は、 Microsoft Fakes Framework を使用することです。これは、適切に設計されたテスト可能なコードを作成するための代替ではありませんが、役立ちます。 Fakesフレームワークを使用すると、静的メンバーをモックして、実行時に独自のカスタム動作に置き換えることができます。

6
Wouter de Kort