Spock Unroll 似乎用布尔参数打印了一些奇怪的东西
Spock Unroll seems to print something odd with boolean parameter
我只是把这个测试方法放在一起:
@Unroll
def 'super start edit should be called if cell is not empty'( boolean empty ){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty | _
true | _
false | _
}
... 就两个测试通过而言它工作正常...但是当你让它失败时它很奇怪:如果参数 empty
是 false
的输出是
super start edit should be called if cell is not empty[1]
...如果参数empty
为true
则为0。这是一个错误吗?
不,这不是错误
您没有告诉 spock 如何以不同的方式命名您的测试,因此它将迭代(0 然后 1)添加到名称中
改为
@Unroll
def 'super start edit should be called if isEmpty for the cell returns #empty'(){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty << [true, false]
}
我更改了 where
部分,因为 table 一栏感觉很奇怪
编辑
您也可以这样做:
@Unroll
def 'super start edit should be called if the cell is #msg'(){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty << [true, false]
msg = empty ? 'empty' : 'not empty'
}
我正在写一个额外的答案,因为
- Tim 的解决方案中有一个关于标题的小错误(但他的回答在技术上仍然是绝对正确的!),
- 这里不需要
GroovySpy
,简单的Spy
就足够了,
- 我想向您展示另一种无需存根的测试方法
isEmpty()
,
- 我想向您展示如何在三元表达式中仅使用一次与调用次数的交互,而不是 if-else(尽管那时错误报告很难看),
- 我想对你的一般测试方式发表评论(见本文结尾post)。
package de.scrum_master.Whosebug.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.Whosebug.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'
}
}
一般备注:
- 我不会通过交互测试单个 class 的内部接线。测试将很脆弱,如果您在内部重构 class 而根本不更改 API,则如果交互不再符合预期,测试可能会中断。我认为这是 over-specification,我只会对不同 class 之间的关键交互或一个 class - "crucial" 的不同实例之间的关键交互使用交互测试,这意味着诸如像 Observer 这样的设计模式。
- 如果整个测试只确切地知道这两种情况,那么使用 if-else 通过两种不同的交互模式区分两种情况只会降低测试的可读性和复杂性,请参阅您自己的代码以及我的代码和蒂姆的。在这种情况下,我宁愿编写两个具有简单标题和简单功能的特征方法,但没有 if-else 或三元表达式,没有标题等的辅助变量
P.S.: 抱歉,我不得不在测试 DueDateEditor
下制作一个示例 class 以使我的测试编译和 运行 符合预期。和往常一样,遗憾的是 Mike 没有提供 MCVE 而只是其中的一部分。
更新: 我们在评论中谈到了 GroovySpy
,正如我所说,如果您的 class 是 Java classes 还有一个你想要存根的最终方法,请参阅 Spock manual。这是给你的证明:
package de.scrum_master.Whosebug.q61032514;
public class TreeTableCell<A, B> {
String text;
public final boolean isEmpty() {
return text == null || text.trim() == "";
}
}
package de.scrum_master.Whosebug.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.Whosebug.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'
}
}
如果您的应用程序 classes 仅是 Groovy classes,则测试将有效。但是如果它们像我的示例中那样 Java class ,测试将像这样失败:
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 手册 link 解释 method unrolling 以及如何使用 where:
块中的变量影响命名。
我只是把这个测试方法放在一起:
@Unroll
def 'super start edit should be called if cell is not empty'( boolean empty ){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty | _
true | _
false | _
}
... 就两个测试通过而言它工作正常...但是当你让它失败时它很奇怪:如果参数 empty
是 false
的输出是
super start edit should be called if cell is not empty[1]
...如果参数empty
为true
则为0。这是一个错误吗?
不,这不是错误
您没有告诉 spock 如何以不同的方式命名您的测试,因此它将迭代(0 然后 1)添加到名称中
改为
@Unroll
def 'super start edit should be called if isEmpty for the cell returns #empty'(){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty << [true, false]
}
我更改了 where
部分,因为 table 一栏感觉很奇怪
编辑
您也可以这样做:
@Unroll
def 'super start edit should be called if the cell is #msg'(){
given:
DueDateEditor editor = GroovySpy( DueDateEditor ){
isEmpty() >> empty
}
when:
editor.startEdit()
then:
if( empty){
0 * editor.callSuperStartEdit()
}
else {
1 * editor.callSuperStartEdit()
}
where:
empty << [true, false]
msg = empty ? 'empty' : 'not empty'
}
我正在写一个额外的答案,因为
- Tim 的解决方案中有一个关于标题的小错误(但他的回答在技术上仍然是绝对正确的!),
- 这里不需要
GroovySpy
,简单的Spy
就足够了, - 我想向您展示另一种无需存根的测试方法
isEmpty()
, - 我想向您展示如何在三元表达式中仅使用一次与调用次数的交互,而不是 if-else(尽管那时错误报告很难看),
- 我想对你的一般测试方式发表评论(见本文结尾post)。
package de.scrum_master.Whosebug.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.Whosebug.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'
}
}
一般备注:
- 我不会通过交互测试单个 class 的内部接线。测试将很脆弱,如果您在内部重构 class 而根本不更改 API,则如果交互不再符合预期,测试可能会中断。我认为这是 over-specification,我只会对不同 class 之间的关键交互或一个 class - "crucial" 的不同实例之间的关键交互使用交互测试,这意味着诸如像 Observer 这样的设计模式。
- 如果整个测试只确切地知道这两种情况,那么使用 if-else 通过两种不同的交互模式区分两种情况只会降低测试的可读性和复杂性,请参阅您自己的代码以及我的代码和蒂姆的。在这种情况下,我宁愿编写两个具有简单标题和简单功能的特征方法,但没有 if-else 或三元表达式,没有标题等的辅助变量
P.S.: 抱歉,我不得不在测试 DueDateEditor
下制作一个示例 class 以使我的测试编译和 运行 符合预期。和往常一样,遗憾的是 Mike 没有提供 MCVE 而只是其中的一部分。
更新: 我们在评论中谈到了 GroovySpy
,正如我所说,如果您的 class 是 Java classes 还有一个你想要存根的最终方法,请参阅 Spock manual。这是给你的证明:
package de.scrum_master.Whosebug.q61032514;
public class TreeTableCell<A, B> {
String text;
public final boolean isEmpty() {
return text == null || text.trim() == "";
}
}
package de.scrum_master.Whosebug.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.Whosebug.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'
}
}
如果您的应用程序 classes 仅是 Groovy classes,则测试将有效。但是如果它们像我的示例中那样 Java class ,测试将像这样失败:
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 手册 link 解释 method unrolling 以及如何使用 where:
块中的变量影响命名。