Спок неясное утверждение поведения - PullRequest
0 голосов
/ 12 декабря 2018

Как я знаю, один из способов проверить результат теста - записать выражения в раздел then, который оценивается как логический.

Однако в последнее время я столкнулся с поведением, которое не понимаю.Кажется, что когда кто-то пытается что-то проверить, то есть в блоке, , то утверждение работает только с явным ключевым словом assert .

Вот пример.Я написал фиктивный оператор if, чтобы иметь блок, но то же самое с циклом for или любым потоком управления.

def "test fails as expected"() {
    when: "result has some value"
    def result = "someValue"

    then: "result has the expected value"
    result == "otherValue"
}

def "test passes, but shouldn't"() {
    when: "result has some value"
    def result = "someValue"

    then: "result has the expected value"
    if (true) {
        result == "otherValue"
    }
}

def "test fails as expected when using assert"() {
    when: "result has some value"
    def result = "someValue"

    then: "result has the expected value"
    if (true) {
        assert result == "otherValue"
    }
}

Я считаю, что это поведение немного вводит в заблуждение.Может кто-нибудь объяснить, почему это так работает?Это ошибка или использование некорректно?

1 Ответ

0 голосов
/ 13 декабря 2018

После Документация Спока :

Блоки when и then всегда встречаются вместе.Они описывают стимул и ожидаемый ответ.В то время как when блоки могут содержать произвольный код, then блоки ограничены условиями , условиями исключения , взаимодействиями и переменнойопределения . Метод объекта может содержать несколько пар блоков when-then.

Это объясняет, почему преобразователь Spocks AST не видит следующий блок then:

then:
if (true) {
    result == "otherValue"
}

как правильный, и он не преобразует его в SpockRuntime.verifyCondition() вызов.

Если вы скомпилируете свой класс (с включенной статической компиляцией для лучшей читаемости) и проверите байт-код, вы увидите нечто похожее на это:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.spockframework.runtime.ErrorCollector;
import org.spockframework.runtime.SpockRuntime;
import org.spockframework.runtime.ValueRecorder;
import org.spockframework.runtime.model.BlockKind;
import org.spockframework.runtime.model.BlockMetadata;
import org.spockframework.runtime.model.FeatureMetadata;
import org.spockframework.runtime.model.SpecMetadata;
import spock.lang.Specification;

@SpecMetadata(
    filename = "OtherSpec.groovy",
    line = 4
)
public class OtherSpec extends Specification implements GroovyObject {
    public OtherSpec() {
    }

    public Object test(String result) {
        return true ? ScriptBytecodeAdapter.compareEqual(result, "otherValue") : null;
    }

    @FeatureMetadata(
        line = 7,
        name = "test fails as expected",
        ordinal = 0,
        blocks = {@BlockMetadata(
    kind = BlockKind.WHEN,
    texts = {"result has some value"}
), @BlockMetadata(
    kind = BlockKind.THEN,
    texts = {"result has the expected value"}
)},
        parameterNames = {}
    )
    public void $spock_feature_0_0() {
        ErrorCollector $spock_errorCollector = new ErrorCollector(false);
        ValueRecorder $spock_valueRecorder = new ValueRecorder();

        Object var10000;
        try {
            String result = "someValue";

            try {
                SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "result == \"otherValue\"", Integer.valueOf(12), Integer.valueOf(9), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), result), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), "otherValue"))));
                var10000 = null;
            } catch (Throwable var13) {
                SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "result == \"otherValue\"", Integer.valueOf(12), Integer.valueOf(9), (Object)null, var13);
                var10000 = null;
            } finally {
                ;
            }

            ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
        } finally {
            $spock_errorCollector.validateCollectedErrors();
            var10000 = null;
        }

    }

    @FeatureMetadata(
        line = 15,
        name = "test passes, but shouldn't",
        ordinal = 1,
        blocks = {@BlockMetadata(
    kind = BlockKind.WHEN,
    texts = {"result has some value"}
), @BlockMetadata(
    kind = BlockKind.THEN,
    texts = {"result has the expected value"}
)},
        parameterNames = {}
    )
    public void $spock_feature_0_1() {
        String result = "someValue";
        if (true) {
            ScriptBytecodeAdapter.compareEqual(result, "otherValue");
        }

        ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
    }

    @FeatureMetadata(
        line = 25,
        name = "test fails as expected when using assert",
        ordinal = 2,
        blocks = {@BlockMetadata(
    kind = BlockKind.WHEN,
    texts = {"result has some value"}
), @BlockMetadata(
    kind = BlockKind.THEN,
    texts = {"result has the expected value"}
)},
        parameterNames = {}
    )
    public void $spock_feature_0_2() {
        ErrorCollector $spock_errorCollector = new ErrorCollector(false);
        ValueRecorder $spock_valueRecorder = new ValueRecorder();

        Object var10000;
        try {
            String result = "someValue";
            DefaultGroovyMethods.println(this, this.test("otherValue"));
            var10000 = null;
            if (true) {
                try {
                    SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "result == \"otherValue\"", Integer.valueOf(32), Integer.valueOf(20), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), result), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), "otherValue"))));
                    var10000 = null;
                } catch (Throwable var13) {
                    SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "result == \"otherValue\"", Integer.valueOf(32), Integer.valueOf(20), (Object)null, var13);
                    var10000 = null;
                } finally {
                    ;
                }
            }

            ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
        } finally {
            $spock_errorCollector.validateCollectedErrors();
            var10000 = null;
        }

    }
}

Теперь, если мы проанализируем этот код,обнаружит, что следующий тестовый пример Спока:

def "test fails as expected"() {
    when: "result has some value"
    def result = "someValue"

    then: "result has the expected value"
    result == "otherValue"
}

компилируется примерно так:

public void $spock_feature_0_0() {
    ErrorCollector $spock_errorCollector = new ErrorCollector(false);
    ValueRecorder $spock_valueRecorder = new ValueRecorder();

    Object var10000;
    try {
        String result = "someValue";

        try {
            SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "result == \"otherValue\"", Integer.valueOf(12), Integer.valueOf(9), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), result), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), "otherValue"))));
            var10000 = null;
        } catch (Throwable var13) {
            SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "result == \"otherValue\"", Integer.valueOf(12), Integer.valueOf(9), (Object)null, var13);
            var10000 = null;
        } finally {
            ;
        }

        ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
    } finally {
        $spock_errorCollector.validateCollectedErrors();
        var10000 = null;
    }

}

И тестовый случай, в который вы помещаете утверждение внутри оператора if:

def "test passes, but shouldn't"() {
    when: "result has some value"
    def result = "someValue"

    then: "result has the expected value"
    if (true) {
        result == "otherValue"
    }
}

компилируется примерно так:

public void $spock_feature_0_1() {
    String result = "someValue";
    if (true) {
        ScriptBytecodeAdapter.compareEqual(result, "otherValue");
    }

    ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
}

Если вы заинтересованы в изучении исходного кода этого преобразования AST, вы можете начать с анализа:

И для последнего варианта использования - добавление assert к блоку оператора if является явная инструкция для Спока, что она должна быть преобразована в вызов условия проверки.Вот почему вы видите байт-код, который декомпилируется примерно так:

public void $spock_feature_0_2() {
    ErrorCollector $spock_errorCollector = new ErrorCollector(false);
    ValueRecorder $spock_valueRecorder = new ValueRecorder();

    Object var10000;
    try {
        String result = "someValue";
        DefaultGroovyMethods.println(this, this.test("otherValue"));
        var10000 = null;
        if (true) {
            try {
                SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "result == \"otherValue\"", Integer.valueOf(32), Integer.valueOf(20), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), result), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), "otherValue"))));
                var10000 = null;
            } catch (Throwable var13) {
                SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "result == \"otherValue\"", Integer.valueOf(32), Integer.valueOf(20), (Object)null, var13);
                var10000 = null;
            } finally {
                ;
            }
        }

        ScriptBytecodeAdapter.invokeMethod0(OtherSpec.class, ((OtherSpec)this).getSpecificationContext().getMockController(), (String)"leaveScope");
    } finally {
        $spock_errorCollector.validateCollectedErrors();
        var10000 = null;
    }
}

Обратите внимание, что if (true) { /*...*/ } все еще присутствует, потому что преобразователь AST все еще игнорирует его преобразование, но условие:

assert result == "otherValue"

был посещен и принят трансформатором AST и заменен:

SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "result == \"otherValue\"", Integer.valueOf(32), Integer.valueOf(20), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), result), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), "otherValue"))));
...