为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
蝴蝶刀刀 2021-06-03 14:46:37
假设以下场景:我有一个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);    }}C#
查看完整描述

2 回答

?
largeQ

TA贡献2039条经验 获得超7个赞

如果PhoneController需要 a Phone,那么您将不得不使用 aPhone或 的子类Phone。使用Substitute.For<Device, IPhone>()将生成一个有点像这样的类型:

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

这与Phone.

所以有几个选择。理想情况下,我会考虑@pm_2 的建议PhoneControllerIPhone改为take an 。通过让它依赖于接口而不是具体的东西,我们获得了一些灵活性:PhoneController现在可以处理任何依附于IPhone接口的东西,包括模拟IPhone实例。这也意味着我们可以通过其他实现来更改生产代码的行为(人为的示例,可能EncryptedPhone : IPhone是加密调用的 ,而无需更改PhoneController)。

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

最后,值得考虑根本不嘲笑Phone并使用真正的类。如果PhoneController取决于具体Phone实现的细节,那么用假版本测试它不会告诉你它是否有效。相反,如果它只需要一个兼容的接口,那么它是使用替代品的一个很好的候选者。模拟库会自动创建与类一起使用的替代类型,但它们不会自动拥有适合该类型使用的设计。:)

编辑第二个场景

我认为我之前的答案仍然适用于额外的场景。回顾一下:我的第一种方法是尝试将 与PhoneController特定实现分离;那么我会考虑Phone直接替换(带有关于非virtual方法的免责声明)。而且我始终牢记根本不要嘲笑(这可能应该是第一个选择)。

有很多方法可以实现第一个选项。我们可以更新PhoneController以获取一个IDevice(extract interface from Device) 和 an 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);

或者,我们可以创建IPhone,IDevice然后有:


interface IPhoneDevice : IPhone, IDevice { }

public class Phone : IPhoneDevice { ... }

我以前看到过不鼓励这样的接口继承,因此您可能需要在选择此选项之前先研究一下。


您还可以组合这些方法,通过IDevice在上面的示例中替换为您当前的Device基类并模拟它(免责声明:非virtuals)。


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


查看完整回答
反对 回复 2021-06-05
  • 2 回答
  • 0 关注
  • 191 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信