我正在写一个额外的答案,因为
- 蒂姆的解决方案中有一个关于标题的小错误(但他的回答在技术上仍然是绝对正确的!),
- 你不需要
GroovySpy
这里,一个简单的Spy
绝对足够了,
- 我想向您展示另一种无需存根的测试方法
isEmpty()
,
- 我想向您展示如何在三元表达式而不是 if-else 中仅使用一次交互与调用次数(即使错误报告很丑陋),
- 我想对您的一般测试方式发表评论(请参阅本文末尾)。
package de.scrum_master.stackoverflow.q61032514;
import java.time.LocalDate;
public class DueDateEditor {
String text;
public boolean isEmpty() {
return text == null || text.trim() == "";
}
public void startEdit() {
if (!isEmpty())
callSuperStartEdit();
}
public void callSuperStartEdit() {}
}
package de.scrum_master.stackoverflow.q61032514
import spock.lang.Specification
import spock.lang.Unroll
class DueDateEditorTest extends Specification {
@Unroll
def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
given:
DueDateEditor editor = Spy() {
isEmpty() >> empty
}
when:
editor.startEdit()
then:
(empty ? 0 : 1) * editor.callSuperStartEdit()
where:
empty << [true, false]
shouldMsg = empty ? 'should not' : 'should'
cellStateMsg = empty ? 'empty' : 'not empty'
}
@Unroll
def "super start edit #shouldMsg be called if cell text is '#text'"() {
given:
DueDateEditor editor = Spy()
editor.text = text
when:
editor.startEdit()
then:
(editor.isEmpty() ? 0 : 1) * editor.callSuperStartEdit()
// Or, if 'isEmpty()' has a side effect:
// (text ? 1 : 0) * editor.callSuperStartEdit()
where:
text << ["foo", "", null, "line 1\nline 2"]
shouldMsg = text ? 'should' : 'should not'
cellStateMsg = text ? 'not empty' : 'empty'
}
}
一般说明:
- 我不会通过交互来测试单个类的内部布线。测试会很脆弱,如果您在内部重构类而不更改 API,如果交互不再符合预期,测试可能会中断。我认为这是过度规范,我只会对不同类或一个类的不同实例之间的关键交互使用交互测试——“关键”意味着像观察者这样的设计模式的功能。
- 如果整个测试只确切地知道这两种情况,那么有一个 if-else 用于通过两种不同的交互模式区分两种情况只会使测试的可读性降低且更复杂,请查看您自己的代码以及我的和 Tim 的代码。在这种情况下,我宁愿编写两个具有简单标题和简单功能的特征方法,但没有 if-else 或三元表达式,没有标题的辅助变量等。
PS:对不起,我必须制作一个正在测试的示例类DueDateEditor
,以使我的测试按预期编译和运行。像往常一样,不幸的是,Mike 没有提供MCVE,而只是提供了其中的一部分。
更新:我们在评论中谈到了GroovySpy
,正如我所说,如果你的类是 Java 类并且你想要存根有一个 final 方法,那么它将不起作用,请参阅Spock 手册。这是给你的证据:
package de.scrum_master.stackoverflow.q61032514;
public class TreeTableCell<A, B> {
String text;
public final boolean isEmpty() {
return text == null || text.trim() == "";
}
}
package de.scrum_master.stackoverflow.q61032514;
import java.time.LocalDate;
public class DueDateEditor extends TreeTableCell<String, LocalDate> {
public void startEdit() {
if (!isEmpty())
callSuperStartEdit();
}
public void callSuperStartEdit() {}
}
package de.scrum_master.stackoverflow.q61032514
import spock.lang.Specification
import spock.lang.Unroll
class DueDateEditorTest extends Specification {
@Unroll
def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
given:
DueDateEditor editor = GroovySpy() {
isEmpty() >> empty
}
when:
editor.startEdit()
then:
(empty ? 0 : 1) * editor.callSuperStartEdit()
where:
empty << [true, false]
shouldMsg = empty ? 'should not' : 'should'
cellStateMsg = empty ? 'empty' : 'not empty'
}
}
如果您的应用程序类仅是 Groovy 类,则该测试将起作用。但如果它们是我的示例中的 Java 类,则测试将失败,如下所示:
Too few invocations for:
(empty ? 0 : 1) * editor.callSuperStartEdit() (0 invocations)
Unmatched invocations (ordered by similarity):
1 * editor.startEdit()
methodName == "callSuperStartEdit"
| |
startEdit false
10 differences (44% similarity)
(s---------)tartEdit
(callSuperS)tartEdit
所以在这种情况下,您不能只使用 Groovy 魔法来检查交互。但正如我所说,无论如何你都不应该那样做。而是确保两者都startEdit()
做callSuperStartEdit()
正确的事情。检查他们的结果,或者,如果他们是void
,检查他们对被测对象或其合作者状态的副作用。
更新 2:关于您关于索引方法命名的原始问题,实际上@tim_yates给出了正确答案。我只想添加相应的 Spock 手动链接,解释方法展开以及如何使用where:
块中的变量影响命名。