2013-10-04 23 views
38

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') 
    } 
} 

バリアント1

この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' 
    } 

バリアント2この1が原因でコンパイル時にスポックが提起したこのエラーの動作していない

例外条件のみ「し、」ブロックに許可されている

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

バリアント3

この1つがあるため、コンパイル時にスポックが提起したこのエラーの機能していません。

例外条件が唯一の推奨される解決策を持っているトップレベルの文

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' 
    } 
+0

は先週と同じシナリオに出くわしたと私は@peterを示唆している正確に何をしたの代わりにバニラのものや他のいくつかの「本当の」例外を使用します。 :) 1つのデータテーブルに基づいて例外(投げられた/ notThrown)の2つのvairantsを処理する方法ではありません。スローされた例外はデータテーブルにもありません。 – dmahapatro

答えて

30

として許可されています2つの方法:良い例をテストする方法と、悪い場合をテストする方法です。両方の方法でデータテーブルを利用することができます。

例:

例からを使用して
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') 
    } 

} 
+1

私が覚えている1つの質問は、(MyException)をスローすることはできません。 'MyException'がスローされない場合はnullを返します。 – dmahapatro

+0

私は私のテストを再訪する必要があります。しかし、データテーブルにthrown()/ notThrown()を使ってエラーが発生しました。とにかく素晴らしいテストフレームワークに感謝します。私はあなたのために自分の仕事で「そのBDDデベロッパー」になったのです。 ;) – dmahapatro

+0

私はそのように動作する可能性がありますが、現在は 'throw 'に' null'を渡すことはできません。 –

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

@AmanuelNega私はspockウェブコンソールでこれを試して、コードを保存しました。http://meetspock.appspot.com/script/5713144022302720

import spock.lang.Specification 

class MathDemo { 
    static determineAverage(...values) 
     throws IllegalArgumentException { 
     for (item in values) { 
      if (! (item instanceof Number)) { 
       throw new IllegalArgumentException() 
      } 
     } 

     if (!values) { 
      return 0 
     } 

     return values.sum()/values.size() 
    } 
} 

class AvgSpec extends Specification { 

    @Unroll 
    def "average of #values gives #result"(values, result){ 
     expect: 
      MathDemo.determineAverage(*values) == result 

     where: 
      values  || result 
      [1,2,3]  || 2 
      [2, 7, 4, 4] || 4.25 
      []   || 0 
    } 

    @Unroll 
    def "determineAverage called with #values throws #exception"(values, exception){ 
     setup: 
      def e = getException(MathDemo.&determineAverage, *values) 

     expect: 
      exception == e?.class 

     where: 
      values  || exception 
      ['kitten', 1]|| java.lang.IllegalArgumentException 
      [99, true] || java.lang.IllegalArgumentException 
      [1,2,3]  || null 
    } 

    Exception getException(closure, ...args){ 
     try{ 
      closure.call(args) 
      return null 
     } catch(any) { 
      return any 
     } 
    } 
} 
​ 
0

ここでは、@Unrollwhen:then:、およびwhere:のブロックを使ってどのように達成したかの例を示します。 3つすべてのテストをデータテーブルのデータで実行します。

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

import java.util.regex.Pattern 

class MyVowelString { 
    private static final Pattern HAS_VOWELS = Pattern.compile('[aeiouAEIOU]') 
    final String string 

    MyVowelString(String string) { 
     assert string != null && HAS_VOWELS.matcher(string).find() 
     this.string = string 
    } 
} 

class PositiveNumberTest extends Specification { 
    @Unroll 
    def "invalid constructors with argument #number"() { 
     when: 
     new MyVowelString(string) 

     then: 
     thrown(AssertionError) 

     where: 
     string | _ 
     ''  | _ 
     null | _ 
     'pppp' | _ 
    } 
} 
4

これは私が思いついた解決策です。基本的にはバリアント3ですが、ブロックを使用して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' 
} 

いくつかの注意点:

  1. あなたは別の例外をテストするために複数のcatchブロックを必要とします。
  2. try/catchブロック内で明示的な条件(assert文)を使用する必要があります。
  3. あなたの刺激と反応をwhen-thenブロックに分けることはできません。
+1

自分の状況に完全に対応しました。 'assert!exceptionMessage'というメッセージが与えられた場合にのみ例外をチェックするように更新しました。' expectException'カラムを削除できます。 –

0

別のアプローチは、非例外シナリオを確認した後に返すことですので、他のシナリオを入れ子ない:

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

    then: 
     if (!expectedException) { 
      noExceptionThrown() 

      return 
     } 

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

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

唯一の難点は、あなたがnoExceptionThrown方法でnotThrownを使用していますが、このことはできませんですテストを分割するよりもやや面倒ではなく、代わりにハッピーパスを検証することで救済することができます。ここで

1

が、私はそれを行う方法です、私は常に、データテーブルでthrownを呼び出し、常にSuccess例外をスローするwhen:句、あなたがthrownnotThrownを呼び出すかどうかを伝えるために、別のテストやロジックを必要としないという方法を変更しますSuccessを期待するかどうかを伝える。

Successの名前をNoneまたはNoExceptionなど任意の名前に変更できます。私が変わってしまう

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') 
    } 
} 

一つの余分な事は、あなたが本当に失敗を期待していたときSuccessが誤って巻き込まれることを避けるために、あまりにも失敗例外のサブクラスを使用することです。あなたのメッセージに余分なチェックがあるのであなたの例には影響しませんが、他のテストでは例外タイプをテストするだけかもしれません。

class Failure extends Exception {} 

Exception

関連する問題