假设以下情形:我有一个使用PhoneController类的Phone类。 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);
    }
}


第二种情况:

控制器使用GetStatus抽象类中的Device方法,因此_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);
    }
}

最佳答案

如果PhoneController使用Phone,则必须使用PhonePhone的子类。使用Substitute.For<Device, IPhone>()将生成如下类型:

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


Phone不同。

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

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

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

编辑第二种情况

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

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

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 s),从而结合使用这些方法。

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

10-08 00:06