我有各种方法,我想使用Visual Studio内置的C#单元测试功能进行单元测试。事情进展顺利,但我遇到了一个场景,我希望“删除”对特定函数调用的依赖。
即,我有一些采用以下格式的方法:
public class ClassWithMethodsIWantToUnitTest
{
public void myFcn(object someArg)
{
...
LoggerService.Instance.LogMessage("myMessage");
...
}
}
基本上,我希望我的单元测试能够简单地验证是否发生了对“LogMessage”的调用。我不想实际检查日志文件或任何东西。我想要一种方法来查看LoggerService行是否已被命中并执行。
LoggerService是一个单例,如果可能的话,我不想仅为单元测试修改代码。
基于这个问题的格式,在我看来应该可以以某种方式控制我的单元测试中的代码。换句话说,有没有办法让我制作一个虚假版本的LogMessage,以便我可以将它用于我的单元测试?我不希望在可能的情况下调用“真正的”LogMessage函数。我只想测试代码是否触及调用函数的路径。
这有什么意义吗?这可能吗?
答案 0 :(得分:6)
这当然有道理并且不是一个未知的问题。
不幸的是,您可能需要更改代码,以便它接受依赖注入。也就是说,在测试时,你应该能够注入一个特制的测试对象(一个模拟)而不是真实的东西。在您的情况下,它可能意味着能够将LoggerService.Instance
设置为特殊的模拟对象。
您需要的第二件事是您将要测试的假记录器实例。您可能需要一个模拟,这是一个特殊的对象,可以设置为检查调用者的行为。有几个模拟框架,我真的建议你使用一个,而不是试图自己推动。
答案 1 :(得分:4)
Anders的回答绝对是解决问题的“规范”方式,但在.NET 4.5中,还有第二种选择:
假货框架。
它允许您向单元测试项目添加“fakes”程序集,该程序集接受代理来代替实际的方法实现。以下是使用File.ReadAllText
[TestMethod]
public void Foo()
{
using (ShimsContext.Create())
{
ShimFile.ReadAllTextString = path => "test 123";
var reverser = new TextReverser();
const string expected = "321 tset";
//Act
var actual = reverser.ReverseSomeTextFromAFile(@"C:\fakefile.txt");
//Assert
Assert.AreEqual(expected, actual);
}
}
该测试方法正在做的是暂时(在ShimsContext
的范围内)用我提供的lambda替换File.ReadAllText
的实现。在这种情况下,只要调用ReadAllText
,它就会返回字符串“test 123”。
它比常规DI慢,但如果你完全依赖于单身的特定实现,它可能正是你所需要的。阅读更多相关信息here。
答案 2 :(得分:3)
安德斯说的话。
一些流行的模拟框架是Moq和RhinoMocks
您将更改代码,以便将记录器依赖项注入您的类:
public class ClassWithMethodsIWantToUnitTest
{
public ClassWithMethodsIWantToUnitTest(ILoggerService logger)
{}
public void myFcn(object someArg)
{
...
logger.LogMessage("myMessage");
...
}
}
或类似的东西。 DI框架可以在需要时自动将记录器注入到类中。一个DI框架自动调用
new ClassWithMethodsIWantToUnitTest(LoggerService.Instance);