После Документация Спока :
Блоки 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"))));