Рубрики
Uncategorized

Как сделать: Настройка библиотеки Jenkins Shared The Jenkins Share The Denkins Share

Учебное пособие о том, как создать библиотеку Jenkins Shared Jenkins Share, проведенную под единицей. Tagged with Jenkins, Tutorial, Testing, DevOps.

(Первоначально опубликовано на loglevelblog.com )

В этом сообщении я постараюсь объяснить, как настроить и разработать общую библиотеку трубопроводов для Дженкинса, над которой легко работать и может быть протестирована с помощью модуля с Junit и Mockito.

ПРИМЕЧАНИЕ: Этот пост в блоге довольно долгий и затрагивает многие темы, не объясняя их подробно. Если вам не хочется следовать за длительным учебным пособием, вы можете посмотреть на полную библиотеку примеров на GitHub Анкет Кроме того, если у вас есть вопросы или действительно какие -либо отзывы о том, что я могу улучшить, оставьте комментарий, и я вернусь к вам как можно скорее;) Кроме того, если вы совершенно незнакомы с общими библиотеками Дженкинса, вам, вероятно, следует сначала прочитать о них в Официальные документы Анкет

Давайте идти!

Базовая настройка разработки

Во -первых, давайте создадим новый проект Intellij Idea. Я предлагаю использовать идею IntelliJ для Denkins Shared Development, потому что это единственная IDE, о которой я знаю, которая правильно поддерживает Java и Groovy и обладает поддержкой Gradle. Итак, если у вас еще нет его установки, вы можете скачать его Здесь Для Windows, Linux или MacOS. Также обязательно установил комплект для разработки Java, который доступен Здесь Анкет

Когда все будет готово, запустите IntelliJ, создайте новый проект, выберите Gradle и обязательно установите флажок на Groovy.

Далее введите группу и артефактид.

Не обращайте внимания на следующее окно (по умолчанию все в порядке), нажмите «Далее», введите имя проекта и нажмите «Отделка».

IntelliJ должен загрузиться с вашим новым проектом. Структура папок в вашем проекте должна быть чем -то вроде следующего.

Это круто для обычных проектов Java/Groovy, но для нашей цели мы должны немного изменить ситуацию, поскольку Дженкинс требует такой структуры проекта:

(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

Так:

  1. Добавить vars Папка в корневой папке вашего проекта
  2. Добавить ресурс Папка в корневой папке вашего проекта
  3. Удалить все файлы/папки внутри SRC и добавить новый пакет, такой как org.somecompany
  4. Редактировать 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:

На этом этапе наш проект имеет правильную структуру, которая будет использоваться в качестве общей библиотеки Дженкинсом. Но, как вы могли видеть в фрагменте кода выше, мы также добавили исходный каталог для модульных тестов, называемых тест Анкет Сейчас самое время создать эту папку на корневом уровне проекта и добавить пакет org.somecompany Как мы сделали с SRC Анкет Окончательная структура должна выглядеть следующим образом.

Круто, пришло время реализовать нашу общую библиотеку!

Общий подход

Сначала кратко избавившись от того, как мы строим нашу библиотеку, и почему мы делаем это таким образом:

  • Мы будем держать «пользовательские» шаги внутри var как можно проще и без какой -либо реальной логики. Вместо этого мы создаем классы (внутри src ), которые выполняют всю работу.
  • Мы создаем интерфейс, который объявляет методы для всех необходимых шагов Jenkins ( sh , летучая мышь , Ошибка , и т.д.). Классы вызывают шаги только через этот интерфейс.
  • Мы пишем модульные тесты для ваших занятий, как вы обычно используете Junit и Mockito.

Таким образом, мы можем:

  • Скомпилируйте и выполните наши библиотечные/модульные тесты без Дженкинса
  • Проверить, что наши занятия работают как предназначены
  • Проверьте, что шаги Jenkins вызываются с правильными параметрами
  • Проверьте поведение нашего кода, когда на шаг Jenkins не удается
  • Строите, тестируйте, запустите метрики и разверните свою библиотеку трубопроводов Jenkins через самого Jenkins

Теперь давай действительно пойдем.

Интерфейс для шага доступа

Сначала мы создадим интерфейс внутри org.somecompany это будет использоваться все Занятия для доступа к обычным шагам Дженкинса, как sh или Ошибка Анкет

package org.somecompany

interface IStepExecutor {
    int sh(String command)
    void error(String message)
    // add more methods for respective steps if needed
}

Этот интерфейс аккуратный, потому что его можно высмеивать в наших модульных тестах. Таким образом, наши занятия становятся независимыми для самого Дженкинса. Пока давайте добавим реализацию, которая будет использоваться в нашем vars Groovy Scripts:

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 во время нормального выполнения и макета Mockito 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
    }
}

Вот и все! Теперь мы можем свободно кодировать тестируемые шаги Jenkins внутри vars Анкет

Кодирование настраиваемого шага Дженкинса

Давайте представим наш пример здесь, что мы хотим добавить шаг в нашу библиотеку, которая называет инструмент .NET Build «MSBuild», чтобы создать проекты .NET. Для этого мы сначала добавили отличный скрипт ex_msbuild.groovy к варс Папка, которая называется нашим пользовательским шагом, который мы хотим реализовать. Поскольку наш сценарий называется ex_msbuild.groovy Наш шаг позже будет призван с ex_mbsbuild в нашем Jenkinsfile. На данный момент добавьте следующий контент в сценарий:

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")
        }
    }
}

Как видите, мы используем оба sh и Ошибка Шаги в нашем классе, но вместо того, чтобы использовать их напрямую, мы используем Contextregistry Чтобы получить экземпляр Istepexecutor Чтобы позвонить Дженкинсу, с этим. Таким образом, мы можем отменить контекст, когда хотим, чтобы подготовить Test The 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()
}

Во -первых, мы устанавливаем контекст с реестра контекста. Поскольку мы не находимся в модульном тесте, мы используем контекст по умолчанию. это Мы переходим в RegisterDefaultContext () будет спасен DefaultContext Внутри своего частного _steps переменная и используется для доступа к шагам Jenkins. После регистрации контекста мы можем создать экземпляр нашего Msbuild класс и позвоните в build () Метод выполняет всю работу.

Приятно, наш vars Сценарий закончен. Теперь нам нужно только написать несколько модульных тестов для нашего Msbuild класс.

Добавление модульных тестов

На этом этапе написание модульных тестов должно быть обычным делом. Мы создаем новый тестовый класс Msbuildtest Внутри тестовой папки с пакетом org.somecompany.build . Перед каждым тестом мы используем Mockito, чтобы издеваться над Icontext и Istepexecutor интерфейсы и зарегистрируйте высмеивающий контекст. Тогда мы можем просто создать новый 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, создать новую работу и запустить Jenkinsfile, чтобы проверить ваш новый пользователь ex_msbuild шаг. Простой тест Jenkinsfile может выглядеть так:

// 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 довольно хороши и помогают в облегчении разработки надежных общих трубопроводов, но, к сожалению, в ваших трубопроводах все еще многое может пойти не так, даже если библиотечные тесты зеленые. Такие вещи, как следующее, это в основном случается из -за странности Дженкинса и песочницы:

  • класс, который не реализует Сериализуем Что необходимо, потому что «трубопроводы должны выжить в перезапуске Дженкинса»
  • Используя классы, такие как Java.io. Файл В вашей библиотеке, которая запрещенный
  • Синтаксис и орфографические ошибки в вашем Jenkinsfile

Поэтому было бы неплохо иметь экземпляр Jenkins исключительно для тестирования интеграции, где новые и модифицированные vars Сценарии можно проверить, прежде чем отправиться в жизнь.

Опять же, не стесняйтесь писать какие -либо вопросы или отзывы в комментариях и взглянуть на завершенную, рабочую библиотеку на GitHub Анкет

Оригинал: «https://dev.to/kuperadrian/how-to-setup-a-unit-testable-jenkins-shared-pipeline-library-2e62»