Moqの使用例

Moqは、匿名メソッドと式ツリーに基づいて構築された、シンプルで軽量な分離フレームワークです。 彼はコード生成を使用してムックを作成するため、「ウェット」インターフェース、仮想メソッド(および保護メソッド)を許可し、「ウェット」非仮想および静的メソッドを許可しません。





市場には、何でも「濡れる」ことができるフレームワークが2つしかありません。 これらはTypeMockIsolatorとMicrosoft Fakesであり、Visual Studio 2012(以前のMicrosoft Moles )で利用できます。 これらのフレームワークは、Moqとは異なり、コード生成を使用しませんが、CLRプロファイリングAPIを使用すると、ほとんどすべてのメソッドに割り込んで、静的メソッド、非仮想メソッド、またはプライベートメソッドでもmoki /スタブを作成できます。



Moqでは、「スタブ」と「モック」の間に分離はありません。より正式には、状態検証と動作検証の間に分離はありません。 また、ほとんどの場合、 スタブとmokaの違いはそれほど重要でなく、同じスタブが両方の役割を実行することもありますが、単純なものから複雑なものまで例を検討するため、最初に状態をチェックする例を見てから、動作をチェックします。 。



状態 検証



例として、次のインターフェイスの単体テストを検討します。



public interface ILoggerDependency { string GetCurrentDirectory(); string GetDirectoryByLoggerName(string loggerName); string DefaultLogger { get; } }
      
      







1. GetCurrentDirectoryメソッドのスコープ:



 // Mock.Of    (-),   -. //   ,    GetCurrentDirectory() //   "D:\\Temp" ILoggerDependency loggerDependency = Mock.Of<ILoggerDependency>(d => d.GetCurrentDirectory() == "D:\\Temp"); var currentDirectory = loggerDependency.GetCurrentDirectory(); Assert.That(currentDirectory, Is.EqualTo("D:\\Temp"));
      
      







2. GetDirectoryByLoggerNameという見出しのメソッドは 、常に同じ結果を返します。



 //     GetDirectoryByLoggerName  "C:\\Foo". ILoggerDependency loggerDependency = Mock.Of<ILoggerDependency>( ld => ld.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Foo"); string directory = loggerDependency.GetDirectoryByLoggerName("anything"); Assert.That(directory, Is.EqualTo("C:\\Foo"));
      
      







3.引数に応じて結果を返すGetDirrectoryByLoggerNameメソッドのスコープ



 //    ,    //  GetDirrectoryByLoggerName    . //    : // public string GetDirectoryByLoggername(string s) { return "C:\\" + s; } Mock<ILoggerDependency> stub = new Mock<ILoggerDependency>(); stub.Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())) .Returns<string>(name => "C:\\" + name); string loggerName = "SomeLogger"; ILoggerDependency logger = stub.Object; string directory = logger.GetDirectoryByLoggerName(loggerName); Assert.That(directory, Is.EqualTo("C:\\" + loggerName));
      
      







4. DefaultLoggerプロパティの見出し



 //  DefaultLogger       ILoggerDependency logger = Mock.Of<ILoggerDependency>( d => d.DefaultLogger == "DefaultLogger"); string defaultLogger = logger.DefaultLogger; Assert.That(defaultLogger, Is.EqualTo("DefaultLogger"));
      
      







5.「moq機能仕様」(Moq v4で表示)を使用して、1つの式で複数のメソッドの動作を設定します。



 //        «» ILoggerDependency logger = Mock.Of<ILoggerDependency>( d => d.GetCurrentDirectory() == "D:\\Temp" && d.DefaultLogger == "DefaultLogger" && d.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Temp"); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp"));
      
      







6. Setupメソッド(「古い」v3構文)を呼び出して、いくつかのメソッドの動作を設定します。



 var stub = new Mock<ILoggerDependency>(); stub.Setup(ld => ld.GetCurrentDirectory()).Returns("D:\\Temp"); stub.Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())).Returns("C:\\Temp"); stub.SetupGet(ld => ld.DefaultLogger).Returns("DefaultLogger"); ILoggerDependency logger = stub.Object; Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp"));
      
      









前述したように、Moqではmokaとスタブは分離されていませんが、2つのスタブ初期化構文を区別する方がはるかに簡単です。 そのため、「moq機能仕様」構文は、状態のテスト(つまり、スタブ)にのみ使用でき、動作の設定には使用できません。 Setupメソッドを使用したスタブの初期化は、第1に、より冗長になり、第2に、それを使用するときに、動作または状態を確認するかどうかが完全に明確ではありません。



行動 検証



次のクラスとインターフェイスを使用して、動作をテストします。



 public interface ILogWriter { string GetLogger(); void SetLogger(string logger); void Write(string message); } public class Logger { private readonly ILogWriter _logWriter; public Logger(ILogWriter logWriter) { _logWriter = logWriter; } public void WriteLine(string message) { _logWriter.Write(message); } }
      
      







1. Loggerクラスのオブジェクト(引数付き)でILogWriter .Writeメソッドの呼び出しを確認します。



 var mock = new Mock<ILogWriter>(); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); // ,    Write      mock.Verify(lw => lw.Write(It.IsAny<string>()));
      
      







2.指定された引数を使用してILogWriter .Writeメソッドの呼び出しを確認します。



 mock.Verify(lw => lw.Write("Hello, logger!"));
      
      







3. ILogWriter .Writeメソッドが1回だけ呼び出されたことの検証(これ以上、以下):



 mock.Verify(lw => lw.Write(It.IsAny<string>()), Times.Once());
      
      









依存関係が発生する回数を確認するためのオプションは多数あります。 Timesクラスには、AtLeast(int)、AtMost(int)、Exactly、Betweenなどのさまざまなメソッドがあります。



4. Verifyメソッドを使用した動作の検証 (いくつかの仮定を確認する必要がある場合に便利です):



 var mock = new Mock<ILogWriter>(); mock.Setup(lw => lw.Write(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); //     Verify   . //  ,      //   mock.Setup mock.Verify();
      
      







5. Verify ()メソッドを使用して複数の呼び出しをテストします。



場合によっては、複数のVerifyメソッドを使用して複数の呼び出しをテストするのは不便です。 代わりに、模擬オブジェクトを作成し、 Setupメソッドを使用して予想される動作を設定し、1つのVerify ()メソッドを呼び出してこれらの前提条件をすべて検証できます 。 このような手法は、テストのSetupメソッドで作成されたモックオブジェクトを再利用するのに便利です。



 var mock = new Mock<ILogWriter>(); mock.Setup(lw => lw.Write(It.IsAny<string>())); mock.Setup(lw => lw.SetLogger(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); mock.Verify();
      
      







トピックからの逸脱。 厳密な モデルと 緩い モデル





Moqは、 厳格と緩いの2つの動作テストモデルをサポートしています。 デフォルトでは、無料のテストモデルが使用されます。つまり、テストされたクラス(テスト対象クラス、CUT)は、アクションの実行中(Actセクション)、依存関係のメソッドを呼び出すことができ、すべてを指定する必要はありません。



したがって、前の例では、 ロガーの .WriteLineメソッドはILogWriterインターフェイスの2つのメソッド、 WriteメソッドとSetLoggerを呼び出します。 MockBehavior .Strictを使用する場合、どの正確な依存関係メソッドを呼び出すかを明示的に指定しないと、 Verifyメソッドは失敗します。



 var mock = new Mock<ILogWriter>(MockBehavior.Strict); //      ,  //  mock.Verify()    mock.Setup(lw => lw.Write(It.IsAny<string>())); mock.Setup(lw => lw.SetLogger(It.IsAny<string>())); var logger = new Logger(mock.Object); logger.WriteLine("Hello, logger!"); mock.Verify();
      
      







MockRepositoryを使用する





MockRepositoryクラスは、スタブを作成するための別の構文を提供します。最も重要なことは、単一のメソッドを呼び出すことで、複数のモックオブジェクトを保存し、より複雑な動作をチェックできるようにすることです。



1. MockRepository .Ofを使用してスタブを作成します。

この構文はMock .Ofの使用に似ていますが、&&演算子ではなく、いくつかのWhereメソッドを使用して、さまざまなメソッドの動作を設定できます。



 var repository = new MockRepository(MockBehavior.Default); ILoggerDependency logger = repository.Of<ILoggerDependency>() .Where(ld => ld.DefaultLogger == "DefaultLogger") .Where(ld => ld.GetCurrentDirectory() == "D:\\Temp") .Where(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>()) == "C:\\Temp") .First(); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("CustomLogger"), Is.EqualTo("C:\\Temp"));
      
      







2. MockRepositoryを使用して、いくつかのモックオブジェクトの動作を設定します。

ILogWriterILogMailerの 2つの依存関係を必要とする、より複雑なSmartLoggerクラスがあるとします 。 テストクラスは、 Writeメソッドを呼び出すときに、2つの依存関係のメソッドを呼び出す必要があります。



 var repo = new MockRepository(MockBehavior.Default); var logWriterMock = repo.Create<ILogWriter>(); logWriterMock.Setup(lw => lw.Write(It.IsAny<string>())); var logMailerMock = repo.Create<ILogMailer>(); logMailerMock.Setup(lm => lm.Send(It.IsAny<MailMessage>())); var smartLogger = new SmartLogger(logWriterMock.Object, logMailerMock.Object); smartLogger.WriteLine("Hello, Logger"); repo.Verify();
      
      







その他のテクニック





インターフェイスによってモックオブジェクト自体を取得すると便利な場合があります( ISomethingインターフェイスを介してMock <ISomething >を取得します)。 たとえば、スタブを初期化するための関数構文はモックオブジェクトを返しませんが、すぐに必要なインターフェイスを返します。 これは、いくつかの単純なメソッドをテストするのに便利ですが、動作を確認する必要がある場合や、パラメーターごとに異なる結果を返すメソッドを設定する場合は不便です。 そのため、メソッドの一部でLINQベースの構文を使用し、他の部分でSetupメソッドを使用すると便利な場合があります



 ILoggerDependency logger = Mock.Of<ILoggerDependency>( ld => ld.GetCurrentDirectory() == "D:\\Temp" && ld.DefaultLogger == "DefaultLogger"); //      GetDirectoryByLoggerName //    ,     Mock.Get(logger) .Setup(ld => ld.GetDirectoryByLoggerName(It.IsAny<string>())) .Returns<string>(loggerName => "C:\\" + loggerName); Assert.That(logger.GetCurrentDirectory(), Is.EqualTo("D:\\Temp")); Assert.That(logger.DefaultLogger, Is.EqualTo("DefaultLogger")); Assert.That(logger.GetDirectoryByLoggerName("Foo"), Is.EqualTo("C:\\Foo")); Assert.That(logger.GetDirectoryByLoggerName("Boo"), Is.EqualTo("C:\\Boo"));
      
      







さらに、Moqを使用すると、保護されたメソッドの動作を確認したり、イベントをテストしたり、その他の機能を使用したりできます。



サイトリンク





Githubの例

モキとスタブ

マイクロソフトのほくろ



All Articles