Spock不确定的断言行为

时间:2018-12-12 13:32:57

标签: unit-testing groovy spock

据我所知,验证测试结果的一种方法是将表达式写入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"
    }
}

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

1 个答案:

答案 0 :(得分:3)

Spock documentation之后:

  

InputStream inputStream = response.getRawBody(); BufferedInputStream bis = new BufferedInputStream(inputStream); ByteArrayOutputStream buf = new ByteArrayOutputStream(); int result = bis.read(); while(result != -1) { buf.write((byte) result); result = bis.read(); } String rawJson = buf.toString("UTF-8"); when块始终一起出现。他们描述了刺激和预期的反应。 then块可以包含任意代码,而when 块仅限于条件例外条件交互变量定义。特征方法可能包含多对then块。

这说明了为什么Spocks AST变压器看不到以下when-then块:

then

是正确的,它不会将其转换为then: if (true) { result == "otherValue" } 调用。

如果编译您的类(启用静态编译以提高可读性)并检查字节码,您将看到类似以下内容:

SpockRuntime.verifyCondition()

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

//
// 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"
}

还有将断言放在if语句中的测试用例:

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

}

编译成这样:

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

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

如果您有兴趣调查此AST转换的源代码,可以先进行以下分析:

     

在最后一个用例中,在if语句块中添加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"); } 是Spock的显式指令,必须将其转换为验证条件调用。这就是为什么您会看到将字节码反编译成这样的原因:

assert

请注意,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; } } 仍然存在,因为AST转换器仍然忽略对其进行转换,但是条件是:

if (true) { /*...*/ }

被AST变压器访问并接受,并替换为:

assert result == "otherValue"