在测试期间返回模拟并在正常运行期间实际实现的类实例

时间:2011-12-10 15:57:29

标签: .net tdd mocking

标题几乎说出我要问的内容,但我只是解释一下我的目标。

我最近一直在阅读单元测试,但是我无法将头部包裹在嘲讽周围。尤其是涉及系统/系统资源的对象的模拟。

假设你有一个需要与网络互动的课程。

从我在.NET中的单元测试中读到的,模式基本上是围绕与网络交互的类创建包装器对象。并将该对象的接口(例如通过构造函数)传递给您的逻辑类。然后在测试期间,您可以使用模拟替换该包装类。

现在有时,一个类是基本的,它不会也不应该通过它的构造函数或setter函数接受奇怪的适配器或包装器。

通过让每个类接受它所依赖的类来实现单元测试,它基本上使你的代码变得更加复杂。

所以我的问题就是这个,是否存在某种机制,在对象的实例化过程中返回真实的 - 如果是正常运行,则是模拟运行 - 如果它是测试运行。

2 个答案:

答案 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隔离框架)。我只参考测试代码中的那些。如果你想从非测试类中返回一个假,我会编写实现。