0

我只是把这个测试方法放在一起:

@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为,则为 0 true。这是一个错误吗?

4

2 回答 2

2

我正在写一个额外的答案,因为

  • 蒂姆的解决方案中有一个关于标题的小错误(但他的回答在技术上仍然是绝对正确的!),
  • 你不需要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:块中的变量影响命名。

于 2020-04-05T03:36:35.810 回答
1

不,这不是错误

您没有告诉 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部分,因为只有一列的表格感觉很奇怪

编辑

你也可以这样做:

@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'
}
于 2020-04-04T20:01:24.587 回答