最小起订量单元测试 c# class 库项目的最佳方式

Best way to moq unit test c# class library project

SalaryManager class 有一个名为 DoCalculation 的方法,它调用使用工厂模式实现的 GetSum 方法。除了调用 GetSum 方法之外,DoCalculation 方法还执行一些其他操作。我想通过模拟对 GetSum() 的调用来对 DoCalculation 方法进行单元测试。有人可以建议在最小起订量模拟中实现它的最佳方法吗?下面是示例代码,

interface ICalc
{
int GetSum(int a, int b);
}

    class NormalCalc : ICalc
    {
        public int GetSum(int a,int b)
        {
            return a + b;
        }
    }
    class SumFactory
    {
        public static ICalc GetSumObject(int option)
        {
            if (option == 1)
                return new NormalCalc();
            return null;
        }
    }
    class SalaryManager
    {
        private static ICalc CalcRef = SumFactory.GetSumObject(1);

        public int DoCalculation(int a, int b)
        {
            int Sum=CalcRef.GetSum(a, b);
            //Perform some other operation
            //
            //
        }
    }

不要使用静态工厂方法。将工厂 class 转换为可注入 service/interface 并将其注入到被测系统中。

public interface ISumFactory {
    ICalc GetSumObject(int option);
}

public class SumFactory : ISumFactory {
    public  ICalc GetSumObject(int option) {
        if (option == 1)
            return new NormalCalc();
        return null;
    }
}
public class SalaryManager {
    private ICalc CalcRef;
    public SalaryManager(ISumFactory factory) {
        CalcRef = factory.GetSumObject(1);
    }

    public int DoCalculation(int a, int b) {
        int Sum = CalcRef.GetSum(a, b);
        //Perform some other operation
        //
        //
        //...;
    }
}

然后模拟依赖项以测试和验证预期。

[TestClass]
public class MyTestClass {
    [TestMethod]
    public void MyTestMethod() {
        //Arrange
        var calcMock = new Mock<ICalc>();
        calcMock.Setup(m => m.GetSum(It.IsAny<int>(), It.IsAny<int>()))
            .Returns((int a, int b) => a + b)
            .Verifiable();

        var factoryMock = new Mock<ISumFactory>();
        factoryMock.Setup(m => m.GetSumObject(1)).Returns(calcMock.Object)
        .Verifiable();

        var sut = new SalaryManager(factoryMock.Object);

        //Act
        var result = sut.DoCalculation(1, 1);

        //Assert
        //...
        factoryMock.Verify();
        calcMock.Verify();
    }
}

为了模拟 GetSum 方法,您需要以某种方式将 ICalc 依赖项注入 SalaryManager class。

尽管 SalaryManager 使用工厂创建 ICalc 并且它本身并不够好,工厂方法本身是静态方法,return是一个具体的 class,您无法更改它被 return 模拟 class 而不是因为它是一个静态方法,因此根据这个设计,即使你的单元测试也必须使用 NormalCalc 作为 Icalc 实现。

你基本上有两个选择:

  1. 通过其构造函数将 ICalc 直接注入 SalaryManager:

    public class SalaryManager
    {
        private readonly ICalc _calc;
    
        public SalaryManager(ICalc clac)
        {
            _calc = calc;
        }
    
        public int DoCalculation(int a, int b)
        {
            int Sum = _calc.GetSum(a, b);
            //...
        }
    }
    

    现在很容易将模拟的 ICalc 实例注入到您的 class, 只需使用 moq 创建 ICalc 的模拟并将其传递给 calss 通过构造函数。

  2. 如果您仍想使用工厂(这似乎 根据它的作用,在你的情况下非常无用,作为旁注,我倾向于在使用 DI 时使用工厂,只有当 class 依赖于一个我不想为 [= 保持活动状态的 IDisposable 对象时38=]的整个生命时间)就是要改变 它从静态方法到将实现一个具体工厂 接口:

    public interface ISumFactory
    {
        ICalc GetCalc(int option);
    }
    
    public SumFactory : ISumFactory
    {
        public ICalc GetCalc(int option)
        {
             if (option == 1)
                return new NormalCalc();
            return null;
        }     
    }
    

    现在你应该将工厂接口注入 SalaryManager class 通过它的构造函数并在需要时使用它:

    public class SalaryManager
    {
        private readonly ICalcFacotry _calcFactory;   
    
        public SalaryManager(ICalcFacotry clacFacotry)
        {
            _calcFactory = clacFacotry;
        }
    
        public int DoCalculation(int a, int b)
        {
            ICalc calc = _calcFactory.GetCalc(1);
            int Sum = calc.GetSum(a, b);
            //...
        }
    }
    

    现在在你的单元测试中你可以创建 ICalcFacotry mock 并将其传递给你的 class,你应该将模拟的 facotry 设置为 return 当使用 facotry 方法时使用 1 作为 ICalc mock选项.

您可以在不更改源代码的情况下对该方法进行单元测试。
通过使用 Typmock Isolator,您将能够在 class 创建之前模拟它的未来实例,因此当您模拟 Iclac 的未来实例时,它将模拟所有 classes 实现了这个接口。

例如:

[TestMethod]
public void TestMethod()
{
    //mock the future instances of Iclac 
    //and when the next NormalClac will be created it will be mocked as well
    var fakeIclac = Isolate.Fake.NextInstance<ICalc>();

    //setting the behavior of GetSum
    Isolate.WhenCalled(() => fakeIclac.GetSum(0, 0)).WillReturn(5);

    var result = new SalaryManager().DoCalculation(0, 0);

    Assert.AreEqual(5, result);
}