Spock "then" 块下的魔法是什么?
What is the magic under Spock "then" block?
我是 Groovy/Spock 的新手,很想了解 "then" 块语法的秘密:
def "my specification"() {
given:
int a = 1
when:
++a
then:
a == 1
a > 0
a != 2
}
框架如何分别评估此方法的最后 3 行?
then
本身就是一个普通的旧 Java 标签。
每一行都是一个新调用 >> Java 的 ;
可以省略。
每个调用都包含在 Groovy 的 assert
语句中,也可以在 Spock 外部执行:
int a = 1
assert a
assert 0 < a
assert !a // here assertion error
除了tim_yates and injecteer所说的,下面是Fernflower反编译(在IntelliJ IDEA中完成)的结果如下:
package de.scrum_master.Whosebug
import spock.lang.Specification
class DummyTest extends Specification {
def "my specification"() {
given:
int a = 1
when:
++a
then:
a == 2
a > 1
a != 3
}
}
package de.scrum_master.Whosebug;
import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
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 = "DummyTest.groovy",
line = 5
)
public class DummyTest extends Specification implements GroovyObject {
public DummyTest() {
CallSite[] var1 = $getCallSiteArray();
super();
}
@FeatureMetadata(
line = 6,
name = "my specification",
ordinal = 0,
blocks = {@BlockMetadata(
kind = BlockKind.SETUP,
texts = {}
), @BlockMetadata(
kind = BlockKind.WHEN,
texts = {}
), @BlockMetadata(
kind = BlockKind.THEN,
texts = {}
)},
parameterNames = {}
)
public void $spock_feature_0_0() {
CallSite[] var1 = $getCallSiteArray();
ErrorCollector $spock_errorCollector = (ErrorCollector)ScriptBytecodeAdapter.castToType(var1[0].callConstructor(ErrorCollector.class, false), ErrorCollector.class);
ValueRecorder $spock_valueRecorder = (ValueRecorder)ScriptBytecodeAdapter.castToType(var1[1].callConstructor(ValueRecorder.class), ValueRecorder.class);
Object var10000;
try {
int a = 1;
int a;
if (BytecodeInterface8.isOrigInt() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
a = a + 1;
} else {
a = DefaultTypeTransformation.intUnbox(var1[2].call(Integer.valueOf(a)));
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a == 2", Integer.valueOf(12), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 2))));
var10000 = null;
} catch (Throwable var44) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a == 2", Integer.valueOf(12), Integer.valueOf(5), (Object)null, var44);
var10000 = null;
} finally {
;
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a > 1", Integer.valueOf(13), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareGreaterThan($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 1))));
var10000 = null;
} catch (Throwable var42) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a > 1", Integer.valueOf(13), Integer.valueOf(5), (Object)null, var42);
var10000 = null;
} finally {
;
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a != 3", Integer.valueOf(14), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareNotEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 3))));
var10000 = null;
} catch (Throwable var40) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a != 3", Integer.valueOf(14), Integer.valueOf(5), (Object)null, var40);
var10000 = null;
} finally {
;
}
var1[3].call(var1[4].call(this.getSpecificationContext()));
} finally {
$spock_errorCollector.validateCollectedErrors();
var10000 = null;
}
}
}
Tim 的 link 为您提供了更多背景信息。如您所见,不仅仅是前置 assert
,它更像是包裹在 try-catch 中的 SpockRuntime.verifyCondition(..)
。
从实施的不同方面来看,有几个很好的答案。我将提供另一个关于如何解析 Spock 语法的答案,并提供源代码链接。
首先,语法很简单"label". The official doc shows that labels are used similar to ancient C labels in goto
. A statement can have label(s)。它是一种语法糖,Spock 使用它来提供直接的 DSL 语法。
groovy 源代码由 groovy 编译器处理(解析)并将源代码转换为 AST。这已经是一个有效的 groovy 程序,但它无法执行您期望的操作。 Spock 需要另一个转换(重写)来为语法添加单元测试功能。 Spock 为您编写“胶水代码”。
Spock 的规范访问者使用 buildBlocks()
检查标签并决定在 AST 中构建一个块供以后重写。
之后,重写器将 re-visit the AST (in your case visitThenBlock()
) to generate code as other answers have shown. The rewriter checks then
block to ensure that statements|expressions in the block are asserts or implicit conditions。
我是 Groovy/Spock 的新手,很想了解 "then" 块语法的秘密:
def "my specification"() {
given:
int a = 1
when:
++a
then:
a == 1
a > 0
a != 2
}
框架如何分别评估此方法的最后 3 行?
then
本身就是一个普通的旧 Java 标签。
每一行都是一个新调用 >> Java 的 ;
可以省略。
每个调用都包含在 Groovy 的 assert
语句中,也可以在 Spock 外部执行:
int a = 1
assert a
assert 0 < a
assert !a // here assertion error
除了tim_yates and injecteer所说的,下面是Fernflower反编译(在IntelliJ IDEA中完成)的结果如下:
package de.scrum_master.Whosebug
import spock.lang.Specification
class DummyTest extends Specification {
def "my specification"() {
given:
int a = 1
when:
++a
then:
a == 2
a > 1
a != 3
}
}
package de.scrum_master.Whosebug;
import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
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 = "DummyTest.groovy",
line = 5
)
public class DummyTest extends Specification implements GroovyObject {
public DummyTest() {
CallSite[] var1 = $getCallSiteArray();
super();
}
@FeatureMetadata(
line = 6,
name = "my specification",
ordinal = 0,
blocks = {@BlockMetadata(
kind = BlockKind.SETUP,
texts = {}
), @BlockMetadata(
kind = BlockKind.WHEN,
texts = {}
), @BlockMetadata(
kind = BlockKind.THEN,
texts = {}
)},
parameterNames = {}
)
public void $spock_feature_0_0() {
CallSite[] var1 = $getCallSiteArray();
ErrorCollector $spock_errorCollector = (ErrorCollector)ScriptBytecodeAdapter.castToType(var1[0].callConstructor(ErrorCollector.class, false), ErrorCollector.class);
ValueRecorder $spock_valueRecorder = (ValueRecorder)ScriptBytecodeAdapter.castToType(var1[1].callConstructor(ValueRecorder.class), ValueRecorder.class);
Object var10000;
try {
int a = 1;
int a;
if (BytecodeInterface8.isOrigInt() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
a = a + 1;
} else {
a = DefaultTypeTransformation.intUnbox(var1[2].call(Integer.valueOf(a)));
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a == 2", Integer.valueOf(12), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 2))));
var10000 = null;
} catch (Throwable var44) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a == 2", Integer.valueOf(12), Integer.valueOf(5), (Object)null, var44);
var10000 = null;
} finally {
;
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a > 1", Integer.valueOf(13), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareGreaterThan($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 1))));
var10000 = null;
} catch (Throwable var42) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a > 1", Integer.valueOf(13), Integer.valueOf(5), (Object)null, var42);
var10000 = null;
} finally {
;
}
try {
SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "a != 3", Integer.valueOf(14), Integer.valueOf(5), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareNotEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), a), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 3))));
var10000 = null;
} catch (Throwable var40) {
SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "a != 3", Integer.valueOf(14), Integer.valueOf(5), (Object)null, var40);
var10000 = null;
} finally {
;
}
var1[3].call(var1[4].call(this.getSpecificationContext()));
} finally {
$spock_errorCollector.validateCollectedErrors();
var10000 = null;
}
}
}
Tim 的 link 为您提供了更多背景信息。如您所见,不仅仅是前置 assert
,它更像是包裹在 try-catch 中的 SpockRuntime.verifyCondition(..)
。
从实施的不同方面来看,有几个很好的答案。我将提供另一个关于如何解析 Spock 语法的答案,并提供源代码链接。
首先,语法很简单"label". The official doc shows that labels are used similar to ancient C labels in goto
. A statement can have label(s)。它是一种语法糖,Spock 使用它来提供直接的 DSL 语法。
groovy 源代码由 groovy 编译器处理(解析)并将源代码转换为 AST。这已经是一个有效的 groovy 程序,但它无法执行您期望的操作。 Spock 需要另一个转换(重写)来为语法添加单元测试功能。 Spock 为您编写“胶水代码”。
Spock 的规范访问者使用 buildBlocks()
检查标签并决定在 AST 中构建一个块供以后重写。
之后,重写器将 re-visit the AST (in your case visitThenBlock()
) to generate code as other answers have shown. The rewriter checks then
block to ensure that statements|expressions in the block are asserts or implicit conditions。