代码之家  ›  专栏  ›  技术社区  ›  mkul

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

  •  2
  • mkul  · 技术社区  · 6 年前

    假设以下场景:我有一个 PhoneController 使用的类 Phone 电话 是从抽象类继承的类 Device 它实现了 IPhone 界面用于测试 电话控制器 我想嘲笑 电话 类,但我不知道如何使用NSubstitute实现,因为 电话 类继承抽象类,并实现接口。

    示例代码:

    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 方法来自 装置 抽象类,所以 _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);
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  3
  •   Paul Michaels    6 年前

    有几种方法可以做到这一点,您选择哪一种取决于您要测试的内容。

    1. 您可以模拟IPhone界面(就像您在注释掉的代码中所做的那样)。

    2. 您可以对Phone类进行子类化(手动或使用NSubstitute的 .ForPartsOf<> ).参见 here 有关此的博客帖子。

    我发现,如果我使用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 班如果是这种情况,那么只需创建 电话 分类并测试;您不需要让控制员参与:

    // Arrange
    var phone = new Phone();
    
    // Act
    string result = phone.GetStatus();
    
    // Assert
    Assert.Equal("New", result);
    
        2
  •  2
  •   David Tchepak    6 年前

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

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

    这与 电话

    所以有几个选择。理想情况下我会考虑 @pm_2's suggestion 制造的 电话控制器 采取行动 IPhone 相反通过使其依赖于接口而不是具体的东西,我们获得了一些灵活性: 电话控制器 现在可以处理任何符合 IPhone 接口,包括模拟 IPhone 例子这也意味着我们可以通过其他实现(人为的示例,可能是 EncryptedPhone : IPhone 对通话进行加密,无需更改 电话控制器 )。

    如果您确实需要将特定 电话 分类至 电话控制器 ,则嘲弄立即变得更加困难。毕竟,您是在声明“这只适用于这个特定的类”,然后试图让它适用于另一个类(被替换的类)。对于这种方法,如果您能够使 电话 virtual 然后可以使用 Substitute.For<Phone>() 。如果需要,还需要运行一些原始 电话 代码,但替换其他部分,然后可以使用 Substitute.ForPartsOf<Phone>() @pm_2 suggested 。请记住,NSubstitute(和许多其他.NET模拟库)不会模拟非- 事实上的 成员,所以在模拟类时请记住这一点。

    最后,值得考虑的是不要嘲笑 电话 而不是使用真正的类。如果 电话控制器 具体取决于 电话 实现,然后用假版本测试它,并不能告诉您它是否有效。如果它只需要一个兼容的接口,那么它是一个很好的替代品。模拟库自动创建用于类的替代类型,但它们不会自动生成适应该类型使用的设计。:)

    编辑第二个方案

    我认为我之前的回答仍然适用于其他场景。概括一下:我的第一种方法是尝试将 电话控制器 从具体实施;然后我会考虑用 电话 直接(带有关于非- 事实上的 方法)。而且我始终牢记不要嘲笑(这可能是第一个选择)。

    我们有很多方法可以实现第一个选择。我们可以更新 电话控制器 采取行动 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);
    

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

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

    我以前见过不鼓励这样的接口继承,所以在选择此选项之前,您可能希望先了解一下这一点。

    您还可以通过替换 IDevice公司 在上面的示例中 装置 基类和模拟(免责声明:非- 事实上的 s) 。

    我认为你需要回答的主要问题是你想如何结合 电话控制器 它的依赖关系?考虑一下它需要什么具体的依赖关系,以及什么可以用逻辑接口表示。答案将决定您的测试选项。