【问题标题】:What is purpose of mocking a class like Calculator?模拟像计算器这样的类的目的是什么?
【发布时间】:2014-06-24 10:47:41
【问题描述】:

我使用 TDD 已经有一段时间了,但现在我正在研究模拟框架,但我没有得到一些东西。对于有经验的人来说,这个问题可能听起来很愚蠢,但我就是不明白。我使用的库是 Moq + xUnit。

问题

如果我明确说2 + 2 将在mock.Setup(x => x.Add(2, 2)).Returns(4); 这一行返回4 然后断言它,那么测试Calculator 类有什么意义?

当然结果是 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);
    }
}

【问题讨论】:

  • 目标是模拟对象的一部分,以便仅测试该对象的另一部分。你不测试什么被嘲笑,那没有意义。
  • 你完全明白嘲笑的目的吗?
  • 实际上是正确的,目的是隔离那个单独的部分。
  • 您的示例实现案例很好地证明了使用 Add() 测试计算 Add(2,3) 而不是 (2,2)。
  • @IamStalker 我知道目的是模拟存储库。但是为什么在示例中我会看到这样的类?这个类甚至应该使用嘲笑吗?我可以嘲笑一些事情而别管其他事情吗?

标签: c# unit-testing mocking tdd moq


【解决方案1】:

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

通过隔离被测代码,您将能够测试真实的代码单元。并准确查看导致您的测试失败的原因。

【讨论】:

  • 现在我明白了。所以这个类不应该使用模拟,但是使用 ICalculator 的不同类应该在它的测试中使用计算器模拟。完全清楚。谢谢。
【解决方案2】:

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

要“单元测试”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));

【讨论】:

    【解决方案3】:

    在这种情况下,模拟是没有意义的——这个例子太简单了。嘲笑ICalculator 并没有任何收获。

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

    例如,假设您的计算器实现实际上调用了一个 Web 服务来执行计算,并且您试图测试一些消耗来自该服务的计算的东西。你的目标不是测试计算器——你的目标是测试使用计算器的东西。让您的测试依赖于 Web 服务的启动和运行是愚蠢的,并且可能导致您的测试意外失败。

    【讨论】:

      【解决方案4】:

      Mocks 用于代替依赖项。

      例如:

      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。根据IAddModule 的实现方式,它可能会产生副作用,例如日志记录或非托管代码。要隔离依赖项,请使用 Mock 代替 IAddModule 来测试类。

      public class CalculatorTests
      {
          private readonly Calculcator _calculator;
      
          public CalculatorTests()
          {
              var mock = new Mock<IAddModule>();
              mock.Setup(a => a.Add(2, 2)).Returns(4);
              _calculator = new Calculator(mock.Object);
          }
      
          [Fact]
          public void Given_2_And_2_Then_4_Is_Returned()
          {
              var result = _calculator.Add(2, 2);
      
              Assert.Equal(4, result);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-16
        • 1970-01-01
        • 2019-12-20
        • 2016-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多