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