在单元测试中模拟是否需要接口?

时间:2017-02-13 11:28:12

标签: c# unit-testing

我已经读过你需要定义接口以模拟类型,但我不知道如何解释它 例如,要模拟FileSystem,我可以将对象传递给方法,然后在从我的测试调用时模拟它,而不是直接调用某些I / O方法。 为什么单元测试示例(如下面的SO问题中的那个),使用接口?

void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
   string path = zipper.Unzip(theZipFile);
   IFakeFile file = fileSystem.Open(path);
   runner.Run(file);
}

我不能简单地使用各种类型的参数,然后注入对象吗?

E.g。在GUICE指南中,他们建议删除构造函数(例如,没有使用“new”关键字进行字段化),但是使用参数istead。

2 个答案:

答案 0 :(得分:3)

这取决于你的模拟框架。使用Moq,您可以模拟具体类型,只要它们没有被密封,并且您想要模拟的成员是虚拟的。接口通常是首选,因为它们的成员不需要(也不能)标记为虚拟。 (还有其他理由喜欢接口,例如保持类之间的耦合松散)

https://github.com/Moq/moq4/wiki/Quickstart

更新

通常,您将模拟一个类型的实例。因此,您无法测试像FileStream.Open这样的静态方法调用,因为没有要模拟的实例。

<强>更新

像Moq这样的框架依靠继承来生成模拟。这就是为什么方法需要是虚拟的。如果要模拟静态或私有方法,请尝试使用Microsoft Fakes。

https://msdn.microsoft.com/en-us/library/hh549175.aspx

答案 1 :(得分:3)

不必将接口作为依赖关系,但这是推荐并广泛遵循的做法。

使用接口和基类作为依赖项可以帮助您创建失败的情侣设计。 松散耦合的设计使得创建模块/应用层成为可能,而不依赖于其他层的实现细节。 这也使得单独测试这段代码非常容易,而不必担心其他层如何工作或反应。 这种类型的测试称为单元测试。

如果您依赖于具体类,则无法单独测试代码。每当执行依赖项更改时,此类测试都很容易失败。如果您的代码依赖于连接到实际数据库的实际文件系统类或数据访问类,那么当文件无法访问或数据库服务器关闭或数据被破坏时,单元测试可能会失败。

这甚至不会被称为单元测试。它被称为集成测试。

正如我所提到的,在单独测试代码时,您不必担心依赖性如何工作。这就是为什么,虽然单元测试依赖项是嘲笑的。依赖项也不能是接口。它也可以是基类。当你模拟依赖时,模拟没有任何功能的实际实现。有多少种方法可以创建模拟,完全是一个不同的主题。

使用模拟,在单元测试中,您只需确保在测试当前类的代码时它们的行为符合您的期望。所以你要求mock以你想要的方式运行,确保调用mock的某些方法或属性,这样你就可以覆盖你正在测试的代码段的所有路径。

以下是具有依赖性的单元测试的简单示例。

public class ProductService
{
    private IProductRepository repository;
    public ProductService (IProductRepository repository)
    {
        this.repository = repository;
    }

    public Product GetProductById(int productId)
    {
        if(productId <= 0)
            return null;

        var product = this.repository.GetProductById(productId);

        return product;
    }
}

在创建ProductService类时,即使我没有IProductRepository接口的实际实现,我仍然可以按照以下方式对ProductService类进行单元测试。

public class ProductServiceTests
{
    ProductService serviceToTest;
    IProductRepository productRepository;

    public ProductServiceTests()
    {
        this.productRepository = Mock<IProductRepository>();
        this.serviceToTest = new ProductService(productRepository);
    }

    public void GetProductByIdReturnsNullIfProductIdIsZero()
    {
        int productid = 0;

        var product = serviceToTest.GetProductById(productid);
        Assert.That(product, Is.Null);
    }

    public void GetProductByIdReturnsNullIfProductIdIsNegative()
    {
        int productid = -1;
        var product = serviceToTest.GetProductById(productid);
        Assert.That(product, Is.Null);
    }

    public void GetProductByIdReturnsProductIfProductIdIsPositive()
    {
        var productid = 1;

        var product = new Product { Id = productId };

        productRepository.Setup(repo => repo.GetProductById(productid)).Returns(product); //Making sure that GetProductById method of repository is called and return an object of product as this call is part of the code which I am testing right now.

        var product = serviceToTest.GetProductById(productid);

        Assert.That(product, Is.Not.Null);
        Assert.That(product.Id, Is.EqualTo<int>(productid));
    }
}

这是用于说明单元测试的示例代码。它可能无法按预期构建或运行,我为此道歉。