日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何对 Jenkins 共享库进行单元测试

發(fā)布時間:2025/3/15 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何对 Jenkins 共享库进行单元测试 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

本文首發(fā)于:Jenkins 中文社區(qū)

Jenkins 共享庫是除了 Jenkins 插件外,另一種擴展 Jenkins 流水線的技術(shù)。通過它,可以輕松地自定義步驟,還可以對現(xiàn)有的流水線邏輯進行一定程度的抽象與封裝。至于如何寫及如何使用它,讀者朋友可以移步附錄中的官方文檔。

對共享庫進行單元測試的原因

但是如何對它進行單元測試呢?共享庫越來越大時,你不得不考慮這個問題。因為如果你不在早期就開始單元測試,共享庫后期可能就會發(fā)展成如下圖所示的“藝術(shù)品”——能工作,但是脆弱到?jīng)]有人敢動。

[圖片來自網(wǎng)絡(luò),侵權(quán)必刪]

這就是代碼越寫越慢的原因之一。后人要不斷地填前人有意無意挖的坑。

共享庫單元測試搭建

共享庫官方文檔介紹的代碼倉庫結(jié)構(gòu)

(root) +- src # Groovy source files | +- org | +- foo | +- Bar.groovy # for org.foo.Bar class +- vars | +- foo.groovy # for global 'foo' variable | +- foo.txt # help for 'foo' variable +- resources # resource files (external libraries only) | +- org | +- foo | +- bar.json # static helper data for org.foo.Bar

以上是共享庫官方文檔介紹的代碼倉庫結(jié)構(gòu)。整個代碼庫可以分成兩部分:src 目錄部分和 vars 目錄部分。它們的測試腳手架的搭建方式是不一樣的。

src 目錄中的代碼與普通的 Java 類代碼本質(zhì)上沒有太大的區(qū)別。只不過換成了 Groovy 類。

但是 vars 目錄中代碼本身是嚴重依賴于 Jenkins 運行時環(huán)境的腳本。

接下來,分別介紹如何搭建它們的測試腳手架。

測試 src 目錄中的 Groovy 代碼

在對 src 目錄中的 Groovy 代碼進行單元測試前,我們需要回答一個問題:使用何種構(gòu)建工具進行構(gòu)建?

我們有兩種常規(guī)選擇:Maven 和 Gradle。本文選擇的是前者。

接下來的第二個問題是,共享庫源代碼結(jié)構(gòu)并不是 Maven 官方標(biāo)準(zhǔn)結(jié)構(gòu)。下例為標(biāo)準(zhǔn)結(jié)構(gòu):

├── pom.xml └── src├── main│?? ├── java│?? └── resources└── test├── java└── resources

因為共享庫使用的 Groovy 寫的,所以,還必須使 Maven 能對 Groovy 代碼進行編譯。

可以通過 Maven 插件:GMavenPlus 解決以上問題,插件的關(guān)鍵配置如下:

<configuration><sources><source><!-- 指定Groovy類源碼所在的目錄 --><directory>${project.basedir}/src</directory><includes><include>**/*.groovy</include></includes></source></sources><testSources><testSource><!-- 指定單元測試所在的目錄 --><directory>${project.basedir}/test/groovy</directory><includes><include>**/*.groovy</include></includes></testSource></testSources> </configuration>

同時,我們還必須加入 Groovy 語言的依賴:

<dependency><groupId>org.codehaus.groovy</groupId><artifactId>groovy-all</artifactId><version>${groovy-all.version}</version> </dependency>

最終目錄結(jié)構(gòu)如下圖所示:

然后我們就可以愉快地對 src 目錄中的代碼進行單元測試了。

測試 vars 目錄中 Groovy 代碼

對 vars 目錄中的腳本的測試難點在于它強依賴于 Jenkins 的運行時環(huán)境。換句話說,你必須啟動一個 Jenkins 才能正常運行它。但是這樣就變成集成測試了。那么怎么實現(xiàn)單元測試呢?

經(jīng) Google 發(fā)現(xiàn),前人已經(jīng)寫了一個 Jenkins 共享庫單元測試的框架。我們拿來用就好。所謂,前人載樹,后人乘涼。

這個框架叫:Jenkins Pipeline Unit testing framework。后文簡稱“框架”。它的使用方法如下:

  • 在 pom.xml 中加入依賴:
  • <dependency><groupId>com.lesfurets</groupId><artifactId>jenkins-pipeline-unit</artifactId><version>1.1</version><scope>test</scope> </dependency>
  • 寫單元測試
  • // test/groovy/codes/showme/pipeline/lib/SayHelloTest.groovy // 必須繼承 BasePipelineTest 類 class SayHelloTest extends BasePipelineTest {@Override@Beforepublic void setUp() throws Exception {// 告訴框架,共享庫腳本所在的目錄scriptRoots = ["vars"]// 初始化框架super.setUp()}@Testvoid call() {// 加載腳本def script = loadScript("sayHello.groovy")// 運行腳本script.call()// 斷言腳本中運行了 echo 方法// 同時參數(shù)為"hello pipeline"assertThat(helper.callStack.findAll { c -> c.methodName == 'echo' }.any { c -> c.argsToString().contains('hello pipeline') }).isTrue()// 框架提供的方法,后面會介紹。printCallStack()} }

    創(chuàng)建單元測試時,注意選擇 Groovy 語言,同時類名要以 Test 結(jié)尾。

  • 改進 以上代碼是為了讓讀者對共享庫腳本的單元測試有更直觀的理解。實際工作中會做一些調(diào)整。我們會將 extends BasePipelineTest 和 setUp 方法抽到一個父類中,所有其它測試類繼承于它。
  • 此時,我們最簡單的共享庫的單元測試腳手架就搭建好了。

    但是,實際工作中遇到場景并不會這么簡單。面對更復(fù)雜的場景,必須了解 Jenkins Pipeline Unit testing framework 的原理。由此可見,寫單元測試也是需要成本的。至于收益,仁者見仁,智者見智了。

    Jenkins Pipeline Unit testing framework 原理

    上文中的單元測試實際上做了三件事情:

  • 加載目標(biāo)腳本,loadScript 方法由框架提供。
  • 運行腳本,loadScript 方法返回加載好的腳本。
  • 斷言腳本中的方法是否有按預(yù)期執(zhí)行,helper 是 BasePipelineTest 的一個字段。
  • 從第三步的 helper.callStack 中,我們可以猜到第二步中的script.call() 并不是真正的執(zhí)行,而是將腳本中方法調(diào)用被寫到 helper 的 callStack 字段中。從 helper 的源碼可以確認這一點:

    /** * Stack of method calls of scripts loaded by this helper */ List<MethodCall> callStack = []

    那么,script.call() 內(nèi)部是如何做到將方法調(diào)用寫入到 callStack 中的呢?

    一定是在 loadScript 運行過程做了什么事情,否則,script 怎么會多出這些行為。我們來看看它的底層源碼:

    /*** Load the script with given binding context without running, returning the Script* @param scriptName* @param binding* @return Script object*/Script loadScript(String scriptName, Binding binding) {Objects.requireNonNull(binding, "Binding cannot be null.")Objects.requireNonNull(gse, "GroovyScriptEngine is not initialized: Initialize the helper by calling init().")Class scriptClass = gse.loadScriptByName(scriptName)setGlobalVars(binding)Script script = InvokerHelper.createScript(scriptClass, binding)script.metaClass.invokeMethod = getMethodInterceptor()script.metaClass.static.invokeMethod = getMethodInterceptor()script.metaClass.methodMissing = getMethodMissingInterceptor()return script}

    gse 是 Groovy 腳本執(zhí)行引擎 GroovyScriptEngine。它在這里的作用是拿到腳本的 Class 類型,然后使用 Groovy 語言的 InvokerHelper 靜態(tài)幫助類創(chuàng)建一個腳本對象。

    接下來做的就是核心了:

    script.metaClass.invokeMethod = getMethodInterceptor() script.metaClass.static.invokeMethod = getMethodInterceptor() script.metaClass.methodMissing = getMethodMissingInterceptor()

    它將腳本對象實例的方法調(diào)用都委托給了攔截器 methodInterceptor。Groovy 對元編程非常友好。可以直接對方法進行攔截。攔截器源碼如下:

    /*** Method interceptor for any method called in executing script.* Calls are logged on the call stack.*/public methodInterceptor = { String name, Object[] args ->// register method call to stackint depth = Thread.currentThread().stackTrace.findAll { it.className == delegate.class.name }.size()this.registerMethodCall(delegate, depth, name, args)// check if it is to be intercepteddef intercepted = this.getAllowedMethodEntry(name, args)if (intercepted != null && intercepted.value) {intercepted.value.delegate = delegatereturn callClosure(intercepted.value, args)}// if not search for the method declarationMetaMethod m = delegate.metaClass.getMetaMethod(name, args)// ...and call it. If we cannot find it, delegate call to methodMissingdef result = (m ? this.callMethod(m, delegate, args) : delegate.metaClass.invokeMissingMethod(delegate, name, args))return result}

    它做了三件事情:

  • 將調(diào)用方法名和參數(shù)寫入到 callStack 中
  • 如果被調(diào)用方法名是被注冊了的方法,則執(zhí)行該方法對象的 mock。下文會詳細介紹。
  • 如果被調(diào)用方法沒有被注冊,則真正執(zhí)行它。
  • 需要解釋一個第二點。并不是所有的共享庫中的方法都是需要攔截的。我們只需要對我們感興趣的方法進行攔截,并實現(xiàn) mock 的效果。

    寫到這里,有些讀者朋友可能頭暈了。筆者在這里進行小結(jié)一下。

    因為我們不希望共享庫腳本中的依賴于 Jenkins 運行時的方法(比如拉代碼的步驟)真正運行。所以,我們需要對這些方法進行 mock。在 Groovy 中,我們可以通過方法級別的攔截來實現(xiàn) mock 的效果。 但是我們又不應(yīng)該對共享庫中所有的方法進行攔截,所以就需要我們在執(zhí)行單元測試前將自己需要 mock 的方法進行注冊到 helper 的 allowedMethodCallbacks 字段中。methodInterceptor攔截器會根據(jù)它來進行攔截。

    在 BasePipelineTest 的 setUp 方法中,框架注冊了一些默認方法,不至于我們要手工注冊太多方法。以下是部分源碼:

    helper.registerAllowedMethod("sh", [Map.class], null) helper.registerAllowedMethod("checkout", [Map.class], null) helper.registerAllowedMethod("echo", [String.class], null)

    registerAllowedMethod 各參數(shù)的作用:

    • 第一個參數(shù):要注冊的方法。
    • 第二參數(shù):該方法的參數(shù)列表。
    • 第三參數(shù):一個閉包。當(dāng)該訪問被調(diào)用時會執(zhí)行此閉包。

    以上就是框架的基本原理了。接下來,再介紹幾種場景。

    幾種應(yīng)用場景

    環(huán)境變量

    當(dāng)你的共享庫腳本使用了 env 變量,可以這樣測試:

    binding.setVariable('env', new HashMap()) def script = loadScript('setEnvStep.groovy') script.invokeMethod("call", [k: '123', v: "456"]) assertEquals("123", ((HashMap) binding.getVariable("env")).get("k"))

    binding 由 BasePipelineTest 的一個字段,用于綁定變量。binding 會被設(shè)置到 gse 中。

    調(diào)用其它共享庫腳本

    比如腳本 a 中調(diào)用到了 setEnvStep。這時可以在 a 執(zhí)行前注冊 setEnvStep 方法。

    helper.registerAllowedMethod("setEnvStep", [LinkedHashMap.class], null)

    希望被 mock 的方法能有返回值

    helper.registerAllowedMethod("getDevOpsMetadata", [String.class, String.class], {return "data from cloud" })

    后記

    不得不說 Jenkins Pipeline Unit testing framework 框架的作者非常聰明。另外,此類技術(shù)不僅可以用于單元測試。理論上還可以用于 Jenkins pipeline 的零侵入攔截,以實現(xiàn)一些平臺級特殊的需求。

    附錄

    • 共享庫官方文檔:https://jenkins.io/zh/doc/book/pipeline/shared-libraries/
    • 本文示例代碼:https://github.com/zacker330/jenkins-pipeline-shared-lib-unittest-demo
    • JenkinsPipelineUnit:https://github.com/jenkinsci/JenkinsPipelineUnit

    作者:翟志軍

    轉(zhuǎn)載于:https://my.oschina.net/jenkinszh/blog/3055265

    總結(jié)

    以上是生活随笔為你收集整理的如何对 Jenkins 共享库进行单元测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。