单元测试是否应该独立于实现?

时间:2015-07-27 13:58:29

标签: unit-testing mocking

我一直被告知单元测试应该是独立于实现的。这对我来说非常有意义 - 如果函数的实现发生变化,它仍然应该满足其API契约,因此不需要更改单元测试。

然而,这似乎与嘲弄的想法相冲突。如果我需要模拟外部依赖项,这会立即使我的测试依赖于我的实现。如果我要使用不同的依赖项,我必须在单元测试中更改模拟代码。将所有单元测试完全独立实现是不切实际/不合理的吗?

1 个答案:

答案 0 :(得分:0)

我不确定我是否理解你,但我会尽力而为。

假设您为课程PrepaidPointManager编写单元测试。它有方法void Recharge(int points)为用户点帐户充值。在这个课程的实施过程中你决定它应该取决于某些ICreditCardProvider(因为你想换取现金换取积分),还取决于IPointRepository,因为你想在一些存储中保持新的积分余额。 / p>

充值方法在使用信用卡提供商之前和存储余额之前进行一些计算。

  1. 当您为此类编写单元测试(这是您的具体实现)时,您不希望将CreditCardProvider的任何特定实现拖到测试中(可能因为它需要Internet连接并且速度很慢)。同样的故事是PointRepository
  2. 对于这些依赖项,您可以创建存根/模拟(ICreditCardProviderIPointRepository),然后将它们注入PrepaidPointManager

    [Test]
    public void ShouldCallCreditCardProviderPoints() {
       // arrange
       ICreditCardProvider creditCardProvideMock = mock<ICreditCardProvider>();
       IPointRepository pointRepositoryStub = stub<IPointRepository>();
       PrepaidPointManager sut = new PrepaidPointManager(creditCardProvideMock ,pointRepositoryStub );
    
       // act
       sut.Recharge(100);
    
       // asserts/verify
       creditCardProvideMock.Received().Charge(/*...*.) // verify if charge was called
    }
    

    现在,您的测试独立来自依赖项的实现细节(ICreditCardProvider,IPointRepository)。这就是我从你的问题中理解术语独立实现的方式。

    当然,如果您认为PrepaidPointManager应该具有其他依赖关系,那么单元测试代码也需要更改。

    在设计良好的系统中,您可以编写针对抽象的代码,当您确实需要更改某些类的依赖关系时,此更改将仅影响少量测试。

    嘲笑背后的主要思想(从我的角度来看)是当你编写测试用例时,你会说出这样的话:

    • 我不关心信用卡提供商的工作方式
    • 我不在乎持久性如何工作

    我只是想测试一下,当我充值时,使用信用卡提供商。

    如果您想要具体的CreditCardProvider的测试实现,那么只需为此目的创建一组新的测试。

    结论:

    使用mock实际上非常实用。如果您愿意,可以从本书Art of unit testing

    中学到一些东西