C# 5 では、Caller Info 属性が導入されました。有用なアプリケーションの 1 つは、明らかにロギングです。実際、与えられた例はまさに次のとおりです。
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
私は現在アプリケーションを開発しており、ロギング ルーチンで呼び出し元情報の使用法を紹介したいと考えています。次の比較的単純なロギング インターフェイスがあると仮定します。
public interface ILogger
{
void Info(String message);
}
通常、Moq を使用して、希望する動作を確認します。
// Arrange
var logger = new Mock<ILogger>();
var sut = new SystemUnderTest(logger.Object);
// Act
sut.DoIt();
// Assert
logger.Verify(log => log.Info("DoIt was called"));
それは大丈夫です。ここで、呼び出し元情報パラメーターを受け入れるようにログ インターフェイスを変更したいと思います。
public interface ILogger
{
void Info(String message, [CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0);
}
TraceMessage
簡潔にするために、実装は上記の例に似ていると想定できます。上記のようにモックを作成して検証することはできません。私が受け取るコンパイルエラーは次のとおりです。
式ツリーには、オプションの引数を使用する呼び出しまたは呼び出しを含めることはできません
これを回避する唯一の方法はIt.IsAny<T>
、Moq でマッチャーを使用することです。
// Arrange
var logger = new Mock<ILogger>();
var sut = new SystemUnderTest(logger.Object);
// Act
sut.DoIt();
// Assert
logger.Verify(log => log.Info("DoIt was called",
It.IsAny<string>(), It.IsAny<String>(), It.IsAny<int>()));
残念ながら、呼び出しサイトが期待どおりに見えることを主張または確認することはできません。
public void DoIt()
{
// do hard work
_logger.Info("DoIt was called");
}
これが私の質問につながります:単体テストで発信者情報属性の動作を確認するにはどうすればよいですか?
It.IsAny<T>
私はハックが特に好きではありません。単体テストを作成し、赤と緑のサイクルを実行すると、誰かが変更を試みるまですべてがうまくいきます。次に、誰かがやって来て、実装を変更して誤ったパラメーターを含めることができますが、テストは引き続きパスします。
解決
Herr Kater の回答に基づいて、次の方法で発信者情報をユーティリティ クラスにラップすることができました。
public CallerInfo GetCallerInformation()
{
var frame = new StackFrame(2, true);
return new CallerInfo
{
FileName = frame.GetFileName(),
MethodName = frame.GetMethod().Name,
LineNumber = frame.GetFileLineNumber()
};
}
次に、この依存関係をコードに挿入し、ロガーの実装がそれを適切に使用していることを確認できました。呼び出し元がログを正しく使用しているかどうかを適切にテストできるようになりました。
// Arrange
var backingLog = new IMock<IBackingLog>();
var callerInfoUtility = new Mock<ICallerInfoUtility>();
var info = new CallerInfo { MethodName = "Test", FileName = "File", LineNumber = 123 };
callerInfoUtility.Setup(utility => utility.GetCallerInformation()).Returns(info);
var logger = new Logger(backingLog.Object, callerInfoUtility.Object);
// Act
logger.Log("test");
// Assert
logger.Verify(log => log.Info("test was called: Line 123 of Test in File"));