Jenkins Shared Libraries教程(一): 开发框架搭建
2019-03-13
Jenkins Shared Libraries是一种扩展Jenkins Pipeline的技术,通过编写Shared Libraries可以实现自定义的Steps,将流水线逻辑中重复或共通的部分进行抽象和封装。 实践中每个DevOps团队都应该通过维护一个或多个Shared Libraries项目再结合第三方的Jenkins插件定制团队自己的Jenkins流水线。
1.Shared Libraries项目初始化 #
Shared Libraries项目的工程化首先要选型构建工具,这里选择gradle:
使用gradle初始化一个空项目:
1mkdir shared-lib
2
3gradle init
4
5Select type of project to generate:
6 1: basic
7 2: cpp-application
8 3: cpp-library
9 4: groovy-application
10 5: groovy-library
11 6: java-application
12 7: java-library
13 8: kotlin-application
14 9: kotlin-library
15 10: scala-library
16Enter selection (default: basic) [1..10] 1
17
18Select build script DSL:
19 1: groovy
20 2: kotlin
21Enter selection (default: groovy) [1..2] 1
22
23Project name (default: shared-lib):
注意这里选择的是1: basic
的项目模板,这是因为jenkins shared lib项目的目录结构与常见maven工程结构不同,所以我们初始化一个空白的gradle工程,后边手动配置出项目的具体结构。
根据官方文档Extending with Shared Libraries 中描述Shared Libraries的目录结构:
1(root)
2+- src # Groovy source files
3| +- org
4| +- foo
5| +- Bar.groovy # for org.foo.Bar class
6+- vars
7| +- foo.groovy # for global 'foo' variable
8| +- foo.txt # help for 'foo' variable
9+- resources # resource files (external libraries only)
10| +- org
11| +- foo
12| +- bar.json # static helper data for org.foo.Bar
一个shared libraries项目的标准代码结构由三部分组成:
src
目录中是标准的Groovy代码vars
目录中是依赖于Jenkins运行环境的Groovy脚本resources
目录中是静态资源文件
下面按照shared libraries项目的标准结构,在前面使用gradle创建的空白项目shared-lib
创建对应目录src
, var
, test
, resources
(这里比标准目录结构多创建了一个test目录将用于存放对shared libraries的单元测试用例):
1.
2├── build.gradle
3├── gradle
4│ └── wrapper
5│ ├── gradle-wrapper.jar
6│ └── gradle-wrapper.properties
7├── gradlew
8├── gradlew.bat
9├── resources
10├── settings.gradle
11├── shared-lib.iml
12├── src
13├── test
14└── vars
因为shared-lib
项目并不遵循标准的maven目录结构,所以需要在build.gradle中针对源码目录做出配置,下面修改build.gradle项目:
1plugins {
2 id 'groovy'
3}
4
5sourceSets {
6 main {
7 groovy {
8 srcDir 'src'
9 srcDir 'vars'
10 }
11 resources {
12 srcDir 'resources'
13 }
14 }
15 test {
16 groovy {
17 srcDir 'test'
18 }
19 }
20}
21
22repositories {
23 mavenCentral()
24}
25
26targetCompatibility = 1.8
27sourceCompatibility = 1.8
28
29configurations {
30 ivy
31}
32
33tasks.withType(GroovyCompile) {
34 groovyClasspath += configurations.ivy
35}
36
37dependencies {
38 implementation 'org.codehaus.groovy:groovy-all:2.5.7'
39 implementation 'com.cloudbees:groovy-cps:1.29'
40 def ivyDep = 'org.apache.ivy:ivy:2.4.0'
41 ivy ivyDep
42 implementation ivyDep
43
44 testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.1'
45 testImplementation 'junit:junit:4.12'
46 // spock
47 testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
48 testImplementation 'org.objenesis:objenesis:3.0.1'
49 testImplementation 'cglib:cglib-nodep:3.2.12'
50}
上面的build.gradle
定义了项目具体的源码结构,同时引入了groovy
依赖以及用于单元测试的jenkins-pipeline-unit
。
同时还引入了JUnit4和Spock的依赖,可以编写Java JUnit或Groovy Spock两种风格的单元测试。
2.使用JenkinsPipelineUnit编写单元测试 #
下面通过一个简单例子介绍一下JenkinsPipelineUnit单元测试框架的使用。
在vars中创建一个简单log.groovy
脚本:
1def info(message) {
2 echo "INFO: ${message}"
3}
4
5def warn(message) {
6 echo "WARNING: ${message}"
7}
上面的脚本定义了两个全局方法,正常我们在Jenkinsfile中是这样使用:
1@Library('shared-lib') _
2
3log.info 'Starting'
4log.warn 'Nothing to do!'
如果需要对Shared Libraries进行集成测试,可以编写Jenkinsfile放到Jenkins中创建Job去运行。 但实际开发Shared Libraries的过程中我们需要通过大量的单元测试用例去完成单元测试,并达成一定的测试覆盖率。
如果你对Groovy和Spock还不太熟悉,推荐编写JUnit风格的单元测试,因为Groovy是兼容Java的,可以渐进式的学习,写单元测试只是为了达到测试Jenkins Shared Library的目的,选择适合自己的工具即可。 不过还是强烈推荐使用Spock编写更简洁的测试代码。
2.1 使用JUnit编写单元测试 #
下面编写log.groovy
的单元测试,创建logTest.groovy
:
1import com.lesfurets.jenkins.unit.BasePipelineTest
2import org.junit.Before
3import org.junit.Test
4import static org.junit.Assert.*;
5import static org.hamcrest.CoreMatchers.*;
6
7class logTest extends BasePipelineTest {
8
9 def log
10
11 @Before
12 void setUp() {
13 super.setUp()
14 log = loadScript("vars/log.groovy")
15 }
16
17 @Test
18 void logInfo() {
19 log.info("info message")
20 assertThat(helper.methodCallCount("info"), is(1L))
21 }
22
23 @Test
24 void logWarn() {
25 log.warn("warn message")
26 assertThat(helper.methodCallCount("warn"), is(1L))
27 printCallStack()
28 }
29
30}
通过执行gradle test
命令运行单元测试。
2.2 使用Spock编写单元测试 #
由于JenkinsPipelineUnit并不支持Spock Specification,所以我们编写一个PipelineSpecification
集成自spock.lang.Specification
,这样后边编写Spock测试类时都继承自PipelineSpecification
即可。
1import com.lesfurets.jenkins.unit.BasePipelineTest
2import spock.lang.Specification
3
4class PipelineSpecification extends Specification {
5
6 @Delegate BasePipelineTest basePipelineTest
7
8 def setup() {
9 basePipelineTest = new BasePipelineTest() {}
10 basePipelineTest.setUp()
11 }
12
13}
注意上面使用@Delegate
将com.lesfurets.jenkins.unit.BasePipelineTest
的属性和方法外推到PipelineSpecification
,所以在PipelineSpecification
的子类中可以直接使用这些属性和方法。
下面是使用Spock编写的logTest.groovy
测试:
1class logGroovyTest extends PipelineSpecification {
2
3 def log
4
5 def setup() {
6 log = loadScript("vars/log.groovy")
7 }
8
9 def 'log info'() {
10 when:
11 log.info("info message")
12 then:
13 helper.methodCallCount("info") == 1
14 helper.callStack.find {call -> call.methodName == 'info'}.args[0] == 'info message'
15 }
16
17 def 'log warn'() {
18 when:
19 log.warn('warn message')
20 then:
21 helper.methodCallCount('warn') == 1
22 helper.callStack.find {call -> call.methodName == 'warn'}.args[0] == 'warn message'
23 }
24}