标题几乎说出我要问的内容,但我只是解释一下我的目标。
我最近一直在阅读单元测试,但是我无法将头部包裹在嘲讽周围。尤其是涉及系统/系统资源的对象的模拟。
假设你有一个需要与网络互动的课程。
从我在.NET中的单元测试中读到的,模式基本上是围绕与网络交互的类创建包装器对象。并将该对象的接口(例如通过构造函数)传递给您的逻辑类。然后在测试期间,您可以使用模拟替换该包装类。
现在有时,一个类是基本的,它不会也不应该通过它的构造函数或setter函数接受奇怪的适配器或包装器。
通过让每个类接受它所依赖的类来实现单元测试,它基本上使你的代码变得更加复杂。
所以我的问题就是这个,是否存在某种机制,在对象的实例化过程中返回真实的 - 如果是正常运行,则是模拟运行 - 如果它是测试运行。
答案 0 :(得分:3)
假设您有一个执行大量文件系统操作的类,并且您希望能够测试您的类而不必担心将文件读/写到真实的文件系统。
您创建一个界面,类似于以下内容:
public interface IFileSystem
{
bool FileExists(string filePath);
string[] ReadAllLines(string filePath);
//...more methods as appropriate
}
并在包装类上实现接口:
public class RealFileSystem : IFileSystem
{
public bool FileExists(string filePath)
{
return File.Exists(filePath);
}
public string[] ReadAllLines(string filePath)
{
return File.ReadAllLines(filePath);
}
}
然后,使用您的文件系统操作类,您将使用依赖注入实例化它:
public class ClassThatDoesFileSystemStuff
{
private readonly IFileSystem fileSystem;
public ClassThatDoesFileSystemStuff(IFileSystem fs)
{
this.fileSystem = fs;
}
public ClassThatDoesFileSystemStuff() : this(new RealFileSystem())
{
}
public void DoSomeStuff()
{
string path = @"C:\temp.txt";
if (fileSystem.FileExists(path))
{
fileSystem.ReadAllLines(path);
}
}
}
正如您所看到的,上面的类有两个构造函数:一个用于实现IFileSystem的类实例,另一个用于实现IFileSystem。那个没有调用那个的人,实例化一个新的RealFileSystem
在课程的生命周期中使用。
然后,对于单元测试,您可以将IFileSystem的模拟注入该类。您可以使用像RhinoMocks或Moq这样的模拟框架,或者您可以自己动手:
public class FakeFileSystem : IFileSystem
{
public bool FileExists(string filePath)
{
return true;
}
public string[] ReadAllLines(string filePath)
{
return new[] {"This", "is", "a", "bunch of", "text!"};
}
}
在测试期间,请执行
var objectToTest = new ClassThatDoesFileSystemStuff(new FakeFileSystem());
要意识到的重要一点是你松散地耦合你的课程。您不希望类知道要使用哪种类型的IFileSystem。您希望告诉该类要使用的IFileSystem实现。
答案 1 :(得分:1)
您的初步解释是正确的。你走在正确的轨道上。
通常,如果一个类是那么基本的,那就不需要依赖(并且这可能比你想象的更基本),它没有任何值得测试的行为。它是一个适配器 - 你挂起一个接口并将其作为依赖注入其他东西。
你肯定可以从给定特定状态的类返回虚假实现,但是如果你遇到那么多麻烦,那么有两个实现,一个接口和一个构造函数可能更容易参数。
在任何情况下,我都试图避免在生产代码中引用模拟库(AKA隔离框架)。我只参考测试代码中的那些。如果你想从非测试类中返回一个假,我会编写实现。