Jenkins Pipeline для АТ

2798ffa83df3c3fd0e59619c0db1f373.jpg

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

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

  • java

  • maven

  • junit5

  • allure

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

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

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

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

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

  • Шаги 

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

    • запуск автотестов с возможностью выбора сьюта

    • формирование 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-отчет: "${env.JOB_NAME} [${env.BUILD_NUMBER}]"

""", to: "${env.mailRecipients}" ) } } }

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

Pipeline детально

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

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

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

  2. Параметры

  3. Credentials

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

  5. Шаги

  6. Действия после шагов: нотификация

Переменные

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

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

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

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

к содержанию

Параметры сборки

В 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

к содержанию

Credentials

beb87253674f0126d2319f1a23b9df46.png

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

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

f91f9a093542b25e6680079c164b9c59.png

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

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

b07ecf75afe8df3b9fb6e01832b71cad.jpg

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

Создание credential в Jenkins

  1. Перейдем в раздел credentials

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

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

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

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

    0575b316b6dbc32d6b7f75263a74d44a.png

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

    d9eaaeb59a92732c0609139938fecbe7.jpg

Для того чтобы 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"
            }
        }
    }
}

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

9945e655ed30bf549531b71dbc75351c.png

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

к содержанию

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

82da1ffc3bfdf6278719698106e24a1e.png

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

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

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

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

bc9f97acc33b7edf57d57a29ff08e666.png

Полностью строка звучит «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»

cabfc0c60e5564add09568dbc57fb441.png

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

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

Генерация Secret Token 

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

    7d8db8963cade66e603a8bc0bba2f34a.png
  2.  Нажать Generate 

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

    67901fdb1692e0ace8b9f44542ea94ed.png

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

4cf39782ccd5eb1ff2082f0367d93c9b.png

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

b19a34f03dbb35d9225c52c2daa52116.png

к содержанию

Этапы/stages 

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

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

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

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

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

GIT

Одним из основных этапов сборки что для разработчиков, что для тестирования является интеграция с репозиторием для получения актуального состояния исходного кода. 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']]]
                )
            }
        }

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

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

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

stage('run tests') {
     steps{
          withMaven(jdk: 'JDK 8u172', maven: 'Maven 3.6.3') {
                             sh 'mvn clean install'
           }
     }
 }

Запуск конкретного сьюта автотестов

  1. Для этого в тестах у нас имеется аннотация Tag.

8b14dd24ac711ad19183db33b2bac6b4.png

Значения 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')}
5f1555e727b0f6f34e5e727ed4e1c9ff.png
  1. Отредактируем команду запуска:

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

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

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

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

57da9686c302f3e891872946d5434d6e.png

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 у сборки

ade9ba44c271a14c8dee40d28fb18d19.png

к содержанию

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

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

57da9686c302f3e891872946d5434d6e.png

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 отчет

ade9ba44c271a14c8dee40d28fb18d19.png449e970b9cdaaff4a6fa29ad43bcb9e2.png

к содержанию

Нотификация

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

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

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

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

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

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

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

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

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

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

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

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

post {
    always{
        echo 'Pipeline is complete'
        emailext (
            subject: "CMXQA.TESTS Отчет прогона тестов [${env.BUILD_NUMBER}] ",
            body:"""Подробный allure-отчет: "${env.JOB_NAME} [${env.BUILD_NUMBER}]"

""", to: "dminakova@cinimex.ru" ) } }

c44c86260fa37a48241edbed39e337e2.png

Pipeline from SCM

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

70248350951a2148040a40c7cc1c3b15.png407345e7e77af24627295389a03a6989.png

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

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

© Habrahabr.ru