【问题标题】:Unit Testing a Business Logic Interface对业务逻辑接口进行单元测试
【发布时间】:2018-06-08 18:13:32
【问题描述】:

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

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

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

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

public class CalculatorLogic : ICalculatorLogic
{
    public int GetSum(int x, int y)
    {
        return x + y;
    }
}

现在,这是我尝试编写 UnitTest 的方式。

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

现在,以上通过了。它运行,它得到了我的期望。不过,感觉不对。它只是返回我在 Setup() 调用中设置的返回值。如果我在 Returns() 中放入 3 而不是 6,它将失败。

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

【问题讨论】:

  • 你模拟的任何东西都没有经过测试,所以这是一个毫无意义的测试,正如你所推测的那样。希望您的业务逻辑类将数据访问依赖项注入其中,这些是您想要模拟的东西,因此您仍然可以实际练习您的业务逻辑。
  • I have a blog post 你可能会觉得有帮助。
  • 那么我不应该从接口实现我的业务逻辑吗?我还在我的控制器中使用依赖注入,并以这种方式将业务逻辑加载到它们中。我现在要读你的文章。谢谢。
  • 连接您的 BL 取决于您。我倾向于不这样做,但如果我确实需要模拟某些东西,请使用虚拟方法。在少数情况下我不想总是运行业务逻辑。
  • 你应该使用模拟来消除依赖而不是逻辑

标签: c# nunit moq


【解决方案1】:

好的,你已经有了一个接口 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]
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());
    }
} 

【讨论】:

  • 只是指出Assert.AreEqual(sum, expectedResult); 是不正确的。该方法签名的第一个参数是 expected 值,第二个参数是 actual 值。如果断言失败,这个错误的顺序会导致错误的消息。这种不合逻辑的顺序是 NUnit 的基于约束的语法变得流行的原因,它在英语中“读起来”更好:Assert.That(sum, Is.EqualTo(expectedResult));
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
相关资源
最近更新 更多