Spock 不清楚的断言行为

问题描述 投票:0回答:2

据我所知,验证测试结果的一种方法是将表达式写入 then 部分,其计算结果为布尔值。

然而最近我经历了一种我不理解的行为。似乎当一个人试图验证某个块中的某些内容时,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"
    }
}

我发现这种行为有点误导。有人可以解释为什么它会这样工作吗?这是一个错误还是用法不正确?

unit-testing groovy spock
2个回答
3
投票

以下 Spock 文档

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;
        }

    }
}

现在,如果我们分析这段代码,我们会发现下面的Spock测试用例:

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 语句块是 Spock 的显式指令,必须将其转换为验证条件调用。这就是为什么您会看到反编译为如下内容的字节码:

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 Transformer访问并接受,并替换为:

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"))));

0
投票

CodeNarc 从 3.3.0 版本开始有一个规则来检测此类错误https://codenarc.org/codenarc-rules-junit.html#spockmissingassert-rule

默认优先级为 3,我建议将其更改为 P1,因为如果缺少断言,测试可能毫无用处

SpockMissingAssert(priority: 1)
© www.soinside.com 2019 - 2024. All rights reserved.