单元测试,验证方法是否未被调用是个好主意
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);
}
}
现在您的断言更容易了 - 您只需确保列表中包含正确数量的元素,以及正确的元素。
重要说明:这些设计所做的是在您的逻辑和副作用之间创建一个接缝。想象一个边界,边界的一侧是复杂的逻辑,易于测试,因为它都是对内存中数据的操作;边界的另一边是难以测试的代码,但是非常简单明了,显然没有任何错误。
假设您要测试以下方法:
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);
}
}
现在您的断言更容易了 - 您只需确保列表中包含正确数量的元素,以及正确的元素。
重要说明:这些设计所做的是在您的逻辑和副作用之间创建一个接缝。想象一个边界,边界的一侧是复杂的逻辑,易于测试,因为它都是对内存中数据的操作;边界的另一边是难以测试的代码,但是非常简单明了,显然没有任何错误。