如果我对项目的质量保证进行单元测试,我是否需要嘲笑?

时间:2011-12-02 14:41:50

标签: mocking

我使用NUNIT,并且主要使用ValueSource进行测试来进行断言。

但我从不尝试嘲笑。

我发现了一个帖子SO - What is Mocking?

它说:

  

举个例子:您可以通过实现一个简单的内存结构来存储记录来存根数据库。然后,测试对象可以读取和写入数据库存根的记录,以允许它执行测试。这可以测试与数据库无关的对象的某些行为,并且仅包含数据库存根以使测试运行。

     

如果您想要验证被测对象是否将某些特定数据写入数据库,则必须模拟数据库。然后,您的测试将包含有关写入数据库模拟内容的断言。

那么,是否意味着模拟框架充当读/写的虚拟数据库以满足数据源的需求?

1 个答案:

答案 0 :(得分:3)

不是真的。模拟框架允许您生成模拟。模拟通常是一个对象,您可以切换为具体实现某些内容以进行测试。模拟将记录对它进行的调用,并且可以将其配置为在调用方法时返回特定值(或抛出异常)。通常只能为接口或抽象类创建模拟,因此为了使您能够获得模拟框架的好处,您的代码库必须遵循一些常见的OO最佳实践,例如:

  

编程为“界面”,而不是“实施”

没有意义?让我们看一个例子。

假设你有一个类来获取一些网页并确定该页面是否包含单词“hello”。它看起来像这样:

public class HelloFinder
{
    public HelloFinder()
    {

    }

    public bool HasWord(string url)
    {
        var client = new WebClient();
        try
        {
            var result = client.DownloadString(url);

            return result.Contains("hello");
        }
        catch (Exception)
        {
            return false;
        }
    }
}

那么如果你的测试机没有连接到互联网,你将如何测试这个功能?你不能,因为client.DownloadString总会抛出异常。对于需要数据库中的特定数据才能工作的其他类型的函数,这将是相同的。它们无法使用模拟进行测试,因为它们不遵循“程序到接口而不是实现”的做法。那我们怎么能改变这个呢?好吧,我们可以使用HasWord方法提供字符串的代码来搜索HelloFinder类的依赖关系,以便我们可以模拟它:

public interface IStringDownloader
{
    string DownloadString(string url);
}

public class StringDownloader: IStringDownloader
{
    public string  DownloadString(string url)
    {
        var client = new WebClient();
        return client.DownloadString(url);
    }
}

public class HelloFinder
{
    private readonly IStringDownloader downloader;
    public HelloFinder(IStringDownloader downloader)
    {
        if (downloader == null) throw new ArgumentNullException("downloader");

        this.downloader = downloader;
    }

    public bool HasWord(string url)
    {
        try
        {
            var result = this.downloader.DownloadString(url);

            return result.Contains("hello");
        }
        catch (Exception)
        {
            return false;
        }
    }
}

此代码与上面的示例完全相同,但它允许我们测试HasWord方法而不必担心互联网连接。通过使用模拟而不是实际的实现(StringDownloader)。

代码可以在您的实时应用程序中使用:

var finder = new HelloFinder(new StringDownloader());
var googleHasHello = finder.HasWord("http://www.google.com");

但是为了测试HasWords方法,我们并不关心字符串是来自google.com还是其他任何地方。我们只需要测试给定 任何 字符串它正确行为,并且当下载程序抛出异常时它也表现正常。这就是模拟发挥作用的地方。我个人使用名为Moq的模拟框架,所以这里的例子也使用它。这是一个使用模拟的测试:

public void TestThatFinderWillReturnTrueWhenHelloIsInString()
{
    var downloaderMock = new Mock<IStringDownloader>();
    downloaderMock.Setup(d => d.DownloadString(It.IsAny<string>())).Returns("A string that has hello in it");

    var finder = new HelloFinder(downloaderMock.Object);

    var result = finder.HasWord("any string will do");

    //Assert that result is true
}

那么这里发生了什么? 首先,我们创建一个新的Mock并要求它模仿IStringDownloader接口。 然后我们调用Setup方法告诉mock每次调用DownloadString方法时返回一个硬编码字符串,我们也告诉它不要关心参数是什么(由{{1实现)参数。 我们现在有一个行为符合我们想要的模拟,因此我们使用它来创建It.IsAny<string>()类并调用HelloFinder方法。要完成测试,我们所要做的就是检查结果是否正确。

此课程的其他有用测试可能是:

CountWords

总结

回到原来的问题。模拟框架是否像虚拟数据库一样?不完全是。但这是你做出任何你想做的事情。你需要嘲笑你的单元测试吗?嗯,它们当然很有帮助,但它们要求您的代码以某种方式编写。使用dependency injection的项目很容易通过模拟进行测试。但由于我不知道你的特定代码库,所以很难说。你说你已经有了一些单元测试。这些必须被重写以使用模拟,并且您的整个代码库也可能必须被重写,以便甚至可以使用模拟进行测试。因此,对您当前的项目来说,模拟是否是个好主意取决于您自己决定。但请确保将它们放在未来的项目中。

我希望这能为这个问题提供一些启示。