我的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()
会收到正确的字符串。换句话说,我需要控制一个方法的结果,并且可以访问另一个方法的参数。
我对所有这些Mock / Stub / Spy的东西仍然非常不稳定。
在我看来,我需要在Spy
,MyClass
stub
CreateHelper()
上使用"手动"创建了对象,但不确定截取messageHelper()
的调用参数的内容。
我还注意到Wiki警告不要使用间谍:
使用此功能前请三思。改变可能会更好 规范下的代码设计。
那么适当的 Spocky 完成任务的方式是什么?
略有重构的代码: (5/5/14)
public MyClass {
// some class variables
private messageSevice = new messageService();
...
private void process() {
private MyObject obj;
...
obj = new MyObject(parameters ...);
...
if (someCondition) {
messageService.produceMessageOne(obj);
}
...
if (otherCondition) {
messageService.produceMessageTwo(obj);
{
...
}
}
public class MessageService implements IMessageService {
private final static MSG_ONE = "...";
private final static MSG_TWO = "...";
...
public void produceMessageOne(MyObject obj) {
produceMessage(obj, MSG_ONE);
...
}
public void produceMessageOne(MyObject obj) {
produceMessage(obj, MSG_TWO);
}
private void produceMessage(MyObject obj, String message) {
...
}
}
如果有人建议用 Spock 测试它的方式,我将不胜感激。
答案 0 :(得分:3)
你所指的谨慎是正确的。可测试代码与优秀设计之间存在非常好的相关性(我建议观看Michael Feathers的讲座,了解为什么http://www.youtube.com/watch?v=4cVZvoFGJTU)。
使用间谍往往是设计问题的先机,因为它通常来自于使用常规模拟和存根的不可能性。
从您的示例中很难预测,因为您显然使用伪名称,但似乎MyClass
类的设计违反了单一责任原则({{3因为它处理,创建和消息传递(3个职责)。
如果您愿意更改设计,以便处理类(MyClass
)只进行处理,那么您将提供另一个进行创建的类({{1}另一个通过构造函数,setter方法或依赖注入执行消息传递(MyObjectFactory
)的类。
使用这个新设计,您可以创建您正在测试的类的实例(MyObjectMessager
),并传递工厂和消息类的模拟对象。然后,您就可以在两者上验证您想要的任何内容。
看看这个例子(使用Mockito):
MyClass
这是一个Spock示例(未经测试,使用前仔细检查......):
public class MyClassTest {
@Test
public void testThatProcessingMessagesCorrectly() {
MyObject object = mock(MyObject.class);
MyObjectFactory factory = mock(MyObjectFactory.class);
when(factory.createMyObject()).thenReturn(object);
MyObjectMessager messager = mock(MyObjectMessager.class);
MyClass processor = new MyClass(factory, messager);
processor.process();
verify(factory).createMyObject();
verify(messager).message(EXPECTED_MESSAGE_1);
verify(messager).message(EXPECTED_MESSAGE_2);
...
verify(messager).message(EXPECTED_MESSAGE_N);
}
...
}
答案 1 :(得分:0)
我想在这里调用异常规则,并说有时隐藏私人方法 - 需要间谍 - 可以是正确和有用的。
这适用于我们这些认为自己有异常但却能获得通常的#34代码味道的人。参数。
我的例子是一个复杂的参数验证器。请考虑以下事项:
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
}
}
FooMethodArgumentValidator
班?)doThing()
函数的可读性。doThing_complexValidateArgs()
不应公开 doThing()
函数受益于简单调用validateArgs(...)
的可恢复性并保持封装。
我现在需要确定的是,我已经在父级中调用了该函数。我怎样才能做到这一点?如果我错了,请纠正我 - 但为了做到这一点,我需要一个Spy()
。
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)
}