【问题标题】:Applying argument constraints with Spock tests使用 Spock 测试应用参数约束
【发布时间】:2020-03-03 21:28:35
【问题描述】:

我已经为一些 Java 类编写了 Spock 测试。当我尝试验证参数中使用的确切对象时,测试失败。

    def "all events are published to subscribers"() {
        given:
        EventSubscriber subscriber = Mock()
        when:
        subscriber.shouldRegister() >> true
        subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
        publisher.subscribe(subscriber)
        def event = publisher.newEvent("test", COUNTER)
        event.start()
        def child = event.newChild("child", GAUGE)
        event.finish()
        then:
//        2 * subscriber.onCreate(event)
//        1 * subscriber.onStart(event, event.start)
//        1 * subscriber.onEnd(event, event.end)
//        1 * subscriber.onChild(event, child)
        2 * subscriber.onCreate(_)
        1 * subscriber.onStart(_, _)
        1 * subscriber.onEnd(_, _)
        1 * subscriber.onChild(_, _)
    }

注释掉的行失败

groovy.lang.MissingPropertyException:没有这样的属性:事件 类:io.hypi.faf.api.EventPublisherTest

在 io.hypi.faf.api.EventPublisherTest.所有事件都发布到 订阅者(EventPublisherTest.groovy:28)

http://spockframework.org/spock/docs/1.3/all_in_one.html#_argument_constraints 似乎暗示我可以传入一个值。第一个示例是字符串,但我认为类型无关紧要。

我做错了什么?

斯波克:1.3 时髦:2.5 groovy-test-junit5-2.5.7 JDK:11

【问题讨论】:

    标签: java groovy spock


    【解决方案1】:

    交互测试中使用的对象实例应在given 部分中定义。

    这会奏效:

    def 'all events are published to subscribers'() {
        given:
        EventSubscriber subscriber = Mock()
        subscriber.shouldRegister() >> true
        subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
        publisher.subscribe(subscriber)
        def event = publisher.newEvent('test', COUNTER)
        def child = event.newChild('child', GAUGE)
    
        when:
        event.start()
        event.finish()
    
        then:
        2 * subscriber.onCreate(event)
        1 * subscriber.onStart(event, event.start)
        1 * subscriber.onEnd(event, event.end)
        1 * subscriber.onChild(event, child)
    }
    

    但是测试onCreate()onChild() 的调用会出现问题,因为这些调用不在when 部分中,只有onStart()onEnd() 可以匹配。

    我没有你的类定义来查看详细信息,但我猜 event.startevent.end 字段在 start()finish() 方法中初始化,然后 1 * onStart(event, event.start)1 * onEnd(event, event.end) 测试将不匹配因为调用测试将在event 具有不同startend 值的阶段进行。那么只有 1 * onStart(event, _)1 * onEnd(event, _) 测试才会匹配。


    看看这个简单的例子,它与你的情况相似,但不同之处在于event 实例是在given 部分中准备的,而不是在when 中修改的,因此它将通过测试:

    class InvocationWithArgumentsSpec extends Specification {
        void 'Invocation test works with arguments other then String'() {
            given:
            def subscriber = Mock(Subscriber)
            def simplePublisher = new Publisher()
            simplePublisher.subscribe(subscriber)
            def event = new SimpleEvent(name: 'test')
    
            when:
            simplePublisher.fireEvent(event)
    
            then:
            1 * subscriber.onEvent(event)
        }
    }
    
    interface Subscriber {
        void onEvent(SimpleEvent event)
    }
    
    class Publisher {
        Subscriber subscriber
    
        void subscribe(Subscriber subscriber) {
            this.subscriber = subscriber
        }
    
        void fireEvent(SimpleEvent event) {
            subscriber.onEvent(event)
        }
    }
    
    class SimpleEvent {
        String name
    }
    

    更新:但是有一个解决方法。它不是很好,但它可以工作并测试正确的事件是否传递给订阅者方法(我猜event 有类似name 的东西):

    def 'all events are published to subscribers'() {
        given:
        EventSubscriber subscriber = Mock()
        subscriber.shouldRegister() >> true
        subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
        publisher.subscribe(subscriber)
    
        when:
        def event = publisher.newEvent('test', COUNTER)
        event.start()
        event.newChild('child', GAUGE)
        event.finish()
    
        then:
        1 * subscriber.onCreate({ it.name == 'test' })
        1 * subscriber.onCreate({ it.name == 'child' })
        1 * subscriber.onStart({ it.name == 'test' }, !null)
        1 * subscriber.onEnd({ it.name == 'test' }, !null)
        1 * subscriber.onChild({ it.name == 'test' }, { it.name == 'child' })
    }
    

    【讨论】:

    • 啊啊啊,我没有意识到交互模拟中使用的实例必须在给定的块中定义。我认为没有办法在不更改 API 和行为的情况下使用 spock 进行模拟,所以现在我已经跳过模拟并创建了一个匿名实例,只捕获对象和计数。第一次使用 Spock,因为它是一个新项目,我想我会用它来测试和模拟,但可能会回到使用 Mockito 和 spock 来做其他事情。
    • 是的,这是 Spock 的一个限制 :-( 但是您可以尝试我现在​​添加到答案中的解决方法。闭包测试了一些参数。
    • 谢谢。我喜欢。我非常热衷于使用 spock,自从我们开始采用它以来,它为我们节省了很多时间,所以知道一种解决方法是有益的。
    • @cgrim 只是一个快速的改进 { e -&gt; e.name == 'test' } -> {ìt.name == 'test'}{ start -&gt; start != null } -> !null 让它变得不那么吵了。
    • 谢谢@LeonardBrünings 我相应地更新了答案。我仍然以 Java 方式思考,而不是 Groovy 方式 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多