如何使用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
这个是有效的,但 / 然后标签以及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' }
}
变体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'
}
答案 0 :(得分:42)
推荐的解决方案是使用两种方法:一种测试好的案例,另一种测试坏的案例。然后这两种方法都可以使用数据表。
示例:
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 :(得分: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
}
}
答案 2 :(得分:4)
这是我提出的解决方案。它基本上是变体3,但它使用 $scope.editPatient = function (patient, index, e) {
if (e.shiftKey) {
angular.forEach($scope.patients, function (value, i) {
//if (value.selected && $scope.firstIndex == -1) {
// firstIndex = i;
//}
value.selected = false;
});
for (var indexOfRows = $scope.firstIndex; indexOfRows <= index; indexOfRows++) {
$('#patientsTb tr').eq(indexOfRows).toggleClass('selected');
}
angular.forEach($scope.patients, function (value, i) {
if (i >= $scope.firstIndex && i <= index) {
value.selected = !value.selected;
}
});
}
else if (e.ctrlkey) {
patient.selected = !patient.selected;
}
else {
angular.forEach($scope.patients, function (value, i) {
value.selected = false;
});
$scope.firstIndex = index;
patient.selected = true;
}
};
块来避免使用Spock的异常条件(因为那些必须顶级)。
try/catch
一些警告:
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
块。答案 3 :(得分:3)
使用@AmanuelNega中的示例我在spock Web控制台上尝试了这个并将代码保存在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
}
}
}
答案 4 :(得分:2)
在这里&#39;我是如何做到的,我修改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')
}
}
我要改变的另一件事是,也可以使用子类来处理失败异常,以避免在您真正期待失败时意外捕获Success
。它不会影响您的示例,因为您对消息进行了额外的检查,但其他测试可能只是测试异常类型。
class Failure extends Exception {}
使用那个或其他一些真实的&#34;异常而不是香草Exception
答案 5 :(得分:0)
以下是使用@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' | _
}
}
答案 6 :(得分:0)
我有解决方案,不会扭曲您的测试工作流程,并且您可以通过放置在表中的动态对象的内容来分析异常
@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
}