Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Heisenbug 2017: Строим свой тестовый фреймворк,...

Heisenbug 2017: Строим свой тестовый фреймворк, c Jenkins Pipeline и библиотеками

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

На примере Java-проектов покажем, как можно строить Pipeline-библиотеки для задач QA и переносить проекты на новую платформу. Мы интегрируем Docker, Maven, JUnit, FindBugs, Сoverity, а потом реализуем динамическую параллелизацию тестов. Также поговорим о подводных камнях и о том, как можно эффективно разрабатывать, тестировать и поддерживать подобные фреймворки.

Страничка доклада: https://heisenbug-moscow.ru/talks/2017/msk/1f0wvztnneoo0gc6oweaio/
Демо: https://github.com/oleg-nenashev/heisenbug2017-demo

Oleg Nenashev

December 09, 2017
Tweet

More Decks by Oleg Nenashev

Other Decks in Programming

Transcript

  1. 2 @oleg_nenashev, #heisenbug • Jenkins, Scripted Pipeline и библиотеки •

    Интеграции: Java, Maven, FindBugs, Cobertura • Обработка ошибок, параллелизация, инкапсуляция • Проблемы при разработке Pipeline О чём доклад? https://github.com/oleg-nenashev/heisenbug2017-demo
  2. 3 @oleg_nenashev, #heisenbug Jenkins и Pipeline “для чайников” •https://jenkins.io/doc/book/pipeline Чего

    НЕТ в докладе? Disclaimer: • Презентация отражает личное мнение докладчика • Оно может не совпадать с позицией сообщества Jenkins и/или CloudBees
  3. 4 @oleg_nenashev, #heisenbug Jenkins и Pipeline “для чайников” Blue Ocean,

    Declarative Pipeline, … •https://jenkins.io/doc/book/pipeline •Дискуссионная зона Чего НЕТ в докладе? Disclaimer: • Презентация отражает личное мнение докладчика • Оно может не совпадать с позицией сообщества Jenkins и/или CloudBees
  4. 5 @oleg_nenashev, #heisenbug Jenkins и Pipeline “для чайников” Blue Ocean,

    Declarative Pipeline, … Внутренностей Jenkins Pipeline •Митап JUG.ru 4 декабря: https://goo.gl/x9x7Zm Чего НЕТ в докладе? Disclaimer: • Презентация отражает личное мнение докладчика • Оно может не совпадать с позицией сообщества Jenkins и/или CloudBees
  5. 6 @oleg_nenashev, #heisenbug Jenkins и Pipeline “для чайников” Blue Ocean,

    Declarative Pipeline, … Внутренностей Jenkins Pipeline CloudBees Jenkins Enterprise •https://go.cloudbees.com Чего НЕТ в докладе? Disclaimer: • Презентация отражает личное мнение докладчика • Оно может не совпадать с позицией сообщества Jenkins и/или CloudBees
  6. 8 @oleg_nenashev, #heisenbug Мой Hall of fameshame • Ядро Jenkins

    • Remoting • Windows Service Wrapper • >25 плагинов Примеры плагинов
  7. 9 @oleg_nenashev, #heisenbug Русскоязычное сообщество Jenkins • Митапы в Москве,

    СПб и Минске • Gitter: #jenkinsci-ru/public • YouTube: http://bit.ly/jenkins-ru-youtube • Facebook: http://bit.ly/jenkins-ru-facebook • Twitter: @jenkins_ru Москва: Здесь может быть Ваше лого
  8. 11 @oleg_nenashev, #heisenbug TODO: Oil Pipelines Image Jenkins Pipeline pipeline

    { agent label:"linux" tools { maven ”M3" } stages { stage("build") { steps { sh 'mvn clean verify' } } } post { always { junit ”target/…/*.xml" } } } * Declarative
  9. 15 @oleg_nenashev, #heisenbug Команда А(втоматизации) + Jenkins Конфиги Инфра- структура

    Общая логика Пла- гины Документация Инфра- структура Инфра- структура
  10. 16 @oleg_nenashev, #heisenbug Марио Инженер команды А Марио знает Jenkins.

    Он не хочет автоматизировать один за всех и всех за одного
  11. 18 @oleg_nenashev, #heisenbug Луиджи нужен Pipeline Тесты #1 Тесты #2

    Тесты #3 Сборка Coverage Уведом- ления “maven clean verify”
  12. 24 @oleg_nenashev, #heisenbug Что делать Луиджи? 1. Взять компоненты из

    фреймворка 2. Собрать свой велосипед Pipeline Просто добавь клея!
  13. 26 @oleg_nenashev, #heisenbug 5 лет назад... • Freestyle • Parameterized

    Trigger • Copy Artifact • TemplateProject / Inheritance • Плагины… Чудовище Дженкинсштейна
  14. Почему Jenkins Pipeline? “Легко” использовать “Легко” разрабатывать • … as

    Code • Snippet Generator • Документация pipeline { agent label:"linux" tools { maven ”M3" } stages { stage("build") { steps { sh 'mvn clean verify' } } } post { always { junit ”target/…/*.xml" } } }
  15. 31 @oleg_nenashev, #heisenbug Прототип! 1. Jenkinsfile 2. Scripted Pipeline 3.

    Вызов Maven через Shell https://jenkins.io/doc/book/pipeline
  16. 32 @oleg_nenashev, #heisenbug Первые шаги node(‘linux’) { checkout scm sh

    “mvn clean verify” } https://jenkins.io/doc/book/pipeline
  17. 33 @oleg_nenashev, #heisenbug Публикация результатов node(‘linux’) { checkout scm sh

    “mvn clean verify -Dmaven.test.failure.ignore” junit ”target/…/*.xml” findbugs pattern: “**/target/findbugsXml.xml” } https://jenkins.io/doc/book/pipeline
  18. 36 @oleg_nenashev, #heisenbug •Cobertura Plugin •Pipeline Syntax •НО: В Cobertura

    Plugin нет шага для Pipeline •https://github.com/jenkinsci/cobertura-plugin/issues/50 Cobertura? Легко! Возможно
  19. 38 @oleg_nenashev, #heisenbug Шаг 3. Cobertura step([$class: 'CoberturaPublisher', autoUpdateHealth: false,

    autoUpdateStability: false, coberturaReportFile: ‘target/coverage.xml', failNoReports: false, failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false]) И ТАК СОЙДЁТ?
  20. 39 @oleg_nenashev, #heisenbug Шаг 3. Cobertura В каждой задаче??? step([$class:

    'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: ‘target/coverage.xml', failNoReports: false, failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false])
  21. 42 @oleg_nenashev, #heisenbug Пример Jenkinsfile с библиотекой my_imaginary_lib // Run

    build using My Imaginary Test Framework buildProject(runCobertura: true)
  22. 49 @oleg_nenashev, #heisenbug Классы Глобальные “переменные” • Живут внутри контекста

    Pipeline • Не обязательно являются переменными… Строение библиотеки
  23. 50 @oleg_nenashev, #heisenbug Cobertura – глобальная “переменная” (root) +- vars

    +- cobertura.groovy def call(String file, boolean failUnhealthy=false, boolean failUnstable=false) { step([$class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: file, failNoReports: true, failUnhealthy: failUnhealthy, failUnstable: failUnstable, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false]) }
  24. 51 @oleg_nenashev, #heisenbug Cobertura – глобальная “переменная” node(‘linux’) { checkout

    scm sh “mvn clean verify -Dmaven.test.failure.ignore -Pcoverage” junit ”target/…/*.xml” findbugs pattern: ”**/target/findbugsXml.xml” cobertura(“target/coverage.xml”) }
  25. 53 @oleg_nenashev, #heisenbug stage() – для визуализации node(‘linux’) { stage(“Checkout”)

    { checkout scm } stage(“Build”) { sh “mvn clean verify -Pcoverage” } stage(“Archive”) { junit ”target/…/*.xml” findbugs pattern: “**/target/findbugsXml.xml” cobertura(file: “target/coverage.xml”) } }
  26. 55 @oleg_nenashev, #heisenbug stage() – для визуализации node(‘linux’) { stage(“Checkout”)

    { checkout scm } stage(“Build”) { sh “mvn clean verify -Pcoverage” } stage(“Archive”) { junit ”target/…/*.xml” findbugs pattern: “**/target/findbugsXml.xml” cobertura(file: “target/coverage.xml”) } } Что не так?
  27. 59 @oleg_nenashev, #heisenbug Обработка ошибок node(‘linux’) { stage(“Checkout”) { checkout

    scm } try { stage(“Build”) { sh “mvn clean verify -Pcoverage” } } finally { stage(“Archive”) { junit ”target/…/*.xml” findbugs pattern: “**/target/findbugsXml.xml” cobertura(file: “target/coverage.xml”) } } }
  28. 60 @oleg_nenashev, #heisenbug Пример. Failover теста при инфраструктурной ошибке Системная

    ошибка (код 253) Агент №1 Агент №2 . . . . . . for (def board : boards) { echo "trying board " + board; try { node(board) { checkout scm sh ‘./bin/run.sh’ // Call passed => DONE break; } } catch (Exception ex) { if (ex.message.contains ("exit code 253")) { // Fatal error fail("Test run failed") } } } Попробовать все стенды:
  29. 63 @oleg_nenashev, #heisenbug Достаточно ли исключений? try {…} / catch

    {…} timeout(60) {…} timestamps {…} limitLogSize(“10Gb”) {…} databaseHealthCheck {…} * Не существуют, но можно сделать
  30. 64 @oleg_nenashev, #heisenbug Closure – лучший друг фреймворкостроителя (root) +-

    vars +- cobertura.groovy +- commons.groovy Object withErrorHandlers( int timeout, Closure body) { timeout(timeout) { timestamps { try { body() } catch (Exception ex) { echo "Caught exception: ${ex}" throw ex // Propagate it } } } }
  31. 65 @oleg_nenashev, #heisenbug Пример использования Closure node('linux') { stage("Checkout") {

    checkout scm } } try { stage("Build") { commons.withErrorHandlers(10) { sh "mvn clean verify -Dmaven.test.failure.ignore -Pcoverage" } } } finally { stage("Archive") { junit "target/**/TEST-*.xml" findbugs pattern: "**/target/findbugsXml.xml" cobertura("target/coverage.xml") } } }
  32. 67 @oleg_nenashev, #heisenbug Параллелизация тестов (root) +- vars +- cobertura.groovy

    +- commons.groovy +- runTests.groovy Parallel Test Executor Plugin Глобальная “переменная” +
  33. 68 @oleg_nenashev, #heisenbug tmp tmp Параллелизация тестов Parallel Test Executor

    Plugin * Для простоты: Нет обработки ошибок
  34. 69 @oleg_nenashev, #heisenbug Параллелизация тестов stage(“Build”) { node(‘linux’) { checkout

    scm try { sh “mvn clean verify -PspotChecksOnly” } finally { junit ”target/…/*.xml” findbugs(”**/target/findBugs.xml”) } } } stage(“Test”) { runTests() }
  35. 75 @oleg_nenashev, #heisenbug tmp tmp Параллелизация тестов -Pcoverage stash includes:

    'target/coverage.xml', name: “coverage-${i}.xml” cobertura(file: “target/coverage.xml”) Disclaimer: Концепт
  36. 76 @oleg_nenashev, #heisenbug Cobertura и parallel() (root) +- vars +-

    cobertura.groovy +- commons.groovy +- runTests.groovy Disclaimer: Концепт def mergeStashes(int testParallelism) { node('linux') { for (int I = 0; I < testParallelism; i++) { unstash"coverage-${i}.xml" } cobertura.merge("coverage-*.xml", "coverage-merged.xml") cobertura(file: "coverage-merged.xml") } } def merge(String pattern, String out) { //TODO: Write your own impl sh "cp coverage-1.xml ${out}" }
  37. 77 @oleg_nenashev, #heisenbug Там еще про классы было. Зачем они

    мне вообще? • Для простых случаев – не нужны
  38. 78 @oleg_nenashev, #heisenbug Там еще про классы было. Зачем они

    мне вообще? • Для простых случаев – не нужны • НО: • Все плюсы [и минусы] ООП • Builder-классы • Меньше проблем с типизацией
  39. 79 @oleg_nenashev, #heisenbug MailExtSender. Что хотим? import my.pipeline.lib.MailExtSender … stage(“Test”)

    { runTests() } stage(“Notify”) { def builder = new MailExtSender(this) .withSubject(”Test results for project Foo”) .withRecipient(”[email protected]”) .withTestReports(true) .send() } * Для простоты: Нет обработки ошибок
  40. 80 @oleg_nenashev, #heisenbug MailExt через классы (root) +- src +-

    my +- pipeline +- lib +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- runTests.groovy +- runTests.txt class MailExtSender { final def script String subject = "Undefined" List<String> recipients = [] String attachmentsPattern = null MailExtSender(def script) { this.script = script } def withSubject(String subject) { this.subject = subject return this } def withRecipient(String email) { this.recipients << email return this } …
  41. 81 @oleg_nenashev, #heisenbug MailExt через классы (root) +- src +-

    my +- pipeline +- lib +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- runTests.groovy +- runTests.txt class MailExtSender { final def script String subject = "Undefined" List<String> recipients = [] String attachmentsPattern = null MailExtSender(def script) { this.script = script } def withSubject(String subject) { this.subject = subject return this } def withRecipient(String email) { this.recipients << email return this } …
  42. 82 @oleg_nenashev, #heisenbug MailExt через классы (root) +- src +-

    my +- pipeline +- lib +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- runTests.groovy +- runTests.txt … def withTestReports(boolean add) { this.attachmentsPattern = add ? ”target/**/TEST-*.xml" : null return this } void send() { script.emailext subject: subject, body: body, attachmentsPattern: attachmentsPattern, to: recipients.join(',') } }
  43. 85 @oleg_nenashev, #heisenbug •Вызовы java.io.File не имеют смысла •Запросы должны

    выполняться через Remoting •Плагин: Pipeline Basic Steps Pipeline ВСЕГДА исполняется на мастере
  44. 87 @oleg_nenashev, #heisenbug Groovy Sandbox • Библиотеки по-умолчанию работают в

    режиме Sandbox • Ограниченный доступ к публичному API Jenkins • Аппрув вызовов через Script Security
  45. 88 @oleg_nenashev, #heisenbug ЧТО НЕ ТАК? Разрешения в Script Security:

    • static method jenkins.model.Jenkins jenkins.model.Jenkins#getInstance() • method hudson.model.Job jenkins.model.Jenkins#copy(…)
  46. 90 @oleg_nenashev, #heisenbug @NonCPS @NonCPS def dumpEnvVars() { def str

    = "Dumping build environment variables...\n" for (Map.Entry<String, String> entry : currentBuild.build().environment) { str += " ${entry.key} = ${entry.value}\n" } echo str }
  47. 92 @oleg_nenashev, #heisenbug 1. Переключаться в NonCPS ТОЛЬКО один раз

    2. Не смешивать CPS/NonCPS логику В библиотеках…
  48. 93 @oleg_nenashev, #heisenbug 1. Переключаться в NonCPS ТОЛЬКО один раз

    2. Не смешивать CPS/NonCPS логику 3. toString() и Co – всегда @NonCPS В библиотеках…
  49. 96 @oleg_nenashev, #heisenbug Pipeline. Средства разработки Интеграция с IDE Unit

    Test Framework Статический анализ Библиотеки Pipeline Средства отладки Докумен- тация Деплоймент скриптов Средства миграции на DSL Хорошо Слабовато Не хватает
  50. 98 @oleg_nenashev, #heisenbug Патч => Коммит => Запуск => Патч

    => Коммит => Запуск => Патч => Коммит => Запуск => ….?
  51. 99 @oleg_nenashev, #heisenbug Патч => Коммит => Запуск => Патч

    => Коммит => Запуск => Патч => Коммит => Запуск => ….?
  52. 101 @oleg_nenashev, #heisenbug Как я работаю? Intellij IDEA Filesystem SCM

    Plugin • Подсветка синтаксиса • Документация Jenkins Исходники (папка Git) • Configuration-as-Code • Статическая конфигурация • Библиотеки • Библиотеки • Проекты с Jenkinsfile volume volume https://github.com/oleg-nenashev/heisenbug2017-demo
  53. 102 @oleg_nenashev, #heisenbug Документирование библиотек (root) +- README.md +- LICENSE.txt

    +- … +- src +- org +- oleg +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- runTests.groovy +- runTests.txt Documentation as Code? Текстовые файлы • Сторонние средства
  54. 103 @oleg_nenashev, #heisenbug Документирование библиотек (root) +- README.md +- LICENSE.txt

    +- … +- src +- org +- oleg +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- runTests.groovy +- runTests.txt Текстовые файлы Javadoc • GMaven: compile stubs • Maven Javadoc Plugin • Работает: Классы библиотек • В процессе: “vars”
  55. 104 @oleg_nenashev, #heisenbug Документирование библиотек (root) +- README.md +- LICENSE.txt

    +- … +- src +- pipeline.gdsl +- org +- oleg +- MailExtSender.groovy +- vars +- cobertura.groovy +- cobertura.txt +- … Текстовые файлы Javadoc GDSL • Генерация по плагинам • Для библиотек автогенерации нет L https://st-g.de/2016/08/jenkins-pipeline- autocompletion-in-intellij
  56. 105 @oleg_nenashev, #heisenbug PipelineUnit • https://github.com/jenkinsci/JenkinsPipelineUnit • Вызывается из Pipeline

    “mvn verify” был вызван… И вызов успешен… Для Jenkinsfile…
  57. 106 @oleg_nenashev, #heisenbug PipelineUnit. Тестируем библиотеки String clonePath = 'path/to/clone'

    def library = library() .name('commons') .retriever(gitSource('git@orgithub/devteam/jenkins-shared.git')) .targetPath(clonePath) .defaultVersion("master") .allowOverride(true) .build() helper.registerSharedLibrary(library) // Как в предыдущем примере… loadScript("job/library/exampleJob.jenkins") printCallStack() …
  58. 108 @oleg_nenashev, #heisenbug Jenkinsfile для библиотеки • Jenkinsfile вызывает PipelineUnit

    • Результаты публикуются в JUnit Что дальше? CI/CD для библиотек
  59. 109 @oleg_nenashev, #heisenbug Jenkinsfile для библиотеки • Jenkinsfile вызывает PipelineUnit

    • Результаты публикуются в JUnit Pull Request Builder Что дальше? CI/CD для библиотек
  60. 110 @oleg_nenashev, #heisenbug Jenkinsfile для библиотеки • Jenkinsfile вызывает PipelineUnit

    • Результаты публикуются в JUnit Pull Request Builder НО: Конфликты библиотек? Folders! Что дальше? CI/CD для библиотек
  61. 113 @oleg_nenashev, #heisenbug Библиотеки… • Упрощают использование и поддержку фреймворков

    • Уменьшают сложность Jenkinsfile • Сложны, но только для разработчиков
  62. 115 @oleg_nenashev, #heisenbug Что делать? 1. Jenkins? Переводить автоматизацию Jenkins

    на Pipeline 2. Использовать библиотеки и минимизировать Jenkinsfile
  63. 116 @oleg_nenashev, #heisenbug Что делать? 1. Jenkins? Переводить автоматизацию Jenkins

    на Pipeline 2. Использовать библиотеки и минимизировать Jenkinsfile 3. Документировать и тестировать библиотеки