【问题标题】:Data Driven Spock Tests数据驱动的 Spock 测试
【发布时间】:2025-12-25 15:30:15
【问题描述】:

我有以下要测试的课程

package bsg

class NotificationService {

    def create(String publicMsg, String privateMsg = null, Player activePlayer, Player passivePlayer = null) {
        new Notification(
                game: activePlayer.game, publicMessage: publicMsg, privateMessage: privateMsg,
                activePlayer: activePlayer, passivePlayer: passivePlayer
                ).save(failOnError: true)
    }

}

还有下面的测试类

package bsg

import grails.test.mixin.*
import spock.lang.Specification
import spock.lang.Unroll

/**
 * See the API for {@link grails.test.mixin.services.ServiceUnitTestMixin} for usage instructions
 */
@TestFor(NotificationService)
@Mock([Notification, Game, Player])
class NotificationServiceSpec extends Specification {

    NotificationService service

    private static final String HIDDEN_SIDE = "1 - Launch Scout"
    private static Player adama
    private static Player baltar
    private static Game game
    private static Card card
    private static NotificationService notificationService

    def setup() {
        service = new NotificationService()
        game = new Game(name: "foo").save(validate:false)
        card = new Card(hiddenSide: HIDDEN_SIDE, backSide: "Bacon", deck: new Deck(backSide: "Skill Card"))
        adama = new Player(game: game, character:Player.Character.Adama).save(validate: false)
        baltar = new Player(game: game, character:Player.Character.Baltar).save(validate: false)
        notificationService = service
    }

    @Unroll
    def "test create1"(String privateMsg, Player passivePlayer, Closure serviceMethod) {
        when: "I create a notification"
        Notification notification = serviceMethod.call()

        then: "a notification has been persisted"
        notification?.id != null

        and: "it displays the correct messages"
        notification.publicMessage == "foo"
        notification.privateMessage == privateMsg
        notification.activePlayer == adama
        notification.passivePlayer == passivePlayer

        where:
        privateMsg  | passivePlayer | serviceMethod
        "bar"       | baltar        | { notificationService.create("foo", "bar", adama, baltar) }
        null        | baltar        | { notificationService.create("foo", null, adama, baltar) }
        "bar"       | null          | { notificationService.create("foo", "bar", adama) }
        null        | null          | { notificationService.create("foo", adama) }
    }

    @Unroll
    def "test create2"(String privateMsg, Player passivePlayer, Closure serviceMethod) {
        when: "I create a notification"
        Notification notification = serviceMethod.call()

        then: "a notification has been persisted"
        notification?.id != null

        and: "it displays the correct messages"
        notification.publicMessage == "foo"
        notification.privateMessage == privateMsg
        notification.activePlayer == adama
        notification.passivePlayer == passivePlayer

        where:
        privateMsg  | passivePlayer | serviceMethod
        "bar"       | baltar        | { notificationService.create("foo", "bar", adama, baltar) }
        null        | baltar        | { notificationService.create("foo", null, adama, baltar) }
        "bar"       | null          | { notificationService.create("foo", "bar", adama) }
        null        | null          | { notificationService.create("foo", adama) }
    }
}

两个测试是相同的,但在"test create1"() 中,前两个测试失败,后两个测试通过。在"test create2"() 中,所有 4 个测试都通过了。两次失败的错误信息是这样的:

Condition not satisfied:

notification.passivePlayer == passivePlayer
|            |             |  |
|            Baltar (null) |  null
|                          false
Adama (null) foo to Baltar (null)

at bsg.NotificationServiceSpec.test create1(NotificationServiceSpec.groovy:44)

所以看起来静态类变量baltar 在第一次测试中没有设置。有人可以帮助我了解发生了什么吗?

【问题讨论】:

    标签: grails groovy spock


    【解决方案1】:

    嗯,有趣的故事... 我正在参加 NFJS RWX/CDX 会议,在发布这个问题后,我立即与 Peter Niederwieser 一起上了电梯。当我们到达底层时,他已经把我整理好了。

    如果你停下来想一想,那是完全有道理的。我确信我会弄错细节,但我的基本理解是 Spock 在执行 setup() 方法之前从数据表构建实际的 junit 测试方法。值得庆幸的是,使用 setupSpec() 将完成我正在寻找的。​​p>

    所以这就是我修复它的方法。谢谢,彼得。

    ...
    private static final String HIDDEN_SIDE = "1 - Launch Scout"
    private static Player adama
    private static Player baltar
    private static Game game
    private static Card card
    private static NotificationService notificationService
    
    def setup() {
        notificationService = new NotificationService()
    
        game.save(validate:false)
        adama.save(validate: false)
        baltar.save(validate: false)
    }
    
    def setupSpec() {
        game = new Game(name: "foo")
        card = new Card(hiddenSide: HIDDEN_SIDE, backSide: "Bacon", deck: new Deck(backSide: "Skill Card"))
        adama = new Player(game: game, character:Player.Character.Adama)
        baltar = new Player(game: game, character:Player.Character.Baltar)
    }
    ...
    

    彼得说他稍后会继续回答这个问题,我相信他会有更好的解释。希望他有时间;如果他愿意,我会接受他的回答。

    【讨论】: