繁体   English   中英

从抽象类继承并实现接口的模拟类

[英]Mock class which inherit from abstract class and implements interface

假定以下情形:我有一个使用Phone类的PhoneController类。 Phone是一个从抽象类Device继承的类,它实现了IPhone接口。 为了测试PhoneController我想模拟Phone类,但是我不知道如何使用NSubstitute完成它,因为Phone类继承了抽象类并且另外实现了接口。

示例代码:

public abstract class Device
{
    protected string Address { get; set; }
}

public interface IPhone
{
    void MakeCall();
}

public class Phone : Device, IPhone
{
    public void MakeCall()
    {
        throw new NotImplementedException();
    }
}

public class PhoneController
{
    private Phone _phone;

    public PhoneController(Phone phone)
    {
        _phone = phone;
    }
}

[TestClass]
public class PhoneControllerTests
{
    [TestMethod]
    public void TestMethod1()
    {
        // How mock Phone class? 
        //var mock = Substitute.For<Device, IPhone>();

        //usage of mock
        //var controller = new PhoneController(mock);
    }
}

第二种情况:

控制器使用Device抽象类中的GetStatus方法,因此_phone不能更改为IPhone类型

public abstract class Device
{
    protected string Address { get; set; }

    public abstract string GetStatus();
}

public interface IPhone
{
    void MakeCall();
}

public class Phone : Device, IPhone
{
    public void MakeCall()
    {
        throw new NotImplementedException();
    }

    public override string GetStatus()
    {
        throw new NotImplementedException();
    }
}

public class PhoneController
{
    private Phone _phone;

    public PhoneController(Phone phone)
    {
        _phone = phone;
    }

    public string GetDeviceStatus()
    {
        return _phone.GetStatus();
    }

    public void MakeCall()
    {
        _phone.MakeCall();
    }
}

[TestClass]
public class PhoneControllerTests
{
    [TestMethod]
    public void TestMethod1()
    {
        // How mock Phone class? 
        //var mock = Substitute.For<Device, IPhone>();

        //usage of mock
        //var controller = new PhoneController(mock);
    }
}

有两种方法可以执行此操作,您选择哪种方法取决于您要测试的内容。

  1. 您可以模拟iPhone接口(如注释掉的代码中所做的那样。

  2. 您可以继承Phone类的子类(手动或使用NSubstitute的.ForPartsOf<> )。 请参阅此处的博客文章。

我发现如果我使用Arrange / Act / Assert方法构造测试,则可以清楚地知道我要测试的内容(理想情况下,您的Act部分应该只有一个调用;例如:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var mock = Substitute.For<IPhone>();
    var controller = new PhoneController(mock);

    // Act
    int result = controller.Method();

    // Assert
    Assert.Equal(result, 3);
}

编辑-基于更新的评论

您不能对抽象类进行单元测试,因为该类不包含(根据定义)任何代码。 在您的方案中,您似乎要尝试测试具体的Phone类。 如果是这样,则只需创建Phone类的实例并对其进行测试; 您不需要涉及控制器:

// Arrange
var phone = new Phone();

// Act
string result = phone.GetStatus();

// Assert
Assert.Equal("New", result);

如果PhoneController需要一个Phone ,那么你将不得不使用Phone或子类Phone 使用Substitute.For<Device, IPhone>()将生成如下类型:

public class Temp : Device, IPhone { ... }

这与Phone

因此,有一些选择。 理想情况下,我会考虑让@ pm_2建议PhoneController IPhone 通过使它依赖于接口而不是具体的东西,我们获得了一定的灵活性: PhoneController现在可以与所有遵循IPhone接口的东西(包括PhoneController IPhone实例)一起使用。 这也意味着我们可以通过其他实现来更改生产代码的行为(人为的示例,例如EncryptedPhone : IPhone可以加密调用,而无需更改PhoneController )。

如果确实需要将特定的Phone类耦合到PhoneController ,则立即进行PhoneController将变得更加困难。 毕竟,您要说“这仅适用于该特定类”,然后尝试使其与另一类(替代类)一起使用。 对于这种方法,如果您能够将Phone类的所有相关成员virtual则可以使用Substitute.For<Phone>()创建替代对象。 如果您还需要运行一些原始Phone代码,但要替换其他部分,则可以按照@ pm_2的建议使用Substitute.ForPartsOf<Phone>() 请记住,NSubstitute(和许多其他.NET模拟库)不会模拟非virtual成员,因此在模拟类时请记住这一点。

最后,值得考虑的是根本不要嘲笑Phone ,而是使用real类。 如果PhoneController取决于特定Phone实现的详细信息,则使用伪造版本对其进行测试不会告诉您它是否有效。 如果相反,它仅需要兼容的接口,则它是使用替代品的理想选择。 模拟库会自动创建与类一起使用的替代类型,但不会自动设计出可以使用该类型的设计。 :)

编辑第二种情况

我认为我先前的答案仍然适用于其他情况。 回顾一下:我的第一种方法是尝试将PhoneController与特定的实现分PhoneController来。 那么我会考虑直接用Phone (用关于非virtual方法的免责声明)代替Phone 而且我始终牢记一点都不嘲笑(这应该是第一个选择)。

我们有很多方法可以实现第一个选择。 我们可以使用构造函数PhoneController(IPhone p, IDevice d)更新PhoneController以获取IDevice (从Device提取接口)和IPhone 实际的代码可以是:

var phone = new Phone();
var controller = new PhoneController(phone, phone);

虽然测试代码可以是:

var phone = Substitute.For<IPhone>();
var device = Substitute.For<IDevice>();
var testController = new PhoneController(phone, device);
// or
var phone = Substitute.For<IPhone, IDevice>();
var testController = new PhoneController(phone, (IDevice) phone);

或者,我们可以创建IPhoneIDevice然后具有:

interface IPhoneDevice : IPhone, IDevice { }
public class Phone : IPhoneDevice { ... }

我以前不鼓励这样的接口继承,因此您可能需要在使用此选项之前先进行研究。

您还可以通过将以上示例中的IDevice替换为当前的Device基类并对其进行virtual (免责声明:non- virtual s)来组合这些方法。

我认为您需要回答的主要问题是如何将PhoneController耦合到其依赖项? 从需要什么具体依赖关系以及可以用逻辑接口表达的角度考虑这一点。 答案将决定您的测试选项。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM