单元测试,验证方法是否未被调用是个好主意

Unit testing, is it a good idea to verify methods are not called

假设您要测试以下方法:

public void foo(object myObject, bool myBool)
{
     if(myBool)
        repositoryA.save(myObject)
     else
        repositoryB.save(myObject)
}

对此类功能进行单元测试的最佳方法是什么?如果你写了 2 个测试 断言当 myBool 为真时调用 repositoryA,当 myBool 为假时调用 repositoryB,那么对函数的以下更改仍会使测试通过,但可能会破坏应用程序的功能:

public void foo(object myObject, bool myBool)
{
    repositoryA.save(myObject)
    repositoryB.save(myObject)
}

另一方面,如果您断言当 myBool 为真时会调用 repositoryA 并且不会调用 repositoryB,这会让您更加确信对函数的任何更改都不会引入错误,但是您有一个测试取决于实现细节。最好的方法是什么?

如果您使用 TDD,您会编写哪些测试以达到所需的功能?

坚持你的问题(请同时阅读我的​​评论,一般来说可能更有价值),我会写这样的东西:(使用 JMock,一个 Java 模拟库)

// with myBool == true

context.checking(new Expectations(){{
    oneOf(repositoryA).save(object);
    never(repositoryB);
}});

underTest.foo(object, true);




// with myBool == false

context.checking(new Expectations(){{
    never(repositoryA);
    oneOf(repositoryB).save(object);
}});

underTest.foo(object, false);

What is the best way to unit test such a function?

从重新设计开始?

首先开发测试的部分要点是声称可测试接口也是"better";更容易消费,更容易维护。

所以您(正确地)提出关于测试此方法的副作用的要点是 "design smell" -- 这暗示也许此代码设计不是您的用例所需的代码设计。

两种可能性:

一个是代码试图告诉你你有一个遥测需求;你应该能够查询被测系统并了解有多少对象已保存到每个存储库,或者最后保存到每个存储库的对象是什么,或者类似的东西。

然后您可以利用遥测来编写您的测试

Given:
    telemetry reports that repository B has stored 7 objects
    and myBool is true
When:
    foo()
Then:
    telemetry reports that repository B has stored 7 objects

这基本上把问题分成两部分;确保遥测准确报告存储库保存对象的次数的一组测试,然后是假定遥测正常工作的 foo() 测试。

第二种选择:测试试图告诉你,你希望能够评估程序中发生了哪些副作用。因此,让这些效果成为您设计中的第一个 class 公民,并编写检查效果的测试。

List<Effect> foo () {
    if (myBool) {
        return List.of(SaveInRepositoryA);
    } else {
        return List.of(SaveInRepositoryB);
    }
}

现在您的断言更容易了 - 您只需确保列表中包含正确数量的元素,以及正确的元素。

重要说明:这些设计所做的是在您的逻辑和副作用之间创建一个接缝。想象一个边界,边界的一侧是复杂的逻辑,易于测试,因为它都是对内存中数据的操作;边界的另一边是难以测试的代码,但是非常简单明了,显然没有任何错误。