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
これは動作していますが、本当の意図はすべてのwhen/thenラベルと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
これは、コンパイル時に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' }
}
_
バリアント
これは、コンパイル時に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'
}
_
推奨される解決策は、2つの方法を使用することです。1つは良いケースをテストし、もう1つは悪いケースをテストします。その後、両方の方法でデータテーブルを使用できます。
例:
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')
}
}
メソッド呼び出しを、メッセージまたは例外クラス、または両方のマップを返すメソッドでラップすることができます...
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
}
}
ここに私が思いついた解決策があります。基本的にはVariant 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'
}
注意点:
assert
ステートメント)を使用する必要があります。when-then
ブロックに分けることはできません。@AmanuelNegaの例を使用して、これをspock Webコンソールで試してみて、コードを http://meetspock.appspot.com/script/571314402230272 に保存しました
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
}
}
}
方法は次のとおりです。when:
句は常にSuccess
例外をスローします。そのようにすると、thrown
またはnotThrown
を呼び出すかどうかを判断するための個別のテストやロジックは必要なく、常にthrown
を呼び出すだけです。 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')
}
}
私が変更するもう1つのことは、失敗の例外にもサブクラスを使用して、実際に失敗を予期しているときにSuccess
が誤ってキャッチされるのを避けることです。メッセージの追加チェックがあるため、例には影響しませんが、他のテストでは例外タイプのみをテストする場合があります。
class Failure extends Exception {}
そして、バニラException
の代わりに、それまたは他の「本当の」例外を使用します
@Unroll
およびwhen:
、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' | _
}
}