C# 对业务逻辑接口进行单元测试

C# 对业务逻辑接口进行单元测试,c#,nunit,moq,C#,Nunit,Moq,我这样做对吗?如果是这样的话,我的理解是否正确?我有点困惑 我的项目设置在三个不同的层中。UI层、业务层和数据访问层。业务层和数据访问层都是基于接口构建的 我正在尝试使用NUnit和Moq编写单元测试 这是我的例子。我想测试GetSum(intx,inty),这是一个只返回x+y的简单函数。此函数存在于CalculatorLogic中,并实现ICalculatorLogic public class CalculatorLogic : ICalculatorLogic { public

我这样做对吗?如果是这样的话,我的理解是否正确?我有点困惑

我的项目设置在三个不同的层中。UI层、业务层和数据访问层。业务层和数据访问层都是基于接口构建的

我正在尝试使用NUnit和Moq编写单元测试

这是我的例子。我想测试GetSum(intx,inty),这是一个只返回x+y的简单函数。此函数存在于CalculatorLogic中,并实现ICalculatorLogic

public class CalculatorLogic : ICalculatorLogic
{
    public int GetSum(int x, int y)
    {
        return x + y;
    }
}
现在,我正试图编写一个单元测试

[TestFixture]
public class CalculatorLogicTests
{
    Mock<ICalculatorLogic> calculatorLogicMock;
    ICalculatorLogic calculatorLogic;

    public CalculatorLogicTests()
    {
        calculatorLogicMock = new Mock<ICalculatorLogic>();

        // now i need to do this setup, right?
        calculatorLogicMock.Setup(x => x.GetSum(It.IsAny<int>(), It.IsAny<int>())).Returns(6);

        calculatorLogic = calculatorLogicMock.Object;
    }

    [Test]
    public void GetSum_Test()
    {
        int expectedResult = 3 + 3;

        var sum = calculatorLogic.GetSum(3, 3);
        Assert.AreEqual(sum, expectedResult);
    }
}
[TestFixture]
公共类计算器逻辑测试
{
模拟计算器逻辑锁;
iCalculator逻辑计算器逻辑;
公共计算器逻辑测试()
{
calculatorLogicMock=新模拟();
//现在我需要做这个设置,对吗?
calculatorLogicMock.Setup(x=>x.GetSum(It.IsAny(),It.IsAny())。返回(6);
calculatorLogic=calculatorLogicMock.Object;
}
[测试]
公共无效GetSum_测试()
{
int expectedResult=3+3;
var sum=calculatorLogic.GetSum(3,3);
断言.AreEqual(总和,预期结果);
}
}
现在,上述情况已经过去。它运行,它得到了我所期望的。然而,这感觉是错误的。它只是返回我在Setup()调用中设置为返回的内容。如果我在Returns()中加上3而不是6,它就会失败


我一定是理解错了。否则,如果我告诉函数返回什么,我真的在测试函数吗?

好的,那么您已经有了一个接口
ICalculatorLogic
和所述接口
CalculatorLogic
的实现,您希望为其编写测试

您正在使用模拟框架
Moq
来模拟您的
CalculatorLogic
没有的依赖项。你应该这样写:

[TestFixture]
public class CalculatorLogicTests
{
    // Our unit under test.
    ICalculatorLogic calculatorLogic;

    [SetUp]
    public void SetUp()
    {
        // This method is called for every [Test] in this class.
        // So let's recreate our CalculatorLogic here so that each
        // test has a fresh instance.
        calculatorLogic = new CalculatorLogic();
    }

    [Test]
    public void GetSum_WithTwoIntegers_ReturnsTheirSum()
    {
        // Arrange
        int expectedResult = 3 + 3;

        // Act
        var sum = calculatorLogic.GetSum(3, 3);

        // Assert
        Assert.AreEqual(sum, expectedResult);
    }
}
现在让我们假设您希望
GetSum
记录调用它的参数。您可以创建一个记录器接口,如下所示:

public interface ILogger {
    void Log(int x, int y);
}
然后,将其作为依赖项放入
CalculatorLogic
类的构造函数中:

public class CalculatorLogic : ICalculatorLogic
{
    private readonly ILogger logger;

    // Now we have a dependency on ILogger!
    public CalculatorLogic(ILogger l) {
        logger = l;
    }

    public int GetSum(int x, int y)
    {
        // Let's log those numbers!
        logger.Log(x, y);
        return x + y;
    }
} 
然后您可以这样编写测试(使用
Moq
):

[TestFixture]
公共类计算器逻辑测试
{
//我们要测试的这个家伙。
iCalculator逻辑计算器逻辑;
//我们的嘲笑!
模拟原木;
[设置]
公共作废设置()
{
//创建日志记录器模拟!
loggerMock=newmock();
//将记录器注入我们的CalculatorLogic!
calculatorLogic=新的calculatorLogic(loggerLock.Object);
}
[测试]
带有两个整数的public void GetSum__ShouldCallLogger()
{
//安排
int expectedResult=3+3;
//表演
var sum=calculatorLogic.GetSum(3,3);
//断言
断言.AreEqual(总和,预期结果);
//验证在x=3和y=3的情况下调用了记录器的Log方法一次。
验证(logger=>logger.Log(It.Is(x=>x==3),It.Is(y=>y==3)),Times.Once();
}
} 

您模拟的任何内容都没有经过测试,因此正如您所猜测的那样,这是一个毫无意义的测试。希望您的业务逻辑类中注入了数据访问依赖项,这些是您想要模拟的内容,以便您仍然可以实际执行业务逻辑。您可能会发现这很有帮助。那么,我是否应该从接口实现我的业务逻辑呢?我还在控制器中使用依赖注入,并以这种方式将业务逻辑加载到控制器中。我现在要读你的文章。谢谢。连接你的BL由你决定。我倾向于不这样做,但如果我真的需要模仿某些东西,就使用虚拟方法。我不希望总是运行业务逻辑的情况很少。您应该使用模拟来消除依赖关系,而不是逻辑,只是为了指出
Assert.AreEqual(sum,expectedResult)不正确。该方法签名的第一个参数是期望值,第二个参数是实际值。如果断言失败,这种错误的顺序将导致错误的消息。这种不合逻辑的顺序正是NUnit基于约束的语法变得流行的原因,这种语法在英语中读起来更好:
Assert.That(sum,is.EqualTo(expectedResult))
[TestFixture]
public class CalculatorLogicTests
{
    // This guy we want to test.
    ICalculatorLogic calculatorLogic;

    // Our mock!
    Mock<ILogger> loggerMock;

    [SetUp]
    public void SetUp()
    {
        // Create the logger mock!
        loggerMock = new Mock<ILogger>();

        // Inject the logger into our CalculatorLogic!
        calculatorLogic = new CalculatorLogic(loggerMock.Object);
    }

    [Test]
    public void GetSum_WithTwoIntegers_ShouldCallLogger()
    {
        // Arrange
        int expectedResult = 3 + 3;

        // Act
        var sum = calculatorLogic.GetSum(3, 3);

        // Assert
        Assert.AreEqual(sum, expectedResult);

        // Verify that the logger's Log method was called once with x = 3 and y = 3.
        loggerMock.Verify(logger => logger.Log(It.Is<int>(x => x == 3), It.Is<int>(y => y == 3)), Times.Once());
    }
}