测试代码抛异常场景

`class ExceptionSpec extends Specification {

    def validateService = new ValidateService()

    @Unroll
    def "验证UserInfo"() {

        when: "调用校验方法"
        validateService.validateUser(user)

        then: "捕获异常并设置需要验证的异常值"
        def exception = thrown(expectedException)
        exception.errorCode == expectedErrCode
        exception.errorMessage == expectedMessage

        where: "验证用户的合法性"
        user                || expectedException | expectedErrCode | expectedMessage
        null                || APIException      | "1001"          | "userInfo is null"
        new UserInfo(0, "") || APIException      | "1002"          | "id is not legal"
        new UserInfo(1, "") || APIException      | "1003"          | "name is not legal"
    }
}`
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

Spock内置thrown()方法,可以捕获调用业务代码抛出的预期异常并验证,再结合where表格的功能,可以很方便的覆盖多种自定义业务异常。

then标签里用到了Spock的thrown()方法,这个方法可以捕获我们要测试的业务代码里抛出的异常。thrown()方法的入参expectedException,是我们自己定义的异常变量,这个变量放在where标签里就可以实现验证多种异常情况的功能。expectedException类型调用validateUser方法里定义的APIException异常,可以验证它所有的属性,errorCodeerrorMessage是否符合预期值。

注意事项

在Spock中,异常条件(如thrownnotThrown)只能在then块的顶层使用,不能嵌套在其他控制结构(如if语句)中。这意味着你不能在if语句内部使用thrown方法。

为了测试不同输入数据下的方法行为,包括抛出异常和不抛出异常的情况,你需要将这些测试拆分为单独的测试用例。每个测试用例只测试一种情况:要么是抛出异常,要么是不抛出异常。

测试案例代码

// 测试捕获异常 RuntimeException 异常
  def "checkUrlThrowException"() {
        given:
        SSRFUtil
        when:
        SSRFUtil.checkUrl(requestUrl)
        then:
        def exception = thrown(expectedException)
        where:
        requestUrl          || expectedException
        "https://baidu.com" || RuntimeException
    }
   // 测试不抛异常
    def "checkUrlNotException"() {
        given:
        SSRFUtil
        when:
        SSRFUtil.checkUrl(requestUrl)
        then:
        def exception = notThrown(expectedException)
        where:
        requestUrl                                  || expectedException
        "https://xxx.aaa.com/xxx/xxxx.xlsx"     || null
        "https://xxx.aaa.net/xxx/xxxx.xlsx"     || null
        "https://xxx.vip.aaa.com/xxx/xxxx.xlsx" || null
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

其它

下面的案例来自于 小美智能助理 的答案.

在Spock中,where块用于数据驱动测试,通过提供多个输入数据集来测试方法的行为。在测试方法不抛出异常的情况下,可以在then块中使用notThrown方法来验证没有异常被抛出。你可以结合where块和notThrown方法来测试方法在不同输入数据下的行为。

以下是一个示例,展示了如何在where块中测试方法在不同输入数据下不抛出异常的情况。

示例代码

假设你有一个Calculator类,它的divide方法在除以零时会抛出一个IllegalArgumentException,在其他情况下正常工作。

class Calculator {
    int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Division by zero")
        }
        return a / b
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

你可以使用where块来测试divide方法在不同输入数据下的行为,不抛出异常的情况如下:

import spock.lang.Specification
import spock.lang.Unroll

class CalculatorSpec extends Specification {

    @Unroll
    def "should return correct result when dividing #a by #b"() {
        given:
        def calculator = new Calculator()

        when:
        def result = calculator.divide(a, b)

        then:
        result == expected
        notThrown(Exception)

        where:
        a | b || expected
        10 | 2 || 5
        15 | 3 || 5
        20 | 4 || 5
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

在这个示例中:

  • @Unroll注解用于展开每个数据驱动的测试用例,以便更好地查看每个测试用例的结果。
  • where块定义了测试数据,包括输入参数ab以及期望的结果expected
  • then块中,我们验证result是否等于期望值,并确保没有抛出任何异常。
结合测试抛异常和不抛异常

你还可以在同一个测试方法中结合测试方法抛出异常和不抛出异常的情况:

import spock.lang.Specification
import spock.lang.Unroll

class CalculatorSpec extends Specification {

    @Unroll
    def "should handle division correctly for a=#a and b=#b"() {
        given:
        def calculator = new Calculator()

        when:
        def result = calculator.divide(a, b)

        then:
        if (b == 0) {
            def e = thrown(IllegalArgumentException)
            e.message == "Division by zero"
        } else {
            result == expected
            notThrown(Exception)
        }

        where:
        a | b || expected
        10 | 2 || 5
        10 | 0 || _
        15 | 3 || 5
        20 | 4 || 5
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

在这个示例中:

  • @Unroll注解用于展开每个数据驱动的测试用例,以便更好地查看每个测试用例的结果。
  • where块定义了测试数据,包括输入参数ab以及期望的结果expected
  • then块中,我们使用了条件语句来分别处理抛出异常和不抛出异常的情况。
  • 如果b == 0,我们期望抛出IllegalArgumentException,并验证异常的消息内容。
  • 否则,我们验证result是否等于期望值,并确保没有抛出任何异常。

通过这种方式,你可以在数据驱动的测试中测试方法在不同输入数据下是否抛出异常或不抛出异常,并验证方法的行为是否符合预期。