模拟像计算器这样的类的目的是什么?

时间:2014-05-07 12:40:46

标签: c# unit-testing mocking tdd moq

我一直在使用TDD一段时间,但现在我正在考虑嘲笑框架而且我没有得到一些东西。这个问题对于经验丰富的人来说可能听起来很愚蠢,但我只是不明白。我使用的库是Moq + xUnit。

问题

如果我明确表示Calculator将在此行2 + 2上返回4然后断言,那么测试mock.Setup(x => x.Add(2, 2)).Returns(4);课程的重点是什么?

当然结果将是4,我只是“强迫”它在测试本身上方的几行中返回4。现在,即使我执行return a * b;代替return a + b;,测试也会通过。

这是同样的计算器测试的另一个例子。 http://nsubstitute.github.io/

示例代码

namespace UnitTestProject1
{
    using Xunit;
    using Moq;

    public class CalculatorTests
    {
        private readonly ICalculator _calculator;

        public CalculatorTests()
        {
            var mock = new Mock<ICalculator>();

            mock.Setup(x => x.Add(2, 2)).Returns(4);
            mock.Setup(x => x.Subtract(5, 2)).Returns(3);

            this._calculator = mock.Object;
        }

        [Fact]
        public void Calculator_Should_Add()
        {
            var result = _calculator.Add(2, 2);

            Assert.Equal(4, result);
        }

        [Fact]
        public void Calculator_Should_Subtract()
        {
            var result = _calculator.Subtract(5, 2);

            Assert.Equal(3, result);
        }
    }

    public class Calculator : ICalculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Subtract(int a, int b)
        {
            return a - b;
        }
    }

    public interface ICalculator
    {
        int Add(int a, int b);
        int Subtract(int a, int b);
    }
}

4 个答案:

答案 0 :(得分:7)

目的是能够根据计算器测试课程而不需要计算器是自己的。在你的测试中,你知道计算器不能成为失败的原因,因为它会返回正确的答案。

通过隔离测试中的代码,您将能够测试真实的代码单元。并且还要确切了解导致测试失败的原因。

答案 1 :(得分:6)

你不应该对模拟进行单元测试。 假设您要测试使用IService和IStorage的对象OrderProcessor。

To&#34;单元测试&#34; OrderProcessor,您可以模拟IService和IStorage的行为,以便您可以在不使用Web服务和数据库的情况下验证目标类是否按预期工作。

即。

class OrderProcessor{
 private IService service, IStorage storage;
 public OrderProcessor(IService service, IStorage storage){ // bla bla}

 public ProcessOrder(Order o){
   // do something

  // use the service
  var price = service.GetPrice(..);


  // store the result
  storage.StoreOrder(order);
 }
}

// test. Define mocks
var mockStorage = new Mock<IStorage>();
var mockService = new Mock<IService>();

// Setup test behaviour
mockStorage.Setup(m => m.GetPrice("X10").Returns(11);
mockStorage.Setup(m => m.GetPrice("X11").Returns(99);
...
var target = new OrderProcessor(mockService.Object, mockStorage.Object);

// ...
target.ProcessOrder(o);

// Verify the storing was called
mockStorage.Verify(m => m.StoreOrder(o), Times.Once());

// Verify the service was called X times
mockService .Verify(m => m.GetPrice(x), Times.Exactly(order.Items.Count));

答案 2 :(得分:5)

在这种情况下,嘲笑没有意义 - 这个例子太简单了。你通过嘲笑ICalculator来获得任何东西。

当你有一个复杂的实现时,你会嘲笑,并且你试图在该接口的实现上测试依赖的东西。在这种情况下,您没有这样做,您正在测试模拟实现。测试模拟实现是没有意义的。

例如,假设您的计算器实现实际调用了Web服务来执行计算,并且您尝试测试消耗服务计算的内容。您的目标不是测试计算器 - 您的目标是测试使用计算器的东西。让测试依赖于正在运行的Web服务是愚蠢的,并且可能导致您的测试意外失败。

答案 3 :(得分:3)

使用模拟代替依赖。

例如:

public interface IAddModule
{
    int Add(int lhs, int rhs);
}

public class Calculator
{
    private readonly IAddModule _addModule;

    public Calculator(IAddModule addModule)
    {
        _addModule = addModule;
    }

    public int Add(int lhs, int rhs)
    {
        return _addModule.Add(lhs, rhs);
    }
}

Calculator课程取决于IAddModule。根据{{​​1}}的实现方式,它可能会产生记录或非托管代码等副作用。要隔离依赖项,可以使用Mock代替IAddModule来测试类。

IAddModule