2 回答
TA贡献2039条经验 获得超7个赞
如果PhoneController
需要 a Phone
,那么您将不得不使用 aPhone
或 的子类Phone
。使用Substitute.For<Device, IPhone>()
将生成一个有点像这样的类型:
public class Temp : Device, IPhone { ... }
这与Phone
.
所以有几个选择。理想情况下,我会考虑@pm_2 的建议,PhoneController
即IPhone
改为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与其依赖项耦合?考虑一下它需要什么具体的依赖关系,以及可以用逻辑接口表达什么。答案将决定您的测试选项。
- 2 回答
- 0 关注
- 217 浏览
添加回答
举报