Java 如何测试一个方法和它的两个助手之间的交互作用?
我的Java 如何测试一个方法和它的两个助手之间的交互作用?,java,unit-testing,groovy,spock,Java,Unit Testing,Groovy,Spock,我的Java代码的结构如下: public MyClass { // some class variables ... private void process() { private MyObject obj; ... obj = createHelper(); ... messageHelper(obj, "One of several possible strings");
Java
代码的结构如下:
public MyClass {
// some class variables
...
private void process() {
private MyObject obj;
...
obj = createHelper();
...
messageHelper(obj, "One of several possible strings");
...
messageHelper(obj, "Another call with a different string");
...
}
private MyObject createHelper {
MyObject obj = new MyObject();
// some Setter calls
...
return obj;
}
private void messageHelper (MyOject obj, String message) {
...
}
}
我想测试,基于属性obj
(我想指定),messageHelper()
接收正确的字符串。换句话说,我需要控制一种方法的结果,并访问另一种方法的参数
我仍然对所有这些模仿/存根/间谍的东西感到非常不安
在我看来,我需要对MyClass
,stub
CreateHelper()
使用“手动”创建的对象进行Spy
,但不确定拦截messageHelper()
调用参数的目的是什么
我还注意到对使用间谍的警告:
使用此功能前请仔细考虑。改变一下可能更好
规范下的代码设计
那么,用什么合适的斯波克方式来完成这项任务呢
稍微重构代码:(5/5/14)
如果有人建议如何使用Spock进行测试,我将不胜感激。您提到的注意事项就在这里。可测试代码和良好设计之间有着很好的相关性(我建议看一下MichaelFeathers的这节课来了解原因) 使用spies往往是设计问题的先兆,因为它通常是由于无法使用常规的模拟和存根而产生的 从您的示例中很难预测,因为您显然使用了伪名称,但是
MyClass
类的设计似乎违反了单一责任原则(),因为它执行处理、创建和消息传递(3个责任)
如果您愿意更改设计,以便处理类(MyClass
)只执行处理,那么您将提供另一个创建类(MyObjectFactory
),以及另一个通过构造函数执行消息传递的类(MyObjectMessager
),setter方法或依赖项注入
使用这种新设计,您可以创建正在测试的类的实例(MyClass
),并将工厂类和消息传递类的模拟对象传递给它。然后你就可以验证你想要的任何东西
看看这个例子(使用Mockito):
下面是一个Spock示例(未经测试,使用前请仔细检查…):
如果你是一把锤子,每个问题都是一颗钉子
我想在这里把这个规则称为例外,并说有时候存根私有方法——需要间谍——既正确又有用
@埃坦法尔对函数的分析很有可能是准确的,95%的情况下是这样,但就像大多数事情一样——我相信——并不总是如此
这是为我们这些相信他们有一个例外,但得到通常的“代码气味”的论点
我的示例是一个复杂的参数验证器。考虑以下事项:
class Foo {
def doThing(...args) {
doThing_complexValidateArgs(args)
// do things with args
}
def private doThing_complexValidateArgs(...args) {
// ... * 20 lines of non-logic-related code that throws exceptions
}
}
- 将验证器放在它自己的类IMO中会使问题变得过于独立。(a
class?)FooMethodArgumentValidator
- 重构验证可以显著提高
函数的可读性doThing()
不应公开doThing\u complexValidateArgs()
doThing()
函数受益于一个简单调用的可靠性validateArgs(…)
,并维护封装
现在我需要确定的是,我已经在父函数中调用了该函数。我该怎么做?好吧-如果我错了,请纠正我-但为了做到这一点,我需要一个Spy()
下面是我用于静态私有方法的实际示例:
@SuppressWarnings("GroovyAccessibility")
@ConfineMetaClassChanges(DateService) // stops a global GroovySpy from affecting other tests by reseting the metaclass once done.
void "isOverlapping calls validateAndNormaliseDateList() for both args" () {
List list1 = [new Date(1L), new Date(2L)]
List list2 = [new Date(2L), new Date(3L)]
GroovySpy(DateService, global: true) // GroovySpy allows for global replacement. see `org.spockframework.mock.IMockConfiguration#isGlobal()`
when:
DateService.isOverlapping(list1, list2)
then:
1 * DateService.isOverlapping_validateAndNormaliseDateList('first', list1) // groovy 2.x currently allows private method calls
1 * DateService.isOverlapping_validateAndNormaliseDateList('second', list2)
}
是的,我认为创建一个单独的
MessageService
类是一个好主意,该类对每种类型的消息都有一个单独的方法。我不相信我需要工厂,因为只要我添加必要的覆盖构造函数并传递它们所需的参数,MyObject
就可以创建自己的实例。messageOne()
和messageTwo()
的MyClass
方法做什么?他们只是调用IMessageService
类型的成员变量的方法吗?顺便说一句,我完全没有注意到你想要一个Spock解决方案,在你回答这些问题后,我会尝试摆脱我的Spock技能,在Spock中写一个例子。对不起。忘记保存我的编辑。这些方法属于MessageService
。现在它似乎是自描述性的。我添加了一个Spock示例。您的类仍然需要一些重构,因为此时它创建了它使用的所有实例(即MyObject
和MessageService
),而不是从外部获取它们。我写《斯波克》已经有一段时间了,所以请相应地对待它:-)
public class MyClassSpec extends Specification {
def "check that the right messages are produced with the expected object"() {
given:
def messageService = Mock(IMessageService)
def testedInstance = new MyClass()
testedInstance.setMessageService(messageService)
when:
testedInstance.process()
then:
1 * messageService.produceMessageOne(_)
1 * messageService.produceMessageTwo(_)
}
}
class Foo {
def doThing(...args) {
doThing_complexValidateArgs(args)
// do things with args
}
def private doThing_complexValidateArgs(...args) {
// ... * 20 lines of non-logic-related code that throws exceptions
}
}
class FooSpec extends Specification {
class Foo {
def doThing(...args) {
doThing_controlTest(args)
doThing_complexValidateArgs(*args)
// do things with args
}
def doThing_controlTest(args) {
// this is a test
}
def private doThing_complexValidateArgs(...args) {
// ... * 20 lines of code
}
}
void "doThing should call doThing_complexValidateArgs" () {
def fooSpy = Spy(Foo)
when:
fooSpy.doThing(1, 2, 3)
then:
1 * fooSpy.doThing_controlTest([1,2,3]) // to prove to ya'll we got into the right method
1 * fooSpy.invokeMethod('doThing_complexValidateArgs', [1, 2, 3]) // probably due to groovy weirdness, this is how we test this call
}
}
@SuppressWarnings("GroovyAccessibility")
@ConfineMetaClassChanges(DateService) // stops a global GroovySpy from affecting other tests by reseting the metaclass once done.
void "isOverlapping calls validateAndNormaliseDateList() for both args" () {
List list1 = [new Date(1L), new Date(2L)]
List list2 = [new Date(2L), new Date(3L)]
GroovySpy(DateService, global: true) // GroovySpy allows for global replacement. see `org.spockframework.mock.IMockConfiguration#isGlobal()`
when:
DateService.isOverlapping(list1, list2)
then:
1 * DateService.isOverlapping_validateAndNormaliseDateList('first', list1) // groovy 2.x currently allows private method calls
1 * DateService.isOverlapping_validateAndNormaliseDateList('second', list2)
}