简体   繁体   English

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

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

Assume following scenario: I have a PhoneController class which use Phone class. 假定以下情形:我有一个使用Phone类的PhoneController类。 Phone is a class which inherit from abstract class Device and it implements IPhone interface. Phone是一个从抽象类Device继承的类,它实现了IPhone接口。 For testing PhoneController I want to mock Phone class, but I don't know how it might be done using NSubstitute, because Phone class inherits abstract class and additionally it implements interface. 为了测试PhoneController我想模拟Phone类,但是我不知道如何使用NSubstitute完成它,因为Phone类继承了抽象类并且另外实现了接口。

Example code: 示例代码:

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);
    }
}

Second scenario: 第二种情况:

Controller uses GetStatus method from Device abstract class, so _phone cannot be changed to IPhone type 控制器使用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);
    }
}

There are a couple of ways to do this, and which one you choose depends what you're trying to test. 有两种方法可以执行此操作,您选择哪种方法取决于您要测试的内容。

  1. You can mock the IPhone interface (as you have done in your commented out code. 您可以模拟iPhone接口(如注释掉的代码中所做的那样。

  2. You can subclass the Phone class (either manually, or use NSubstitute's .ForPartsOf<> ). 您可以继承Phone类的子类(手动或使用NSubstitute的.ForPartsOf<> )。 See here for a blog post on this. 请参阅此处的博客文章。

I find that if I structure my test using the Arrange/Act/Assert method, it's clearer what I'm trying to test (ideally there should be a single call in your Act section; for example: 我发现如果我使用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);
}

EDIT - Based on updated comments 编辑-基于更新的评论

You can't unit test an abstract class, because that class doesn't contain (by definition) any code. 您不能对抽象类进行单元测试,因为该类不包含(根据定义)任何代码。 In your scenario, it looks like what you're trying to do is to test the concrete Phone class. 在您的方案中,您似乎要尝试测试具体的Phone类。 If that's the case, then simply create an instance of the Phone class and test it; 如果是这样,则只需创建Phone类的实例并对其进行测试; you don't need to involve the controller: 您不需要涉及控制器:

// Arrange
var phone = new Phone();

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

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

If PhoneController takes a Phone , then you will have to use a Phone or a subclass of Phone . 如果PhoneController需要一个Phone ,那么你将不得不使用Phone或子类Phone Using Substitute.For<Device, IPhone>() will generate a type a bit like this: 使用Substitute.For<Device, IPhone>()将生成如下类型:

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

Which is not the same as a Phone . 这与Phone

So there are a few options. 因此,有一些选择。 Ideally I'd consider @pm_2's suggestion of making PhoneController take an IPhone instead. 理想情况下,我会考虑让@ pm_2建议PhoneController IPhone By making it depend on an interface rather than something concrete we gain some flexibility: PhoneController now can work with anything that adheres to the IPhone interface, including a mocked IPhone instance. 通过使它依赖于接口而不是具体的东西,我们获得了一定的灵活性: PhoneController现在可以与所有遵循IPhone接口的东西(包括PhoneController IPhone实例)一起使用。 This also means we can change the behaviour of production code by have other implementations (contrived example, maybe an EncryptedPhone : IPhone that encrypts the call, without requiring a change to PhoneController ). 这也意味着我们可以通过其他实现来更改生产代码的行为(人为的示例,例如EncryptedPhone : IPhone可以加密调用,而无需更改PhoneController )。

If you do need to couple the specific Phone class to PhoneController , then mocking immediately becomes more difficult. 如果确实需要将特定的Phone类耦合到PhoneController ,则立即进行PhoneController将变得更加困难。 After all, you are stating "this only works with this specific class", then trying to get it working with another class (the substituted class). 毕竟,您要说“这仅适用于该特定类”,然后尝试使其与另一类(替代类)一起使用。 For this approach, if you are able to make all the relevant members of the Phone class virtual then you can create a substitute using Substitute.For<Phone>() . 对于这种方法,如果您能够将Phone类的所有相关成员virtual则可以使用Substitute.For<Phone>()创建替代对象。 If you need to also need to run some of the original Phone code, but substitute other parts, then you can using Substitute.ForPartsOf<Phone>() as @pm_2 suggested . 如果您还需要运行一些原始Phone代码,但要替换其他部分,则可以按照@ pm_2的建议使用Substitute.ForPartsOf<Phone>() Remember that NSubstitute (and many other .NET mocking libraries) will not mock non- virtual members, so keep this in mind when mocking classes. 请记住,NSubstitute(和许多其他.NET模拟库)不会模拟非virtual成员,因此在模拟类时请记住这一点。

Finally, it is worth considering not mocking Phone at all and using the real class instead. 最后,值得考虑的是根本不要嘲笑Phone ,而是使用real类。 If PhoneController depends on the details of specific Phone implementation, then testing it with a fake version is not going to tell you whether it works. 如果PhoneController取决于特定Phone实现的详细信息,则使用伪造版本对其进行测试不会告诉您它是否有效。 If instead it only needs a compatible interface, then it is a good candidate for using a substitute. 如果相反,它仅需要兼容的接口,则它是使用替代品的理想选择。 Mocking libraries automate the creation of an alternative type to use with a class, but they will not automate having a design that accommodates the use of that type. 模拟库会自动创建与类一起使用的替代类型,但不会自动设计出可以使用该类型的设计。 :) :)

Edit for second scenario 编辑第二种情况

I think my previous answer still applies for the additional scenario. 我认为我先前的答案仍然适用于其他情况。 To recap: my first approach is to attempt to decouple the PhoneController from a specific implementation; 回顾一下:我的第一种方法是尝试将PhoneController与特定的实现分PhoneController来。 then I'd consider substituting for Phone directly (with the disclaimer about non- virtual methods). 那么我会考虑直接用Phone (用关于非virtual方法的免责声明)代替Phone And I'd always keep in mind not mocking at all (this should probably be the first option). 而且我始终牢记一点都不嘲笑(这应该是第一个选择)。

There are many ways we can achieve the first option. 我们有很多方法可以实现第一个选择。 We could update PhoneController to take an IDevice (extract interface from Device ) and an IPhone , with the constructor PhoneController(IPhone p, IDevice d) . 我们可以使用构造函数PhoneController(IPhone p, IDevice d)更新PhoneController以获取IDevice (从Device提取接口)和IPhone The real code can then be: 实际的代码可以是:

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

While the test code could be: 虽然测试代码可以是:

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);

Alternatively we could create IPhone , IDevice and then have: 或者,我们可以创建IPhoneIDevice然后具有:

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

I've seen interface inheritance like this discouraged before, so you may want to look into that first before taking this option. 我以前不鼓励这样的接口继承,因此您可能需要在使用此选项之前先进行研究。

You can also combine these approaches, by replacing IDevice in the examples above with your current Device base class and mocking that (disclaimer: non- virtual s). 您还可以通过将以上示例中的IDevice替换为当前的Device基类并对其进行virtual (免责声明:non- virtual s)来组合这些方法。

I think the main question you need to answer is how to you want to couple PhoneController to its dependencies? 我认为您需要回答的主要问题是如何将PhoneController耦合到其依赖项? Think about this in terms of what concrete dependencies does it need, and what can be expressed in terms of logical interfaces. 从需要什么具体依赖关系以及可以用逻辑接口表达的角度考虑这一点。 The answer to that will determine your options for testing. 答案将决定您的测试选项。

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

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