通过在 C# 中使用 Moq 模拟外部 DLL 进行单元测试

问题描述

我正在尝试为我的一个依赖于外部 DLL 的函数编写单元测试。我决定使用 Moq(模拟外部依赖)来测试我的功能

MyClass 有一个函数一个外部依赖项。

namespace MyService
{
    /// <summary>
    /// MyClass
    /// </summary>
    public class MyClass : IMyClass
    {
        private ExternalClass ex;
        public MyClass() {
            ex = new ExternalClass();
        }
        public bool publish(string param) {
           if(... param is not valid)
              throw error;
           return ex.externalPublish(param);
        }
    }
}

外部类发布函数调用了一些运行时依赖,因此在单元测试期间不能期望函数运行


namespace ExternalService
{
    /// <summary>
    /// ExternalClass
    /// </summary>
    public class ExternalClass : IExternalClass,OtherExternalClass // OtherExternalClass is a concrete one,not an interface
    {
        public ExternalClass(){}

        public bool publish(string param) {
           // some implementation which contacts database,other running services etc.
        }
    }
}
namespace MyServiceTests
{
    using Moq;
     /// <summary>
    /// MyClass test
    /// </summary>
    [TestClass]
    public class MyClasstest
    {
      
        /// <summary>
        /// Publish
        /// </summary>
        [TestMethod]
        public void PublishTestSucceed()
        {
            var moq = new Mock<ExternalClass>(); // I tried IExternalClass but it is not the only inheritence that ExternalClass has.
            moq.CallBase = true;
            moq.Setup(tsmv2 => tsmv2.publish(
                It.IsAny<string>()))
                .Returns(true);


            MyClass myC = new MyClass();
            PrivateObject obj = new PrivateObject(myC);

            obj.SetField("ex",moq.Object); // If I use IExternalClass to create moq,I am not able to convert IExternalClass to ExternalClass here.

            bool result = myC.publish("asdf");
            Assert.IsTrue(result);
        }

所以有两个问题:

  1. 外部类并不完全依赖于一个接口。我还能在这里使用 Moq 库吗?
  2. 即使我使用接口,即使使用显式转换,我也无法将 moq.Object 转换为 IExternalClass 对象。

解决方法

被测对象与具体的外部实现细节紧密耦合,应该依赖于通过构造函数注入使用显式依赖原则的抽象。

重构的主题类:

public class MyClass : IMyClass {
    private IExternalClass ex;

    public MyClass(IExternalClass ex) {
        this.ex = ex;
    }

    public bool publish(string param) {
       if(... param is not valid)
          throw error;
       return ex.publish(param); //... external call
    }
}

对于主题类来说,依赖是外部的,这真的无关紧要。这是一个实现细节。

这现在允许主题类明确说明它需要什么来执行其功能,并且通过依赖抽象允许使用替代品。例如,在单独测试时。

[TestClass]
public class MyClassTest {
  
    public void PublishTestSucceed() {
        // Arrange
        var dependency = new Mock<IExternalClass>();
        dependency
            .Setup(_ => _.publish(It.IsAny<string>()))
            .Returns(true);

        MyClass myC = new MyClass(dependency.Object);

        //Act
        bool result = myC.publish("asdf");

        //Assert
        Assert.IsTrue(result);
    }
}

如果显式使用 IExternalClass 作为依赖项有太多限制,则考虑使用封装/包装外部依赖项功能的实现创建您自己的抽象。

这样您就可以更好地控制自己的代码,而不受您无法控制的依赖项的限制。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...