Groovy 中的隐式断言语句

Implicit assert statement in Groovy

假设我用 Groovy:

编写了 JUnit 测试
class AssertTests {

    @Test
    void "explicit assert statement"() {
        def value = 42
        assert value == 100
    }

    @Test
    void "no assert statement"() {
        def value = 42
        value == 100
    }

}

当我执行它们时, 由于 assert 语句,explicit assert statement 测试按预期失败。

no assert statement 测试通过,我希望它会以与我使用 http://spockframework.org

时类似的方式失败

如何实现 implicit assert 以纯 Groovy 编写的测试的行为?

答案很简单 - 你不能。 Spock 使用 AST 转换来抓取 then: 部分中编写的代码,并将其转换为与断言等效的代码(不是确切的 assert.)

为了说明这一点,这是您用 Spock 编写的测试:

import spock.lang.Specification

class TestSpec extends Specification {

    def "should fail"() {
        when:
        def value = 42

        then:
        assert value == 100
    }
}

这是它的字节码反编译回 Java 的样子:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
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 = "TestSpec.groovy",
    line = 5
)
public class TestSpec extends Specification implements GroovyObject {
    public TestSpec() {
        CallSite[] var1 = $getCallSiteArray();
        super();
    }

    @FeatureMetadata(
        line = 7,
        name = "should fail",
        ordinal = 0,
        blocks = {@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 {
            Object value = 42;

            try {
                SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), "value == 100", Integer.valueOf(12), Integer.valueOf(16), (Object)null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(2)), ScriptBytecodeAdapter.compareEqual($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(0)), value), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(Integer.valueOf(1)), 100))));
                var10000 = null;
            } catch (Throwable var14) {
                SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, "value == 100", Integer.valueOf(12), Integer.valueOf(16), (Object)null, var14);
                var10000 = null;
            } finally {
                ;
            }

            var1[2].call(var1[3].call(this.getSpecificationContext()));
        } finally {
            $spock_errorCollector.validateCollectedErrors();
            var10000 = null;
        }

    }
}

如果你查看 SpockRuntime class,你会发现 verifyCondition 方法检查是否在 then:and: 块中找到条件计算结果为 true:

public static void verifyCondition(@Nullable ErrorCollector errorCollector, @Nullable ValueRecorder recorder,
    @Nullable String text, int line, int column, @Nullable Object message, @Nullable Object condition) {
  if (!GroovyRuntimeUtil.isTruthy(condition)) {
    final ConditionNotSatisfiedError conditionNotSatisfiedError = new ConditionNotSatisfiedError(
      new Condition(getValues(recorder), text, TextPosition.create(line, column), messageToString(message), null, null));
    errorCollector.collectOrThrow(conditionNotSatisfiedError);
  }
}

您无法在 JUnit 测试中避免显式断言。您可以将它们隐藏在一些辅助方法中,但您仍然需要调用它们。请记住,JUnit 运行ner 需要测试方法 returns void,因此您无法捕获测试方法的结果。 (将 void 替换为 def,您会发现 JUnit 不会 运行 您的测试。)

如果您想在 Groovy 中探索 compile-time metaprogramming 的世界,您可以尝试编写自己的 AST 转换。也许有一种方法可以找到布尔表达式并在它前面注入 assert,但我不能保证它会起作用。如果您在 Groovy JUnit 测试中寻找隐式断言的现成解决方案,那么没有这样的解决方案。