Spock - 使用数据表测试异常

Ren*_*ibe 47 testing groovy exception spock

如何用Spock以一种很好的方式测试异常(例如数据表)?

示例:具有validateUser可以使用不同消息抛出异常的方法,或者如果用户有效则无异常.

规范类本身:

class User { String userName }

class SomeSpec extends spock.lang.Specification {

    ...tests go here...

    private validateUser(User user) {
        if (!user) throw new Exception ('no user')
        if (!user.userName) throw new Exception ('no userName')
    }
}
Run Code Online (Sandbox Code Playgroud)

变式1

这一个是工作,但真正的意图是所有的混乱时,/然后标签和的多次呼吁validateUser(user).

    def 'validate user - the long way - working but not nice'() {
        when:
        def user = new User(userName: 'tester')
        validateUser(user)

        then:
        noExceptionThrown()

        when:
        user = new User(userName: null)
        validateUser(user)

        then:
        def ex = thrown(Exception)
        ex.message == 'no userName'

        when:
        user = null
        validateUser(user)

        then:
        ex = thrown(Exception)
        ex.message == 'no user'
    }
Run Code Online (Sandbox Code Playgroud)

变体2

由于Spock在编译时引发了这个错误,因此无法正常工作:

仅在'then'块中允许例外条件

    def 'validate user - data table 1 - not working'() {
        when:
        validateUser(user)

        then:
        check()

        where:
        user                         || check
        new User(userName: 'tester') || { noExceptionThrown() }
        new User(userName: null)     || { Exception ex = thrown(); ex.message == 'no userName' }
        null                         || { Exception ex = thrown(); ex.message == 'no user' }
    }
Run Code Online (Sandbox Code Playgroud)

变式3

由于Spock在编译时引发了这个错误,因此无法正常工作:

异常条件仅允许作为顶级语句

    def 'validate user - data table 2 - not working'() {
        when:
        validateUser(user)

        then:
        if (expectedException) {
            def ex = thrown(expectedException)
            ex.message == expectedMessage
        } else {
            noExceptionThrown()
        }

        where:
        user                         || expectedException | expectedMessage
        new User(userName: 'tester') || null              | null
        new User(userName: null)     || Exception         | 'no userName'
        null                         || Exception         | 'no user'
    }
Run Code Online (Sandbox Code Playgroud)

Pet*_*ser 46

推荐的解决方案是有两种方法:一种测试好的情况,另一种测试坏的情况.然后这两种方法都可以使用数据表.

例:

class SomeSpec extends Specification {

    class User { String userName }

    def 'validate valid user'() {
        when:
        validateUser(user)

        then:
        noExceptionThrown()

        where:
        user << [
                new User(userName: 'tester'),
                new User(userName: 'joe')]
    }

    def 'validate invalid user'() {
        when:
        validateUser(user)

        then:
        def error = thrown(expectedException)
        error.message == expectedMessage

        where:
        user                     || expectedException | expectedMessage
        new User(userName: null) || Exception         | 'no userName'
        new User(userName: '')   || Exception         | 'no userName'
        null                     || Exception         | 'no user'
    }

    private validateUser(User user) {
        if (!user) throw new Exception('no user')
        if (!user.userName) throw new Exception('no userName')
    }

}
Run Code Online (Sandbox Code Playgroud)

  • @PeterNiederwieser这对于如何处理数据表中的异常的基本示例非常有用.这是Google上"spock数据例外"的热门话题,参考示例(或指向doc)将非常有用谢谢. (3认同)
  • 我记得的一个问题是,如果不抛出`MyException`,则抛出(MyException)`返回null? (2认同)

Ben*_*ass 7

这是我想出的解决方案。它基本上是变体 3,但它使用一个try/catch块来避免使用 Spock 的异常条件(因为它们必须是顶级的)。

def "validate user - data table 3 - working"() {
    expect:
    try {
        validateUser(user)
        assert !expectException
    }
    catch (UserException ex)
    {
        assert expectException
        assert ex.message == expectedMessage
    }

    where:
    user                         || expectException | expectedMessage
    new User(userName: 'tester') || false           | null
    new User(userName: null)     || true            | 'no userName'
    null                         || true            | 'no user'
}
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

  1. 您需要多个 catch 块来测试不同的异常。
  2. 您必须assert在 try/catch 块内使用显式条件(语句)。
  3. 你不能把你的刺激和反应分成when-then块。

  • 非常适合我的情况。我刚刚更新以仅在提供消息时检查异常:`assert !exceptionMessage`,并且可以删除 `expectException` 列。 (2认同)

idi*_*dij 7

这是我的做法,我修改了when:子句以始终抛出Success异常,这样您就不需要单独的测试或逻辑来判断是否调用thrownor notThrown,只需始终thrown使用数据表进行调用即可告知是否期望Success

您可以重命名SuccessNoneNoException或任何您喜欢的名称。

class User { String userName }

class SomeSpec extends spock.lang.Specification {

    class Success extends Exception {}

    def 'validate user - data table 2 - working'() {
        when:
            validateUser(user)
            throw new Success ()

        then:
            def ex = thrown(expectedException)
            ex.message == expectedMessage

        where:
            user                         || expectedException | expectedMessage 
            new User(userName: 'tester') || Success           | null
            new User(userName: null)     || Exception         | 'no userName'
            null                         || Exception         | 'no user'
    }

    private validateUser(User user) {
        if (!user) throw new Exception ('no user')
        if (!user.userName) throw new Exception ('no userName')
    }
}
Run Code Online (Sandbox Code Playgroud)

我要更改的另一件事是也为失败异常使用子类,以避免Success在您真正期待失败时被意外捕获。它不会影响您的示例,因为您对消息进行了额外检查,但其他测试可能只测试异常类型。

class Failure extends Exception {}
Run Code Online (Sandbox Code Playgroud)

并使用那个或其他一些“真正的”异常而不是香草 Exception

  • 只有在测试中才会抛出它,以解决框架的限制,因此在我看来,这种启发式方法不适用,或者被它掩盖的其他气味所掩盖。 (2认同)

Ama*_*ega 5

您可以使用返回消息或异常类的方法或两者的映射来包装方法调用.

  def 'validate user - data table 2 - not working'() {
        expect:
            expectedMessage == getExceptionMessage(&validateUser,user)
        where:
        user                         || expectedMessage
        new User(userName: 'tester') || null
        new User(userName: null)     || 'no userName'
        null                         || 'no user'
    }

    String getExceptionMessage(Closure c, Object... args){
        try{
            return c.call(args)
            //or return null here if you want to check only for exceptions
        }catch(Exception e){
            return e.message
        }
    }
Run Code Online (Sandbox Code Playgroud)


And*_*eck 5

我有一个不会扭曲您的测试工作流程的解决方案,您可以通过放置在表中的动态对象的内容来分析异常

@Unroll
def "test example [a=#a, b=#b]"() {
    given:
    def response
    def caughtEx

    when:
    try {
      result = someAmazingFunctionWhichThrowsSometimes(a,b)
    } catch (Exception ex) {
      caughtEx = ex
    }

    then:
    result == expected

    if (exception.expected) {
        assert caughtEx != null && exception.type.isInstance(caughtEx)
    } else {
        assert caughtEx == null
    }

    where:
    a    | b    || exception                                  | expected
    8    | 4    || [expected: false]                          | 2
    6    | 3    || [expected: false]                          | 3
    6    | 2    || [expected: false]                          | 3
    4    | 0    || [expected: true, type: RuntimeException]   | null

}
Run Code Online (Sandbox Code Playgroud)