使用 spock 框架测试状态机

Test a state machine using spock framework

我在 Java 中构建了一个状态机,我正在尝试使用 spock 框架对输入可以进行转换的所有可能状态组合进行单元测试。我更喜欢捕获数据 table 中的可能组合,但我面临着迭代值列表作为数据 table 中输入的挑战。请参考以下测试:

@Unroll("#current state can successfully transit to #next")
def "Validate the successful transition of the state machine"() {
    given:
    when:
    def result = current.canTransitionTo(next)
    then:
    result == true

    where:
    current | next
    STATE_A | STATE_B // Works 
    STATE_A | STATE_C // Works
    STATE_A | [STATE_B, STATE_C] // This approach won't work since canTransitionTo() is not expecting an arraylist
}

前两种方法有效,但是我正在寻找一种方法使第三种方法在 spock 框架需要迭代所有可能的下一个值的情况下起作用,因为 canTransitionTo() 被设计为采用单一状态而不是arraylist 并验证结果。

有没有人做过类似的事情。我对 spock 有点陌生,但想了解是否可以测试第三种方法,否则请建议一种更好的方法来测试这个状态机

就像你说的:

This approach won't work since canTransitionTo() is not expecting an arraylist

因此,您需要确保遍历列表。多亏了 Groovy 的好意,你有了方便的方法。 next.every { current.canTransitionTo(it) }怎么样?

package de.scrum_master.Whosebug.q68674245

import spock.lang.Specification
import spock.lang.Unroll

import static de.scrum_master.Whosebug.q68674245.StateMachineTest.StateMachine.*

class StateMachineTest extends Specification {
  @Unroll("#current state can successfully transit to #next")
  def "Validate the successful transition of the state machine"() {
    expect:
    next.every { current.canTransitionTo(it) }

    where:
    current | next
    STATE_A | STATE_B
    STATE_A | STATE_C
    STATE_A | [STATE_B, STATE_C]
    START   | [STATE_A, STATE_B, STATE_C]
    STATE_C | [STATE_B, START]  // fails, because START is a forbidden target state
  }

  static enum StateMachine {
    START, STATE_A, STATE_B, STATE_C

    boolean canTransitionTo(StateMachine stateMachine) {
      println "transition $this -> $stateMachine"
      return stateMachine != START
    }
  }
}

你甚至可以走得更远,在转换的两端都允许迭代:

package de.scrum_master.Whosebug.q68674245

import spock.lang.Specification
import spock.lang.Unroll

import static de.scrum_master.Whosebug.q68674245.StateMachineTest.StateMachine.*

class StateMachineTest extends Specification {
  @Unroll("#current can transit to #next")
  def "Validate successful transition of the state machine"() {
    expect:
    current.every { from -> next.every { to -> from.canTransitionTo(to) } }

    where:
    current                            | next
    [START, STATE_A, STATE_B, STATE_C] | [STATE_A, STATE_B, STATE_C]
  }

  @Unroll("#current must not transit to #next")
  def "Validate failed state machine transition"() {
    expect:
    current.every { from -> next.every { to -> !from.canTransitionTo(to) } }

    where:
    current                            | next
    [START, STATE_A, STATE_B, STATE_C] | START
  }

  static enum StateMachine {
    START, STATE_A, STATE_B, STATE_C

    boolean canTransitionTo(StateMachine stateMachine) {
      println "transition $this -> $stateMachine"
      return stateMachine != START
    }
  }
}

P.S.: 在上面的例子中,如果数据表只包含单行,你实际上不需要 where 块。但我假设你的状态机比我的例子复杂一点,你需要指定更多不同的情况。

您还可以使用 combinations() 生成您想要的不同组合。

class ASpec extends Specification {
    def "can transition"() {
        expect: true
        where:
        [from, to] << 
            [["STATE_A"], ["STATE_B", "STATE_C"]].combinations() + 
            [["STATE_B", "STATE_C"], ["STATE_C", "STATE_D"]].combinations()
    }
}

这将产生

╷
└─ Spock ✔
   └─ ASpec ✔
      └─ can transition ✔
         ├─ can transition [from: STATE_A, to: STATE_B, #0] ✔
         ├─ can transition [from: STATE_A, to: STATE_C, #1] ✔
         ├─ can transition [from: STATE_B, to: STATE_C, #2] ✔
         ├─ can transition [from: STATE_C, to: STATE_C, #3] ✔
         ├─ can transition [from: STATE_B, to: STATE_D, #4] ✔
         └─ can transition [from: STATE_C, to: STATE_D, #5] ✔

您可以在 groovy webconsole 中在线试用。