groovy spock 使用 spy 测试关闭
groovy spock testing closure with spy
我有调用管道步骤方法 (withCredentials) 的共享库。我正在尝试测试 withCredentials 方法是否在调用 myMethodToTest 时使用 sh 脚本正确调用,但在迭代 withCredentials 闭包时遇到错误:
测试方法
class myClass implements Serializable{
def steps
public myClass(steps) {this.steps = steps}
public void myMethodToTest(script, credentialsId, dataObject) {
dataObject.myKeyValue.each {
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) {
steps.sh("git push --set-upstream origin ${it.branch}")
}
}
}
}
嘲讽
class Steps {
def withCredentials(List args, Closure closure) {}
}
class Script {
public Map env = [:]
}
测试用例
def "testMyMethod"(){
given:
def steps = Spy(Steps)
def script = Mock(Script)
def myClassObj = new myClass(steps)
def myDataObject = [
'myKeyValue' : [['branch' :'mock' ]]
]
when:
def result = myClassObj.myMethodToTest(script, credId, myDataObject)
then:
1 * steps.withCredentials([[
$class: 'UsernamePasswordMultiBinding',
credentialsId: "mycredId",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]])
1 * steps.sh(shString)
where:
credId | shString
"mycredId" | "git push --set-upstream origin mock"
错误(it变量在闭包中变为null)
java.lang.NullPointerException: Cannot get property 'branch' on null object
你有两个嵌套闭包的情况
dataObject.myKeyValue.each { // <- first closure it references the map
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) { // <- second closure it is null as no parameter is passed to this closure
steps.sh("git push --set-upstream origin ${it.branch}")
}
}
要修复它,您应该命名第一个参数
dataObject.myKeyValue.each { conf ->
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) {
steps.sh("git push --set-upstream origin ${conf.branch}")
}
}
请接受 Leonard 的回答,但我想 post MCVE 进行一些修复,以便其他人实际上可以 运行 测试和验证解决方案,因为即使他的回答你的代码永远不会 运行 没有错误。所以我们开始吧(请注意我的内联评论):
package de.scrum_master.Whosebug.q60044097
class Script {
public Map env = [:]
}
package de.scrum_master.Whosebug.q59442086
class Steps {
def withCredentials(List args, Closure closure) {
println "withCredentials: $args, " + closure
// Evaluate closure so as to do something meaningful
closure()
}
// Add missing method to avoid "too few invocations" in test
def sh(String script) {
println "sh: $script"
}
}
package de.scrum_master.Whosebug.q60044097
class MyClass implements Serializable {
def steps
MyClass(steps) { this.steps = steps }
void myMethodToTest(script, credentialsId, dataObject) {
// Fix wrong quotes in ‘UsernamePasswordMultiBinding’
// and incorporate Leonard's solution to the nested closure problem
dataObject.myKeyValue.each { conf ->
steps.withCredentials(
[
[
$class : 'UsernamePasswordMultiBinding',
credentialsId : "${credentialsId}",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]
]
) {
steps.sh("git push --set-upstream origin ${conf.branch}")
}
}
}
}
package de.scrum_master.Whosebug.q60044097
import spock.lang.Specification
class MyClassTest extends Specification {
def "testMyMethod"() {
given:
def steps = Spy(Steps)
// Actually this noes not need to be a mock, given your sample code.
// Maybe the real code is different.
def script = Mock(Script)
def myClassObj = new MyClass(steps)
def myDataObject = [
'myKeyValue': [['branch': 'mock']]
]
when:
// Result is never used, actually no need to assign anything
def result = myClassObj.myMethodToTest(script, credId, myDataObject)
then:
1 * steps.withCredentials(
[
[
$class : 'UsernamePasswordMultiBinding',
credentialsId : "mycredId",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]
],
// Add missing closure parameter placeholder '_' to make the test run
_
)
1 * steps.sh(shString)
where:
credId | shString
"mycredId" | "git push --set-upstream origin mock"
}
}
请注意: 进行测试 运行 并让应用程序做一些边际有意义的事情只是为了完成图片。但实际上你问的问题是应用程序代码中的一个 错误(以不正确的方式使用嵌套闭包)。 两者中的其他错误测试和应用程序代码只是隐藏它,因为测试甚至从未到达有问题的部分。
更新:你的问题归结为包括两个可能的解决方案(B 基本上是伦纳德建议的):
def evalClosure(Closure closure) {
closure()
}
// Problem: inner closure's 'it' shadowing outer closure's 'it'
[1, 2].each {
println "outer closure: it = $it"
evalClosure {
println "inner closure: it = $it"
}
}
println "-" * 30
// Fix A: make inner closure explicitly parameter-less
[1, 2].each {
println "outer closure: it = $it"
evalClosure { ->
println "inner closure: it = $it"
}
}
println "-" * 30
// Fix B: explicitly rename outer closure's parameter
[1, 2].each { number ->
println "outer closure: number = $number"
evalClosure {
println "inner closure: it = $it"
println "inner closure: number = $number"
}
}
控制台日志:
outer closure: it = 1
inner closure: it = null
outer closure: it = 2
inner closure: it = null
------------------------------
outer closure: it = 1
inner closure: it = 1
outer closure: it = 2
inner closure: it = 2
------------------------------
outer closure: number = 1
inner closure: it = null
inner closure: number = 1
outer closure: number = 2
inner closure: it = null
inner closure: number = 2
我有调用管道步骤方法 (withCredentials) 的共享库。我正在尝试测试 withCredentials 方法是否在调用 myMethodToTest 时使用 sh 脚本正确调用,但在迭代 withCredentials 闭包时遇到错误:
测试方法
class myClass implements Serializable{
def steps
public myClass(steps) {this.steps = steps}
public void myMethodToTest(script, credentialsId, dataObject) {
dataObject.myKeyValue.each {
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) {
steps.sh("git push --set-upstream origin ${it.branch}")
}
}
}
}
嘲讽
class Steps {
def withCredentials(List args, Closure closure) {}
}
class Script {
public Map env = [:]
}
测试用例
def "testMyMethod"(){
given:
def steps = Spy(Steps)
def script = Mock(Script)
def myClassObj = new myClass(steps)
def myDataObject = [
'myKeyValue' : [['branch' :'mock' ]]
]
when:
def result = myClassObj.myMethodToTest(script, credId, myDataObject)
then:
1 * steps.withCredentials([[
$class: 'UsernamePasswordMultiBinding',
credentialsId: "mycredId",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]])
1 * steps.sh(shString)
where:
credId | shString
"mycredId" | "git push --set-upstream origin mock"
错误(it变量在闭包中变为null)
java.lang.NullPointerException: Cannot get property 'branch' on null object
你有两个嵌套闭包的情况
dataObject.myKeyValue.each { // <- first closure it references the map
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) { // <- second closure it is null as no parameter is passed to this closure
steps.sh("git push --set-upstream origin ${it.branch}")
}
}
要修复它,您应该命名第一个参数
dataObject.myKeyValue.each { conf ->
steps.withCredentials([[
$class: ‘UsernamePasswordMultiBinding’, credentialsId: "${credentialsId}",
usernameVariable: 'USR', passwordVariable: 'PWD']]) {
steps.sh("git push --set-upstream origin ${conf.branch}")
}
}
请接受 Leonard 的回答,但我想 post MCVE 进行一些修复,以便其他人实际上可以 运行 测试和验证解决方案,因为即使他的回答你的代码永远不会 运行 没有错误。所以我们开始吧(请注意我的内联评论):
package de.scrum_master.Whosebug.q60044097
class Script {
public Map env = [:]
}
package de.scrum_master.Whosebug.q59442086
class Steps {
def withCredentials(List args, Closure closure) {
println "withCredentials: $args, " + closure
// Evaluate closure so as to do something meaningful
closure()
}
// Add missing method to avoid "too few invocations" in test
def sh(String script) {
println "sh: $script"
}
}
package de.scrum_master.Whosebug.q60044097
class MyClass implements Serializable {
def steps
MyClass(steps) { this.steps = steps }
void myMethodToTest(script, credentialsId, dataObject) {
// Fix wrong quotes in ‘UsernamePasswordMultiBinding’
// and incorporate Leonard's solution to the nested closure problem
dataObject.myKeyValue.each { conf ->
steps.withCredentials(
[
[
$class : 'UsernamePasswordMultiBinding',
credentialsId : "${credentialsId}",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]
]
) {
steps.sh("git push --set-upstream origin ${conf.branch}")
}
}
}
}
package de.scrum_master.Whosebug.q60044097
import spock.lang.Specification
class MyClassTest extends Specification {
def "testMyMethod"() {
given:
def steps = Spy(Steps)
// Actually this noes not need to be a mock, given your sample code.
// Maybe the real code is different.
def script = Mock(Script)
def myClassObj = new MyClass(steps)
def myDataObject = [
'myKeyValue': [['branch': 'mock']]
]
when:
// Result is never used, actually no need to assign anything
def result = myClassObj.myMethodToTest(script, credId, myDataObject)
then:
1 * steps.withCredentials(
[
[
$class : 'UsernamePasswordMultiBinding',
credentialsId : "mycredId",
usernameVariable: 'USR',
passwordVariable: 'PWD'
]
],
// Add missing closure parameter placeholder '_' to make the test run
_
)
1 * steps.sh(shString)
where:
credId | shString
"mycredId" | "git push --set-upstream origin mock"
}
}
请注意: 进行测试 运行 并让应用程序做一些边际有意义的事情只是为了完成图片。但实际上你问的问题是应用程序代码中的一个 错误(以不正确的方式使用嵌套闭包)。 两者中的其他错误测试和应用程序代码只是隐藏它,因为测试甚至从未到达有问题的部分。
更新:你的问题归结为包括两个可能的解决方案(B 基本上是伦纳德建议的):
def evalClosure(Closure closure) {
closure()
}
// Problem: inner closure's 'it' shadowing outer closure's 'it'
[1, 2].each {
println "outer closure: it = $it"
evalClosure {
println "inner closure: it = $it"
}
}
println "-" * 30
// Fix A: make inner closure explicitly parameter-less
[1, 2].each {
println "outer closure: it = $it"
evalClosure { ->
println "inner closure: it = $it"
}
}
println "-" * 30
// Fix B: explicitly rename outer closure's parameter
[1, 2].each { number ->
println "outer closure: number = $number"
evalClosure {
println "inner closure: it = $it"
println "inner closure: number = $number"
}
}
控制台日志:
outer closure: it = 1
inner closure: it = null
outer closure: it = 2
inner closure: it = null
------------------------------
outer closure: it = 1
inner closure: it = 1
outer closure: it = 2
inner closure: it = 2
------------------------------
outer closure: number = 1
inner closure: it = null
inner closure: number = 1
outer closure: number = 2
inner closure: it = null
inner closure: number = 2