В данной статье хочу поделиться содержанием pipeline.jenkinsfile с минимальной необходимостью для организации автоматизированного тестирования. Установку, настройку самого Jenkins мы рассматривать не будем, только pipeline и его содержание для АТ.

Автотесты в нашем случае будут написаны с использованием:

  • java

  • maven

  • junit5

  • allure

Бонусом расскажу как сделать запуск сборки по действию в GitLab.

Что мы хотим от Jenkins?

  • Автоматический запуск тестов по расписанию (ночные прогоны регресса)

  • Хранение самого файла с pipeline в репозитории с исходным кодом автотестов

  • Использование переменных, параметров и сredentials

  • Шаги 

    • клонирование репозитория

    • запуск автотестов с возможностью выбора тестового набора (Test Suite)

    • формирование allure-отчета

  • Нотификация на почту о завершении прогона

pipeline.jenkinsfile

И так, сначала сразу результат, а потом посмотрим на него более детально.

pipeline {
    agent {
        node {
            label 'jenkins-vm.company.ru'
        }
    }
    triggers {
            cron('H 7 * * *')
    }
    parameters{
        choice(choices: ['regress', 'smoke'], description: 'Выбор сьюта для запуска', name: 'suiteToRun')
    }
    environment{
        mailRecipients = 'dminakova@cinimex.ru'
        TEST_CREDS = credentials('444444444')
    }
    stages {
        stage('clone repo') {
            steps{
                checkout([$class                           : 'GitSCM', branches: [[name: '*/master']],
                          doGenerateSubmoduleConfigurations: false,
                          extensions                       : [],
                          submoduleCfg                     : [],
                          userRemoteConfigs                : [[credentialsId: '22222222',
                                                               url          :  'https://git.company.ru/java_example.git']]
                          ])
            }
        }
        stage('run tests') {
            steps{
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    withMaven(jdk: 'JDK 8u172', maven: 'Maven 3.6.3') {
                        sh 'mvn clean install -Dgroups=${suiteToRun}'
                    }
                }
            }
        }
        stage('run allure reports') {
            steps {
                allure([includeProperties: true,
                                      jdk: '',
                               properties: [],
                        reportBuildPolicy: 'ALWAYS',
                                  results: [[path: '**/allure-results']]
                ])
            }
        }
    }
    post {
        always{
            echo 'Pipeline is complete'
            emailext (
            subject: "CMXQA.TESTS Отчет прогона тестов [${env.BUILD_NUMBER}] ",
            body:"""Подробный allure-отчет: "<a href='${env.BUILD_URL}allure/'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>"</p>""",
            to: "${env.mailRecipients}"
            )
        }
    }
}

Ссылка на GitHub: https://github.com/DaryaMin/jenkins_pipeline_at/tree/master


Pipeline детально

Рассмотрим pipeline автотестов более детально.

Сборка автотестов чаще всего содержит следующие блоки:

  1. Переменные

  2. Параметры

  3. Credentials

  4. Триггеры сборки

  5. Этапы/stages

  6. Нотификация

  7. Pipeline from SCM


1. Переменные

Pipeline поддерживает объявление переменных, которые можно в нем дальше использовать.

Для этого используется кодовое слово environment. Переменные перечисляются через запятую по принципу ключ = 'значение'.

В дальнейшем вызываются при помощи ${env.<название переменной>}, например: ${env.mailRecipients}

environment {
        allureResults = 'target/allure-results',
        allureReportPolicy = 'ALWAYS',
        mailRecipients = 'test@cinimex.ru, dminakova@cinimex.ru'
    }

к содержанию

2. Параметры

В Pipeline можно указать некие параметры сборки с возможностью выбора при ручном старте сборки и дефолтным значением для автоматического старта по триггеру. Значения параметров могут использоваться как значения параметров самого приложения, например: url стенда, название сьюта для запуска и т.п.

Кодовое слово - parameters.
Параметры имеют тип, название, значение по умолчанию и описание. 
Параметры бывают разных типов:

  1. string

    Поддерживает строчный параметр, например:
    parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }

  2. text
    Может содержать несколько строк, например:
    parameters { text(name: 'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '') }

  3. booleanParam
    Параметр принимающий значения истина/ложь, например:
    parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }

  4. choice
    Параметр, который содержит уже конкретные значения к выбору. Часто используется для выбора сьюта (регресс, смок, дебаг), т.к. названия сьютов редко меняются.
    parameters { choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '') }

  5. password
    Параметр предназначенный для пароля.
    parameters { password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'A secret password') 

  6. отдельным типом заслуживающим особенного внимания является gitParameter. Этот тип не входит в базовую версию Jenkins и добавляется плагином Git Parametr. parameters { gitParameter(name: 'BRANCH', defaultValue: 'develop', description: 'Ветка, из которой деплоимся', branchFilter: 'origin/(.*)', type: 'PT_BRANCH', selectedValue: 'DEFAULT')}

Пример синтаксиса параметров:
pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')

        text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')

        booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')

        choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')

        password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"

                echo "Biography: ${params.BIOGRAPHY}"

                echo "Toggle: ${params.TOGGLE}"

                echo "Choice: ${params.CHOICE}"

                echo "Password: ${params.PASSWORD}"
            }
        }
    }
}
Отображение при ручном запуске сборки
Отображение при ручном запуске сборки
Результат вывода в консоль jenkins
Результат вывода в консоль jenkins

к содержанию


3. Credentials

Обратим внимание, что в выводе в консоль Jenkins нас предупреждает о том, что выводить значение пароля в echo используя Groovy String interpolation не является безопасным. 

Как же избежать возможной утечки пароля в данном случае?!

Самый простой вариант - это заменить двойные кавычки на одинарные.
О данной особенности не стоит забывать и при работе с методом credentials().

Результат выполнения

Метод credentials() поддерживает несколько вариантов:

  • secret text

  • usernames and passwords

  • secret files

  • Jenkins' Snippet Generator для других типов

Создание credential в Jenkins
  1. Перейдем в раздел credentials

  2. Выберем папку

  3. Выбираем домен

  4. Здесь отображаются уже существующие credentials. Их можно отредактировать. Нас интересует команда меню: Add credentials

  5. В нашем случае выберем тип Username and Password. Также существует плагин для Jenkins для интеграции с Vault.  Подробней https://plugins.jenkins.io/hashicorp-vault-plugin/

    Чек-бокс Treat username as secret не отображает username на списке credentials
    ID если не будет заполнено, сформируется автоматически

Для того чтобы credentials можно было использовать, необходимо объявить переменную:

environment {

        TEST_CREDS = credentials('4444444444444')

    }

здесь TEST_CREDS - это название переменной, а в скобках указан ID credentials

Также стоит отметить, что при обращении по имени переменной данные получаются в формате user:password. При необходимости получения непосредственно user или password к названию переменной добавляется _USR или _PSW соответственно. Объявлять эти переменные дополнительно не требуется.

Обращение к значению переменной осуществляется за счет указания знака$ перед названием переменной.

Пример

Добавим в наш пайплайн credentials и выведем их значение:

pipeline {
    agent {
        node {
            label 'jenkins-vm.company.ru'
        }
    }
 
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
 
        text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
 
        booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
 
        choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
 
        password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
    }
    environment {
        TEST_CREDS = credentials('4444444444')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
 
                echo "Biography: ${params.BIOGRAPHY}"
 
                echo "Toggle: ${params.TOGGLE}"
 
                echo "Choice: ${params.CHOICE}"
 
                echo 'Password: ${params.PASSWORD}'
                 
                echo 'from credentials: $TEST_CREDS'
                 
                echo "user from credentials: $TEST_CREDS_USR"
                 
                echo "password from credentials: $TEST_CREDS_PSW"
            }
        }
    }
}

Результат выполнения:

В данном случае даже использование двойных кавычек не выводит конкретное значение в консоль. Но все равно интерполяция является небезопасной, о чем нас предупреждает Jenkins. И рекомендуется использовать одинарные кавычки.

к содержанию


4. Триггеры сборки

Существует несколько вариантов триггеров сборки:
- Удаленный запуск сборки 
- Старт сборки после окончания сборки другого проекта
- Запуск периодически
- По изменениям в GitLab
- Триггер из артифактори
- Триггеры GitHub
- Scm изменения 

Самый популярный для запуска автотестов - это периодический запуск, например: ежедневно в 7 утра. Чтобы по началу рабочего дня было актуальное состояние прогона тестов.

Пример, ежедневный запуск в рабочие дни в 7.00: H 7 * * 1-5 

Синхронизация с GitLab

Полностью строка звучит "Build when a change is pushed to GitLab. GitLab webhook URL: https://company.jenkins.ru/project/TEST/ExampleMVN" 
Из названия нам потребуется URL. Именно на этот адрес GitLab будет отправлять события.

По каким же событиям Jenkins стартует сборку?

  1. любой Push в репозиторий

  2. Push в случае удалении ветки

  3. Создание любого Merge Request

  4. Создание Merge Request содержащего новые  коммиты

  5. Принятие  Merge Request

  6. Закрытие Merge Request

  7. Подтверждение Merge Request

  8. Если Merge Request содержит конкретный коммент

А теперь посмотрим,  что нужно сделать со стороны Gitlab.

В меню "Настройки" следует выбрать пункт "Webhooks"
В меню "Настройки" следует выбрать пункт "Webhooks"

Нам понадобится URL и Secret Token.
URL - указан в Jenkins когда мы проставляли чек-бокс по триггеру сборки. В нашем случае это https://company.jenkins.ru/project/TEST/ExampleMVN

Secret Token  - также берем из Jenkins из настроек триггера сборки по изменениям в Gitlab. Точнее, то значение которое мы укажем в Gitlab должно совпадать с указанным в Jenkins.

Генерация Secret Token 

  1. Необходимо перейти в Расширенные настройки триггер:

  2.  Нажать Generate 

  3. Скопировать сгенерированный код и вставить его в GitLab

После добавления webhook он отобразится внизу страницы

Можно проверить что все настроено корректно, нажав на кнопку Test. В случае если все сделано корректно, то появится уведомление 

к содержанию


5. Этапы/stages 

Основное описание того что проиcходит в Pipeline заключается как раз в блоке Stages. Блок Stages должен содержать минимум один этап (stage).  Рекомендуется делать этапы дискретными, например: Build, Test, and Deploy.

Каждый этап (stage) содержит:

  1. название, которое отображается в консоле при выполнении. Указывается в круглых скобках в одинарных кавычках

  2. непосредственно шаги выполнения (steps)

stages {
    stage('Example') {
        steps {
            echo 'Hello World'
        }
    }
}

5. 1 Клонирование репозитория

Одним из основных этапов сборки что для разработчиков, что для тестирования является интеграция с репозиторием для получения актуального состояния исходного кода. https://www.jenkins.io/doc/pipeline/steps/git/

В данном случае нам помогает плагин Jenkins https://plugins.jenkins.io/git/

Существует два варианта клонирования репозитория:

  • git step

  • checkout step

git step является более простым вариантом и соответственно менее функциональным.
checkout step лучше использовать с SCM checkout method. 

Рассмотрим простейший вариант git step. Подробнее про SCM checkout method можно прочитать https://plugins.jenkins.io/git/#plugin-content-pipeline-examples

Пример Pipeline, в котором клонируюется репозиторий
pipeline {
    agent {
        node {
            label 'jenkins-vm.company.ru'
        }
    }
    stages {
        stage('clone repo') {
            steps {
                git url: 'https://git.company.ru/java_example.git',
                credentialsId: '22222222',
                branch: 'master'
            }
        }
    }
}
Результат выполнения Pipeline
Результат выполнения Pipeline

Пример c checkout:

Stage('clone repo') {
    steps{
        checkout([$class : 'GitSCM', branches: [[name: '*/master']],
        doGenerateSubmoduleConfigurations: false,
        extensions                       : [],
        submoduleCfg                     : [],
        userRemoteConfigs                : [[credentialsId: '2222222222',
        url          :  'https://git.company.ru/java_example.git']]]
                )
            }
        }

5.2 Запуск тестов

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

Запускаются они стандартной командой mvn (в данном случае) mvn clean install. Также можно указать версию jdk, maven. Команда запуска полностью дублируется той что мы запускаем у себя локально из консоли. Например: можно добавить запуск конкретного тестового набора (Test Suite).

stage('run tests') {
     steps{
          withMaven(jdk: 'JDK 8u172', maven: 'Maven 3.6.3') {
                             sh 'mvn clean install'
           }
     }
 }
Запуск конкретного сьюта автотестов
  1. Для этого в тестах у нас имеется аннотация Tag.

Значения SMOKE и REGRESS вынесены в отдельный класс как константы. И уже на их значение настраивается Pipeline

public class SuiteName {
    public static final String SMOKE = "smoke";
    public static final String REGRESS= "regress";
}
  1. В пайплайн добавим параметр:

parameters{choice(choices: ['regress', 'smoke',],
                  description: 'Выбор сьюта для запуска',
                  name: 'suiteToRun')}
  1. Отредактируем команду запуска:

sh 'mvn clean install -Dgroups=${suiteToRun}'

В итоге при запуске сборки запустятся только те автотесты, которые помечены соответствующей аннотацией.

к содержанию

5.3 Формирование отчета

После прогона тестов нам бы хотелось получить некий отчет о результатах выполнения автотестов.

Jenkins по умолчанию формирует отчет об упавших тестах, из которого видно:
- Кем был осуществлен запуск
- Сколько по времени выполнялось
- В каком репозитории лежат исходники
- Результаты теста в виде количества упавших тестов и с какой ошибкой, если провалиться в конкретный тест

Данный вариант не особо удобен в использовании и мало информативен для сотрудников, не связанных с написанием автотестов. Более читабельный формат с описанием непосредственно шагов автотеста позволяет сформировать allure report.  Jenkins умеет также с ним работать  https://www.jenkins.io/doc/pipeline/steps/allure-jenkins-plugin/

Чтобы Allure отчет был информативен нужно уделять внимание исходному коду и аннотациям Allure.  

Подробнее про Allure: официальная документация: https://docs.qameta.io/allure/#_junit_5

Для того, чтобы jenkins формировал allure-отчет, необходимо добавить в pipline соответствующий этап:


stage('run allure reports') {
            steps {
                allure([includeProperties: true,
                                      jdk: '',
                               properties: [],
                        reportBuildPolicy: 'ALWAYS',
                                  results: [[path: '**/allure-results']]
                ])
            }
        }

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

к содержанию


6. Нотификация

Post блок состоит из одного или нескольких шагов выполняющихся либо после конкретного stage либо после всех stage, в зависимости от его расположения в pipeline.

Блок post может содержать следующие условия выполнения:

  • always

  • changed

  • fixed

  • regression

  • aborted

  • failure

  • success

  • unstable

  • unsuccessfulcleanup

Подробнее https://www.jenkins.io/doc/book/pipeline/syntax/#post

Мы прогнали автотесты, сформировали отчет, но пока никто не знает об этом. 
Наша следующая задача - рассказать об этом всем заинтересованным лицам.
Варианты тут могут быть разные:

  • нотификация о начале тестирования (не в блоке Post)

  • нотификация только в случае упавших тестов

  • нотификация всегда по завершении

  • прочие варианты, которые можно придумать)

Но все они базируются на процессе нотификации.  Рассмотрим вариант нотификации по почте.

Отправка письма на почту

Для отправки письма на почту есть команда emailext(). Подробнее https://plugins.jenkins.io/email-ext/

Добавим в наш pipeline отправку сообщения, указав:

  • тему сообщения

  • тело (в теле добавим сразу ссылку на allure отчет)

  • адресатов

post {
    always{
        echo 'Pipeline is complete'
        emailext (
            subject: "CMXQA.TESTS Отчет прогона тестов [${env.BUILD_NUMBER}] ",
            body:"""Подробный allure-отчет: "<a href='${env.BUILD_URL}allure/'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>"</p>""",
            to: "dminakova@cinimex.ru"
        )
    }
}

Pipeline from SCM

Ну и в завершении, хотелось бы отметить: bestPractice является хранение файла jenkins.pipeline вместе с кодом

В проекте создаем файл в корневом каталоге и называем его piplene.jenkinsfile. И это же название указываем в Script Path.

В самом файле указываем все то что у нас было в jenkins

Комментарии (6)


  1. fishca
    27.12.2023 18:47

    Спасибо, пригодится в качестве шаблона.


  1. magnus80
    27.12.2023 18:47

    Спасибо, сохранил себе. Небольшая ремарка-правильно называть "свит", а не "сьют".


    1. Darmina Автор
      27.12.2023 18:47

      Спасибо за комментарий, даже никогда не задумывалась как Suite корректно произносится. Будем избавляться от жаргонизмов и называть своими словами - тестовый набор)


  1. MisterKot
    27.12.2023 18:47

    А в чем преимущество такого подхода перед, например, freestyle job? Кроме того, что этот jenkinsfile можно хранить в vcs.


    1. Darmina Автор
      27.12.2023 18:47
      +1

      С моей точки зрения, freestyle job используется при "несложных" проектах. Если же нужно, что-то более кастомное, то тут нам на помощь приходит Pipeline. Пример: отправка уведомления на почту. В freestyle нет возможности указать тело сообщения, в Pipeline - можно.

      Ну и если верить официальному сайту Jenkins к преимуществам Pipeline относится:

      • Код: Pipelines реализованы в коде и обычно возвращаются в систему управления версиями, что дает командам возможность редактировать, просматривать и итерировать конвейер доставки.

      • Долговечность: Pipelines могут выдерживать как запланированные, так и незапланированные перезапуски контроллера Jenkins.

      • Пауза: Pipelines могут при желании останавливаться и ждать ввода или утверждения человеком, прежде чем продолжить выполнение конвейера.

      • Универсальность: Pipeline поддерживает сложные требования CD реального мира, включая возможность разветвления/соединения, зацикливания и выполнения работы параллельно.

      • Расширяемость: плагин Pipeline поддерживает настраиваемые расширения для своего DSL (domain-specific language)  и несколько вариантов интеграции с другими плагинами.

      Также более детально они рассказывают о разнице в видео: https://youtu.be/IOUm1lw7F58


  1. MisterKot
    27.12.2023 18:47

    Смотрите, что я увидел.
    Вы для автотестов предлагаете писать pipeline, при этом в вашем примере я не вижу ничего такого, чего нельзя было бы сделать через freestyle job. Если я перепишу ваш пайплайн на freestyle job, он разве станет "несложным"?

    Как инженер, я хотел бы видеть какую-то аргументацию посильнее, чем Если же нужно, что-то более кастомное, то тут нам на помощь приходит Pipeline. Пример: отправка уведомления на почту. В freestyle нет возможности указать тело сообщения, в Pipeline - можно. Как минимум по той причине, что для тех же уведомлений на почту есть плагин Email Extension (https://plugins.jenkins.io/email-ext/), который позволяет настраивать что угодно и сейчас ставится чуть ли не по дефолту. Аналогично и с плагином для слака — полная кастомизация (справедливости ради добавлю, что через пайплайн можно настроить еще лучше, но для автоматизации это излишне — нам же по сути нужен статус прогона и ссылка на него).

    Аргументы с официального сайта, конечно, хороши, но это не ваш опыт. Сравните хотя бы картину пайплайна по ссылке с тем, что описано у вас. И далее по списку преимуществ — часто ли у вас перезапускается контроллер Jenkins, часто ли вам нужно писать реально сложные пайплайны с ветвлениями и зацикливаниями, часто ли вы используете настраиваемые расширения для своего DSL? Почему-то кажется, что вообще нет. Что по всем этим пунктам ответ будет отрицательный. И в таком случае получается, что у такого подхода для автотестов нет преимущества. Для полноценного процесса развертки + тестирования + деплоя — да. Но статья не про это. Ну и следом был бы вопрос — а чем этот подход лучше написания скрипта на груви?

    Не поймите неправильно, статья отличная, но для автоматизации тестирования я только один аргумент увидел — хранение в vcs. Но и на это можно возразить, что если джобы настолько сложные, что их нужно где-то хранить, то скорее всего что-то делаете не так.