어떻게: 유닛 테스트 가능한 Jenkins 공유 파이핑 라이브러리 설정

(최초 loglevel-blog.com에 발표)
이 블로그 글에서 저는 Jenkins를 위해 공유 파이프 라이브러리를 설정하고 개발하는 방법을 설명할 것입니다. 이 라이브러리는 조작하기 쉽고 JUnit과 Mockito로 단원 테스트를 할 수 있습니다.
주의: 이 박문은 좀 길어서 많은 화제와 관련이 있지만 상세하게 설명하지 않았습니다.지루한 강좌를 따르지 않으려면 GitHub에서 완전한 예시 라이브러리를 볼 수 있습니다.그 밖에 제가 개선할 수 있는 부분에 대해 궁금한 점이나 피드백이 있으면 댓글을 남겨 주시면 빠른 시일 내에 답장을 드리겠습니다.)또한 Jenkins 공유 라이브러리에 익숙하지 않다면 official docs에서 먼저 읽어야 할 수도 있습니다.
우리 갑시다!

기본 개발 설정


우선 새로운 IntelliJ IDEA 프로젝트를 만듭니다.저는 IntelliJ 사상을 이용하여 Jenkins 공유 파이프라인 개발을 진행할 것을 건의합니다. 이것은 제가 알고 있는 유일한 Java와 Groovy를 정확하게 지원하고 Gradle이 지원하는 IDE이기 때문입니다.따라서 아직 설치하지 않았다면 Windows, Linux, MacOS에서 here을 다운로드할 수 있습니다.또한 Java 개발 키트(here)가 설치되어 있는지 확인하십시오.
모든 것이 준비되면 IntelliJ를 시작하고 새 프로젝트를 만듭니다. Gradle을 선택하고 Groovy에 체크 상자를 설정하십시오.

다음은 GroupId 및 Artifactid 를 입력합니다.

다음 창을 무시하고 (기본값이 좋음) 다음을 클릭하고 항목 이름을 입력하고 완료를 클릭합니다.

IntelliJ는 새 프로젝트를 시작해야 합니다.프로젝트의 폴더 구조는 다음과 같아야 한다.

이것은 일반적인 Java/Groovy 프로젝트에 있어서는 매우 멋있지만, 우리의 목적을 위해서, 우리는 약간의 변화를 해야 한다. 왜냐하면 Jenkins는 이런 프로젝트 구조를 필요로 하기 때문이다.
(root)
+- src                     # Groovy source files
|   +- org
|       +- somecompany
|           +- 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
|       +- somecompany
|           +- bar.json    # static helper data for org.foo.Bar
따라서
  • 프로젝트 루트 폴더에 vars 폴더 추가
  • 프로젝트 루트 폴더에 resource 폴더 추가
  • 에서 src의 모든 파일/폴더를 삭제하고 org.somecompany과 같은 새 패키지를 추가합니다.
  • build.gradle 파일 편집:
  • group 'somecompany'
    version '1.0-SNAPSHOT'
    
    apply plugin: 'groovy'
    apply plugin: 'java'
    
    sourceCompatibility = 1.8
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile 'org.codehaus.groovy:groovy-all:2.3.11'
        testCompile group: 'junit', name: 'junit', version: '4.12'
        testCompile "org.mockito:mockito-core:2.+"
    }
    
    sourceSets {
        main {
            java {
                srcDirs = []
            }
            groovy {
                // all code files will be in either of the folders
                srcDirs = ['src', 'vars'] 
            }
        }
        test {
            java {
                srcDir 'test'
            }
        }
    }
    
    저장하면 Gradle 항목 가져오기가 변경됩니다.

    이때 우리의 프로젝트는 정확한 구조를 가지고 있어 Jenkins가 공유 라이브러리로 사용할 수 있다.그러나 위의 코드 세션에서 보듯이 단원 테스트에 test이라는 원본 디렉터리를 추가했습니다.프로젝트의 루트 단계에서 이 폴더를 만들고 패키지 org.somecompany을 추가할 때입니다. src에서 했던 것처럼.최종 구조는 다음과 같다.

    쿨, 우리 공유 라이브러리를 실현할 때가 됐어!

    일반 방법


    먼저 우리가 어떻게 도서관을 세웠는지, 그리고 우리가 왜 이렇게 했는지 간략하게 소개한다.
  • 우리는 var의'사용자'절차를 가능한 한 간단하고 진정한 논리가 없도록 할 것이다.반대로 우리는 모든 작업을 완성하는 클래스(src 내부)를 만들었다.
  • 필요한 모든 Jenkins 단계 (sh, bat, error 등) 를 설명하는 인터페이스를 만들었습니다.이 클래스들은 이 인터페이스를 통해서만 호출됩니다.
  • 저희는 귀하의 클래스 컴파일러 테스트를 위해 JUnit과 Mockito를 사용하는 것과 같습니다.
  • 이러한 방식으로 우리는 다음과 같은 작업을 수행할 수 있습니다.
  • Jenkins 없이 저희 라이브러리/단원 테스트를 컴파일하고 실행합니다
  • 우리의 과정이 예상대로 작동하는지 테스트
  • 테스트는 정확한 매개 변수를 사용하여
  • Jenkins 절차를 호출합니다.
  • Jenkins 단계 실패 시 코드 동작
  • 테스트
  • Jenkins 자체
  • 을 통해 구축, 테스트, 운행 지표 및 Jenkins 파이프 라이브러리 배치
    이제 우리 진정으로 시작합시다.

    Step 액세스 인터페이스


    우선, 우리는 org.somecompany 내부에 인터페이스를 만들 것입니다. 모든 종류는 이 인터페이스를 사용하여 일반적인 Jenkins 절차인 sh 또는 error에 접근할 것입니다.
    package org.somecompany
    
    interface IStepExecutor {
        int sh(String command)
        void error(String message)
        // add more methods for respective steps if needed
    }
    
    이 인터페이스는 우리의 단원 테스트에서 시뮬레이션할 수 있기 때문에 매우 깔끔하다.이렇게 하면 우리 반은 젠킨스에서 독립할 것이다.이제 vars Groovy 스크립트에서 사용할 구현을 추가합니다.
    package org.somecompany
    
    class StepExecutor implements IStepExecutor {
        // this will be provided by the vars script and 
        // let's us access Jenkins steps
        private _steps 
    
        StepExecutor(steps) {
            this._steps = steps
        }
    
        @Override
        int sh(String command) {
            this._steps.sh returnStatus: true, script: "${command}"
        }
    
        @Override
        void error(String message) {
            this._steps.error(message)
        }
    }
    

    기본 의존 항목 추가 주입


    우리는 단원 테스트에서 상술한 실현을 사용하고 싶지 않기 때문에, 단원 테스트 기간에 상술한 실현을 시뮬레이션으로 대체하기 위해 기본적인 의존항 주입을 설정할 것이다.의존항 주입에 익숙하지 않으면, 범위를 초과했다고 설명하기 때문에, 이 장에 붙여넣은 코드를 복사해서 계속할 수 있습니다.
    따라서 우선 org.somecompany.ioc 패키지를 만들고 IContext 인터페이스를 추가합니다.
    package org.somecompany.ioc
    
    import org.somecompany.IStepExecutor
    
    interface IContext {
        IStepExecutor getStepExecutor()
    }
    
    마찬가지로, 이 인터페이스는 우리의 단원 테스트에서 시뮬레이션될 것이다.그러나 정기적으로 라이브러리를 실행하기 위해서는 기본값이 필요합니다.
    package org.somecompany.ioc
    
    import org.somecompany.IStepExecutor
    import org.somecompany.StepExecutor
    
    class DefaultContext implements IContext, Serializable {
        // the same as in the StepExecutor class
        private _steps
    
        DefaultContext(steps) {
            this._steps = steps
        }
    
        @Override
        IStepExecutor getStepExecutor() {
            return new StepExecutor(this._steps)
        }
    }
    
    기본 종속 주입 설정을 완료하기 위해 현재 컨텍스트를 저장하기 위해'컨텍스트 등록표'를 추가합니다 (일반 실행 기간 DefaultContext, 단원 테스트 기간 IContext).
    package org.somecompany.ioc
    
    class ContextRegistry implements Serializable {
        private static IContext _context
    
        static void registerContext(IContext context) {
            _context = context
        }
    
        static void registerDefaultContext(Object steps) {
            _context = new DefaultContext(steps)
        }
    
        static IContext getContext() {
            return _context
        }
    }
    
    그렇습니다!이제 우리는 vars에서 테스트 가능한 Jenkins 절차를 자유롭게 작성할 수 있다.

    사용자 정의 Jenkins 작성 단계


    이 예시에서 우리는 라이브러리에 절차를 추가하고 이 절차를 호출하기를 원한다고 상상해 봅시다.NET 빌드 도구'MSBuild'를 사용하여 빌드합니다.NET 프로젝트.이를 위해, 우선groovy 스크립트 ex_msbuild.groovyvars 폴더에 추가합니다. 이 폴더는 우리가 실현하고자 하는 사용자 정의 절차라고 합니다.스크립트 이름이 ex_msbuild.groovy이기 때문에 잠시 후 Jenkinsfile에서 ex_mbsbuild을 사용하여 절차를 호출할 수 있습니다.스크립트에 다음 내용을 잠시 추가합니다.
    def call(String solutionPath) {
        // TODO
    }
    
    우리의 전체적인 생각에 따라 우리는 ex_msbuild 스크립트를 가능한 한 간단하게 하고 하나의 단원에서 테스트 클래스 내에서 모든 작업을 완성하기를 희망한다.따라서 새 패키지 MsBuild에 새 클래스 org.somecompany.build을 만듭니다.
    package org.somecompany.build
    
    import org.somecompany.IStepExecutor
    import org.somecompany.ioc.ContextRegistry
    
    class MsBuild implements Serializable {
        private String _solutionPath
    
        MsBuild(String solutionPath) {
            _solutionPath = solutionPath
        }
    
        void build() {
            IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
    
            int returnStatus = steps.sh("echo \"building ${this._solutionPath}...\"")
            if (returnStatus != 0) {
                steps.error("Some error")
            }
        }
    }
    
    보시다시피, 우리는 클래스에서 sherror 절차를 동시에 사용했지만, 우리는 그것들을 직접 사용하지 않고 ContextRegistry을 사용하여 IStepExecutor의 실례를 얻어 Jenkins 절차를 호출했습니다.이렇게 하면 우리는 잠시 후에 build() 방법에 대해 단원 테스트를 진행할 때 상하문을 교환할 수 있다.
    이제 ex_msbuild 스크립트를 완성할 수 있습니다.
    import org.somecompany.build.MsBuild
    import org.somecompany.ioc.ContextRegistry
    
    def call(String solutionPath) {
        ContextRegistry.registerDefaultContext(this)
    
        def msbuild = new MsBuild(solutionPath)
        msbuild.build()
    }
    
    우선, 우리는 상하문 등록표를 사용하여 상하문을 설정한다.단원 테스트에 없기 때문에 기본 상하문을 사용합니다.this에 전달된 registerDefaultContext()DefaultContext이 개인 _steps 변수에 저장되어 Jenkins 단계에 접근하는 데 사용됩니다.상하문을 등록한 후, 우리는 MsBuild류를 자유롭게 실례화하고, build()방법을 호출하여 모든 작업을 완성할 수 있다.
    좋습니다. 저희 vars 스크립트가 완성되었습니다.현재 우리는 MsBuild 클래스를 위해 단원 테스트를 작성하기만 하면 된다.

    셀 테스트 추가


    이때 단원 테스트를 작성하는 것은 평소와 같아야 한다.우리는 패키지 MsBuildTest이 있는 테스트 폴더에 새로운 테스트 클래스 org.somecompany.build을 만들었습니다.매번 테스트를 하기 전에 우리는 Mockito 시뮬레이션 IContextIStepExecutor 인터페이스를 사용하고 시뮬레이션의 상하문을 등록합니다.그리고 우리는 테스트에서 간단하게 새로운 MsBuild 실례를 만들고 build() 방법의 행위를 검증할 수 있다.전체 테스트 클래스에는 다음과 같은 두 가지 예제 테스트가 포함됩니다.
    package org.somecompany.build;
    
    import org.somecompany.IStepExecutor;
    import org.somecompany.ioc.ContextRegistry;
    import org.somecompany.ioc.IContext;
    import org.junit.Before;
    import org.junit.Test;
    
    import static org.mockito.Mockito.*;
    
    /**
     * Example test class
     */
    public class MsBuildTest {
        private IContext _context;
        private IStepExecutor _steps;
    
        @Before
        public void setup() {
            _context = mock(IContext.class);
            _steps = mock(IStepExecutor.class);
    
            when(_context.getStepExecutor()).thenReturn(_steps);
    
            ContextRegistry.registerContext(_context);
        }
    
        @Test
        public void build_callsShStep() {
            // prepare
            String solutionPath = "some/path/to.sln";
            MsBuild build = new MsBuild(solutionPath);
    
            // execute
            build.build();
    
            // verify
            verify(_steps).sh(anyString());
        }
    
        @Test
        public void build_shStepReturnsStatusNotEqualsZero_callsErrorStep() {
            // prepare
            String solutionPath = "some/path/to.sln";
            MsBuild build = new MsBuild(solutionPath);
    
            when(_steps.sh(anyString())).thenReturn(-1);
    
            // execute
            build.build();
    
            // verify
            verify(_steps).error(anyString());
        }
    }
    
    IntelliJ 코드 편집기의 왼쪽에 있는 녹색 재생 단추를 사용하여 테스트를 실행할 수 있습니다. 녹색이 될 수 있습니다.

    짐을 싸다


    기본적으로 이렇다.이제 Jenkins로 라이브러리를 설정할 때가 되었습니다. 새로운 작업을 만들고 Jenkins 파일을 실행하여 사용자 정의 ex_msbuild 절차를 테스트할 때입니다.간단한 테스트 파일은 다음과 같습니다.
    // add the following line and replace necessary values if you are not loading the library implicitly
    // @Library('my-library@master') _
    
    pipeline {
        agent any
        stages {
            stage('build') {
                steps {
                    ex_msbuild 'some/path/to.sln'
                }
            }
        }
    }
    
    분명히 나는 아직 말할 수 있는 것이 많다. (예를 들어 단원 테스트, 의존 주입, Gradle, Jenkins 설정, Jenkins 자체로 구축 및 테스트 라이브러리 등) 그러나 나는 이 긴 블로그 글을 간결하게 유지하고 싶다.그러나 저는 전체적인 사고방식과 방법이 명확해지고 일반적인 것보다 더 튼튼하고 조작하기 쉬운 단원 테스트 공유 라이브러리를 만드는 데 도움을 주고 싶습니다.
    마지막 건의: 단원 테스트와Gradle 설정이 매우 좋아서 건장한 공유 파이프의 개발을 간소화하는 데 도움이 되지만 불행하게도 라이브러리 테스트가 녹색이더라도 파이프 내부에 오류가 발생할 수 있는 곳이 상당히 많다.아래와 같은 일은 주로 Jenkins의 Groovy와 샌드박스가 이상하기 때문이다.
  • Serializable을 실현하지 못하는 클래스는 필수적이다. 왜냐하면 파이프는 반드시 Jenkins가 다시 시작한 후에 생존해야 하기 때문이다.
  • 은 창고에서 java.io.File 같은 종류, 즉 prohibited을 사용한다
  • Jenkinsfile
  • 의 문법 및 맞춤법 오류
    따라서 Jenkins 실례를 단독으로 통합 테스트에 사용하는 것은 좋은 생각일 수 있습니다. "오프라인"전에 새로운 스크립트와 수정된 vars 스크립트를 테스트할 수 있습니다.
    마찬가지로 댓글에 어떤 유형의 질문이나 피드백을 마음대로 쓰시고 GitHub에서 완성된 작업 예시 라이브러리를 보십시오.

    좋은 웹페이지 즐겨찾기