【问题标题】:Jenkins pipeline mocking with Groovy使用 Groovy 模拟 Jenkins 管道
【发布时间】:2018-08-20 07:58:22
【问题描述】:

我对 Jenkins 管道的东西还很陌生,我正在 Groovy 中构建一个小型共享库。
在这种情况下,我试图提出一些单元测试,然后我必须模拟管道对象。

基本上,我有一个 Groovy 类,其中包含一个使用凭据执行某些操作的方法:

class MyClass implements Serializable {

  def pipeline

  MyClass(def pipeline) {
    this.pipeline = pipeline
  }

  void my method(String version) {
    pipeline.withCredentials([pipeline.usernamePassword(credentialsId: 'MY_ID', usernameVariable: 'MY_USER', passwordVariable: 'MY_PASSWORD')]) {
        pipeline.sh "./release.sh complete --username ${MY_USER} --password ${MY_PASSWORD} --version '${version}'"
    }
  }
}

因此,在对这个方法进行单元测试时,我创建了一个 PipelineMock Groovy 类来(尝试)模拟 withCredentialsusernamePassword

测试类是这样的(有点简化):

class MyClassTest {

  @Test
  void callWithWithVersionShouldCallCommad() {
    def pipelineMock = new PipelineMock()
    def myClass = new MyClass(pipelineMock)
    myClass('1.0.1')
    assertTrue(pipelineMock.commandCalled.startsWith('./release.sh complete'))
  }

}

而我想出的 PipelineMock 是:

class PipelineMock {

  String commandCalled

  def sh(String command) {
    commandCalled = command
  }

  def usernamePassword(Map inputs) {
    inputs
  }

  def withCredentials(List args, Closure closure) {
    for (arg in args) {
        closure.setProperty(arg.get('usernameVariable'), 'the_login')
        closure.setProperty(arg.get('passwordVariable'), 'the_password')
    }
    closure()
  }

}

最初我只是调用closure() 来执行代码,但是当执行到达pipeline.sh 行时,我得到了错误groovy.lang.MissingPropertyException: No such property: MY_USER for class: MyClass
所以我尝试将测试值注入MY_USERMY_PASSWORD,以便可以使用for 循环解决它们。我得到了完全相同的错误,但这次调用closure.setProperty 时。我在调试时进行了检查,arg.get('usernameVariable') 正确解析为MY_USER

好吧,我迷路了。我不是真正的 Groovy 专家,所以我可能会错过一些东西。任何有关了解正在发生的事情的帮助将不胜感激!

【问题讨论】:

  • Jenkins 对 Groovy 进行了一些低级编译器和运行时更改,并且很难模拟这些。我建议查看 JenkinsPipelineUnit 和可能的 this Gradle plugin(注意:我是插件的作者)看看这些对你有没有帮助。
  • @mkobit 是的,JenkinsPipelineUnit 应该是正确的答案,但是 OP 代码不起作用的原因并不是因为 Jenkins 运行时。我相信 OP 正试图将其作为没有 Jenkins 运行时的常规 Groovy 单元测试运行。

标签: unit-testing jenkins groovy closures jenkins-pipeline


【解决方案1】:

这看起来确实像 @mkobit 在评论中提到的那样使用 JenkinsPipelineUnit 的理想候选者,但为了学术兴趣,问题中的示例实际上可以与委托人相关的微小更改工作。原始MyClass.my method 中还有一个错字,我认为应该是MyClass.call。这是工作的PipelineMock

class PipelineMock {

  String commandCalled

  def sh(String command) {
    commandCalled = command
  }

  def usernamePassword(Map inputs) {
    inputs
  }

  def withCredentials(List args, Closure closure) {
    def delegate = [:]
    for (arg in args) {
        delegate[arg.get('usernameVariable')] = 'the_login'
        delegate[arg.get('passwordVariable')] = 'the_password'
    }
    closure.delegate = delegate
    closure()
  }

}

这里是MyClass,没有错字:

class MyClass implements Serializable {

  def pipeline

  MyClass(def pipeline) {
    this.pipeline = pipeline
  }

  void call(String version) {
    pipeline.withCredentials([pipeline.usernamePassword(credentialsId: 'MY_ID', usernameVariable: 'MY_USER', passwordVariable: 'MY_PASSWORD')]) {
        pipeline.sh "echo ./release.sh complete --username ${MY_USER} --password ${MY_PASSWORD} --version '${version}'"
    }
  }
}

测试应该按原样进行。

【讨论】:

  • 感谢@haridsv 对我的初始代码的解释和调整!我会深入研究这个delegate 的东西。