moq单元测试c#类库项目的最佳方法

时间:2016-10-24 17:23:45

标签: c# unit-testing mocking moq

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

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
            //
            //
        }
    }

3 个答案:

答案 0 :(得分:1)

不要使用静态工厂方法。将工厂类转换为可注入服务/接口,并将其注入被测系统。

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();
    }
}

答案 1 :(得分:1)

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

虽然SalaryManager使用工厂创建ICalc而不是它本身不够好,但工厂方法本身是静态方法,它返回一个具体的类,你不能改变它来返回模拟类而不是因为它是一种静态方法,因此根据这种设计,即使你的单元测试也必须使用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实例注入你的班级, 只需使用moq创建ICalc的模拟并将其传递给calss 通过构造函数。

  2. Anotehr选项,如果您仍想使用工厂(似乎 根据它的作用,在你的情况下相当无用,作为旁注,我倾向于在使用DI时使用工厂,只有当课程依赖于我不想在课堂上保持活力的IDisposable对象时才会使用工厂。整个生命时间)是改变 它从静态方法到将要实现的具体工厂 界面:

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

    现在您应该将工厂interfce注入SalaryManager类 通过它的构造函数,并在需要时使用它:

    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模拟并将其传递给您的类,您应该设置模拟的facotry以在使用facotry方法时返回ICalc模拟,并选择1作为选项。

答案 2 :(得分:1)

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

例如:

[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);
}