Условное условие не работает должным образом

#groovy #spock

Вопрос:

Я заметил странное поведение в том, как оценивается состояние в then блоке. Условие должно быть оценено после выполнения when блока, но в одном конкретном случае оно выполняется раньше и поэтому не выполняется. Я могу воспроизвести поведение с помощью приведенной ниже простой спецификации:

 import spock.lang.Specification

class ConditionalCondition extends Specification {
    def 'non-working condition check'() {
        given:
            def result = 0

        when:
            System.out.println("----- before: ${result}")
            result = 1
            System.out.println("----- after: ${result}")

        then:
            if (true) {
                assert result == 1
            }
            else {
                [Mock(Closure)].each {
                    it.call() >> ""
                }
            }
    }

    def 'working condition check'() {
        given:
            def result = 0

        when:
            System.out.println("----- before: ${result}")
            result = 1
            System.out.println("----- after: ${result}")

        then:
            if (true) {
                assert result == 1
            }
            if (false) {
                [Mock(Closure)].each {
                    it.call() >> ""
                }
            }
    }
}
 

Если вы запустите вышеописанное, вы увидите вывод, который выглядит примерно так:

 ConditionalCondition > non-working condition check FAILED
    org.spockframework.runtime.SpockComparisonFailure at ConditionalCondition.groovy:15

ConditionalCondition > working condition check STANDARD_OUT
    ----- before: 0
    ----- after: 1
 

Как вы можете видеть, даже вывод консоли не отображается, так как код в then блоке никогда не выполняется. И отчет выглядит так, еще раз подтверждая тот же факт:

   non-working condition check

 Condition not satisfied:

 result == 1
 |      |
 0      false

         at ConditionalCondition.non-working condition check(ConditionalCondition.groovy:15)

Tests

   Test                        Duration Result
   non-working condition check 0.350s   failed
   working condition check     0.030s   passed
 

Единственное различие между этими двумя тестами заключается в условии, описанном ниже в коде:

                 [Mock(Closure)].each {
                    it.call() >> ""
                }
 

Сам код не делает ничего полезного для теста, но когда код является else блоком, он не работает, но если он находится в отдельном if состоянии, он работает нормально. Кроме того, если я удалю код из блока или полностью удалю код, тест будет работать должным образом.

Кто-нибудь может помочь мне понять это странное поведение?

Ответ №1:

Для этого вам нужно заглянуть под капюшон того, как Спок работает, это магия.

Чтобы сделать такие вещи, как

 def "mock example"() {
        given:
        Receiver receiver = Mock()
        def producer = new Producer(receiver)
        
        when:
        producer.createItem()
    
        then:
        1 * receiver.receive(_) >> true
    }
 

работая, он перемещает все макетные объявления взаимодействия из then блока непосредственно перед кодом when блока.

Вот как выглядит код после преобразования AST.

 @org.spockframework.runtime.model.FeatureMetadata(name = 'mock example', ordinal = 2, line = 43, blocks = [org.spockframework.runtime.model.BlockKind.SETUP[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.WHEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.THEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41], parameterNames = [])
    public void $spock_feature_0_2() {
        Receiver receiver = this.MockImpl('receiver', Receiver)
        java.lang.Object producer = new Producer(receiver)
        this.getSpecificationContext().getMockController().enterScope()
        this.getSpecificationContext().getMockController().addInteraction(new org.spockframework.mock.runtime.InteractionBuilder(52, 9, '1 * receiver.receive(_) >> true').setFixedCount(1).addEqualTarget(receiver).addEqualMethodName('receive').setArgListKind(true, false).addEqualArg(_).addConstantResponse(true).build())
        producer.createItem()
        this.getSpecificationContext().getMockController().leaveScope()
        this.getSpecificationContext().getMockController().leaveScope()
    }
 

Если мы возьмем ваш код, то увидим, что происходит то же самое.

     @org.spockframework.runtime.model.FeatureMetadata(name = 'non-working condition check', ordinal = 0, line = 5, blocks = [org.spockframework.runtime.model.BlockKind.SETUP[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.WHEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.THEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41], parameterNames = [])
    public void $spock_feature_0_0() {
        org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE
        org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder()
        java.lang.Object result = 0
        this.getSpecificationContext().getMockController().enterScope()
        then:
        if (true) {
            try {
                org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'result == 1', 16, 24, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), result) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), 1)))
            } 
            catch (java.lang.Throwable $spock_condition_throwable) {
                org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'result == 1', 16, 24, null, $spock_condition_throwable)} 
            finally { 
            } 
        } else {
            [this.MockImpl(null, null, groovy.lang.Closure)].each({ 
                return this.getSpecificationContext().getMockController().addInteraction(new org.spockframework.mock.runtime.InteractionBuilder(20, 21, 'it.call() >> ""').addEqualTarget(it).addEqualMethodName('call').setArgListKind(true, false).addConstantResponse('').build())
            })
        }
        java.lang.System.out.println("----- before: $result")
        result = 1
        java.lang.System.out.println("----- after: $result")
        this.getSpecificationContext().getMockController().leaveScope()
        this.getSpecificationContext().getMockController().leaveScope()
    }

    @org.spockframework.runtime.model.FeatureMetadata(name = 'working condition check', ordinal = 1, line = 25, blocks = [org.spockframework.runtime.model.BlockKind.SETUP[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.WHEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41, org.spockframework.runtime.model.BlockKind.THEN[]org.codehaus.groovy.ast.AnnotationNode@d9f41], parameterNames = [])
    public void $spock_feature_0_1() {
        org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE
        org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder()
        java.lang.Object result = 0
        java.lang.System.out.println("----- before: $result")
        result = 1
        java.lang.System.out.println("----- after: $result")
        then:
        if (true) {
            try {
                org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'result == 1', 36, 24, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), result) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), 1)))
            } 
            catch (java.lang.Throwable $spock_condition_throwable) {
                org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'result == 1', 36, 24, null, $spock_condition_throwable)} 
            finally { 
            } 
        }
        this.getSpecificationContext().getMockController().leaveScope()
    }

 

Комментарии:

1. В вашем примере addInteraction это не приводит к немедленной оценке, верно? Позвольте мне попробовать провести тот же анализ для моего примера кода и посмотреть, что произойдет.

2. Посмотрел на два декомпилированных метода и понял, что происходит в контексте вашего объяснения. Причина if else сбоя / заключается в том, что Спок перемещает весь блок впереди when блока, потому что он не может перемещать только else блок. Когда взаимодействия находятся в отдельном состоянии if, перемещается только этот блок, и поэтому он работает. Спасибо, что помогли мне понять это, я изначально думал, что это ошибка Спока.

3. Вы наткнулись на один из редких крайних случаев, когда магия Спока не делает того, что вы хотите, и трудно понять, почему без более глубокого знания того, как она работает.