Найти тему
(java || kotlin) && devOps

Модульные тесты Jenkins pipeline - а это вообще возможно?

Всем привет!

Раз уж заговорили про тесты расскажу про отладку и тестирование Jenkins pipeline. Для начала: отладка и тестирование Jenkins pipeline - это боль(((

Почему?

1) pipeline пишется на Groovy, а Groovy - это язык с динамической типизацией. Динамическая типизация хорошо подходит для небольших скриптов, но как только кода становится много - код сыпется, править его становится страшно.

Да, есть аннотация @groovy.transform.CompileStatic, но см. п.2

2) pipeline пишется не просто на Groovy, а на Groovy DSL. Стандартный Jenkins предоставляет ряд команд, они же шаги, плюс их число расширяется плагинами. Список см. https://www.jenkins.io/doc/pipeline/steps/

Т.е классов, описывающих синтаксис pipeline просто нет.

3) чтобы протестировать pipeline вживую нужно время. На ожидание свободного Jenkins slave, на скачивание кода пайплайн. Т.е. цикл обратной связи получается большим.

Что же можно сделать.

1) как только пайплайн перестает быть элементарным или появляется общий код - выносим его в shared library: https://www.jenkins.io/doc/book/pipeline/shared-libraries/

Она может содержать как DSL - в папке vars, так и обычные Groovy классы в папке src. А на обычные классы можно и нужно писать модульные тесты.

Как вариант - пайплайн и библиотека могут быть в одном репозитории, тогда библиотеку из pipeline можно загрузить так:

library identifier: "selflib@${scm.branches[0]}", retriever: legacySCM(scm)

В скрипте pipeline, который указан в джобе, оставляем минимум кода. Почему это полезно кроме упрощения тестирования - код в pipeline по сути является статическим инициализатором и выполняется одновременно с загрузкой в память скрипта pipeline.

2) Максимум кода выносим в src, т.к. его легко тестировать и код можно писать как будто это не скрипт сборки, а приложение для CI\CD.

Собственно именно так к коду и нужно относится.

3) создаем проект. Логичным выглядит использование Gradle, т.к у нас все-таки Groovy.

Вы можете вызывать в своем коде Java и Groovy стандартные библиотеки, следовательно, чтобы подсветка синтаксиса и автоподстановка работали, надо соответствующим образом настроить проект.

Кроме того стоит добавить в проект зависимости Jenkins Core и плагины. Я говорил, что для шагов pipeline нет соответствующих классов, но кроме шагов у Jenkins есть открытое и полузакрытое API, которое можно вызвать из PIpeline.

Примеры:

com.cloudbees.groovy.cps.NonCPS

hudson.AbortException

Полный список библиотек в сети я не нашел, да и не нужен он, я гуглил по нужным мне классам. Если нужен моя заготовка - обращайтесь.

4) подключаем в проект GDSL https://www.tothenew.com/blog/gdsl-awesomeness-introduction-to-gdsl-in-intellij-idea/

Это механизм, позволяющий объявить контекст, это может быть контекст Jenkins scripts, контекст определенного класса, контекст Groovy closure, контекст Closure внутри другого Closure в определенным именем... и объявить какие методы и свойства доступны в этом контексте.

IDEA умеет подсвечивать синтактсис и автодополнять код по GDSL https://st-g.de/2016/08/jenkins-pipeline-autocompletion-in-intellij

Есть одна тонкость - файл нужно скачивать из своего Jenkins, чтобы файл с описанием DSL включал методы из ваших плагинов Jenkins.

При этом гарантии, что полученный GDSL будет включать все нет, это не так, т.к. зависит от добросовестности авторов плагинов. Я файл дописывал, добавляя недостающее и корректируя типы.

4) с GDSL есть одна засада. Он работает в скриптах с DSL кодом, но не в исходниках из папки src. Т.к контест другой. А часто ваши классы будут обвертками над вызовами pipeline DSL.

Решение есть - из скрипта передаем this, это контекст скрипта, он содержит все доступные DSL методы, в классе объявляем поле groovy.lang.Script script и модифицируем GDSL файл, добавляя все методы и свойства DSL в контекст класса groovy.lang.Script.

vars/process.groovy

...

def processor = new Processor(this)

...

src/Processor.groovy

@TupleConstructor

class Processor {

Script script

}

Может возникнуть вопрос - что такое groovy.lang.Script? По сути это аналог Object для Groovy скриптов. Да, в Groovy код может быть не только в классах, но и в скриптах https://docs.groovy-lang.org/latest/html/documentation/#_scripts_versus_classes

5) В API своих классов не используем def, всегда объявляем типы явно.

def - это как var в Java, но опаснее, т.к в отличие от Java его можно использовать везде вместо указания типа. Как по мне - использовать def можно только для локальных переменных, т.е по сути я за подход Java.

6) кроме подсветки синтаксиса рекомендую периодически вызывать Inspect Code в IDEA, а лучше повесить его на commit. Не все проверки по умолчанию актуальны для Jenkins pipeline кода, лишние можно отключить.

7) ну и возвращаясь к тестам - по максимуму покрываем код в src тестами. Можно использовать JUnit и Mockito. Тесты при этом пишем на Groovy, чтобы воспользоваться преимуществом компактного синтаксиса Groovy.

8) JenkinsPipelineUnit - https://github.com/jenkinsci/JenkinsPipelineUnit По сути набор моков для запуска кода pipeline.

Что может:

  • запуск пайплайн из файла и из строки
  • передача параметров и переменных среды
  • проверка статуса выполнения джобы
  • моки для ряда методов pipeline
  • загрузка shared library
  • возможность добавлять свои моки на команды pipeline или конкретные вызовы sh
  • печать стектрейса выполнения pipeline

з) сравнение стректрейсов, поиск по вхождению - можно искать были ли выполнена та или иная команда

Из мелких косяков - требует наследования тестового класса от BasePipelineTest, что вышло из моды с появлением Unit 4)))

Из более крупных косяков - по умолчанию многие команды Jenkins DSL не замоканы, при появлении такой команды джоба падает.

То что падает - это правильно, мы же тестируем pipeline. Но часто приходится писать свои mock, примеры: readYaml, readProperties, findFiles.

Mock по умолчанию - ничего не делать. echo выводит данные в лог на машине разработчика.

Могу рекомендовать с ремаркой - моки придется дописывать.

9) Jenkins Test Harness - https://www.jenkins.io/doc/developer/testing/,

Это интеграционное тестирование pipeline. В документации фреймворк предлагается для тех, кто разрабатывает Jenkins или плагины для него.

Можно ли использовать для тестирования своего pipeline и shared libraries - вопрос, дам на него ответ позже.

Коммиты в репозитории есть с 2016 года, но в документации по ссылке выше до сих пор встречаются TODO.

Подключение к тестам в примерах происходит через Rule из JUnit 4 - что тоже намекает.

Что он может:

Также как и JenkinsPipelineUnit

  • запуск пайплайн из файла и из строки
  • передача параметров и переменных среды в джоб
  • проверка статуса выполнения джоба

Кроме того:

  1. проверка записей в логе джоба - как я понял, это в большинстве случаев будет заменой Assert
  2. мок для загрузки из SCM
  3. загрузка файлов из среды разработки в workspace джоба

Пока рекомендовать не могу, буду исследовать.

10) com.mkobit.jenkins.pipelines.shared-library - https://github.com/mkobit/jenkins-pipeline-shared-libraries-gradle-plugin,

Это плагин Gradle для разработки shared libraries. Включает в себя два предыдущих фреймворка. Есть тестовый репо https://github.com/mkobit/jenkins-pipeline-shared-library-example, если взять его как основу для своего проекта - получите из коробки подключение ряда библиотек Jenkins для declarative pipeline, некую версию gdsl и готовый проект, который содержит модульные и интеграционные тесты и проходит build.

Выглядит интересно для начала разработки, я к сожалению в свое время его упустил, по сути сделав аналогичный каркас)

Причем для разработки scripted pipeline мой каркас подходит лучше)

Пока рекомендовать не могу, учитывая комментарии выше.

11) любые тесты не на 100% заменяют запуск с реальными интеграциями. Как организовать интеграционное и функциональное тестирование pipeline, что для этого нужно?

  • а) создаем или копируем тестовый Java проект, который будем собирать. Ключевое требование - небольшой размер кода, чтобы сборка была быстрой и максимальное использование фичей pipeline. Использование настоящих проектов - плохо, т.к. создаются левые tags, build statuses, что может вводить разработчиков в заблуждение
  • б) тестовые джобы на Jenkins для всех созданных вами pipeline. Можно даже создать джобу, запускающую в параллель все эти джобы
  • в) тестовый проект SonarQube
  • г) тестовые репозитории в Nexus\Artifactory
  • д) тестовый проект на вашем Git сервере если джобы что-то делают в Git
  • е) важно: описываем в документации чек-лист - что и когда нужно тестировать при внесении изменений в pipeline
  • ж) придерживаемся описанных нами правил, это важно)

#devops #ci #unittests #jenkins #groovy