Автоматизация тестов на Go + Allure

от автора

В этой статье расскажу:

  • как писала интеграционные тесты на Go

  • с какими проблемами столкнулась

  • с какими библиотеками и инструментами работаю

Эта статья для тех:

  • кто впервые столкнулся с Go, как когда-то я

  • кому интересно, как можно взаимодействовать с Go в тестировании

  • кто не знает, с чего начать

О чем будем говорить:

По терминам

Unit-тестирование (Модульное тестирование) заключается в тестировании этого отдельного модуля.

Интеграционное тестирование отвечает за тестирование интеграции между сервисами.

Allure — это библиотека, которая позволяет формировать отчеты на человекочитаемом языке. В нем есть пошаговая структура с предусловиями и постусловиями. Также можно отслеживать историю прохождения тест-кейсов и статистику. С его помощью классно отчитываться боссам о тестировании =)

Выбор языка Go и Allure

В компании разработка сервисов пишется на нескольких языках: Java, Kotlin, Scala и Go. В моей команде — Go.  Поэтому было решено, что тесты будут написаны тоже на Go, чтобы разработчики могли помочь с автоматизацией инструментов и поддержкой тестов.

В тестах самое главное — это понимание, что уже протестировано, где падает и как падает. Для этого нужны понятные отчеты. У нас развернут allure-сервер, поэтому необходимо было интегрироваться с Allure.

Почему выбрана Allure-go библиотека

Фреймворка от создателей Allure для языка Go, к сожалению, нет (пока нет).

На момент написания тестов я нашла всего две библиотеки для интеграции Allure с Go

Allure-go-common — написана 6 лет назад. И никак не изменялась, а хотелось, чтобы была какая-то поддержка библиотеки в случае чего; отсутствует какая-либо документация.

Allure-go — недавно написана, есть документация с многочисленными примерами и множеством функций.

Собственно, из всего выбрала allure-go по функционалу и удобству работы с ним.

Как выглядит Allure-go

Сравнение с java

Если кто-то, как я, пришел из Java, то расскажу немного про отличия.

Про структуру кода:

  • Java — понятная структура. Код пишется в src/main/, тесты лежат в src/test/

  • Go — нет четкой структуры, есть только рекомендации. Юнит тесты обычно лежат рядом с пакетом, т.е в папке будет, например, пакет sender.go и рядом будет в этой же папке sender_test.go (по постфиксу _test можно понять, что это файл с тестами)

Как выглядит тест на Java + Allure

Нужно написать аннотацию @Test

@Test func sendTransactionSuccеedTest() {     String clientID = config.GetClientID();     String trxID = apiSteps.SendTransaction(clientID);     Transaction trx = dbSteps.GetTransactionByID(trxID);     assertSteps.CheckTransactionFields(trx); }

В шаге указываем аннотацию @Step

@Step(“Send transaction with clientID \\\\d+“) func (Steps s) SendTransaction(String clientID) {     String msg = parseJSON();     msg.SetClient(clientID);     s.trxClient.Send(msg); }

В Go нет аннотаций, как в Java. Чтобы описать шаг, тут не обойдешься @step.

Структура такая же, только вместо аннотации нужно добавить метод allure.step. Внутри step-a нужно добавить описание в методе allure.description, вторым аргументом передается allure.Action — где указывается функция с действием.

func doSomething(){     allure.Step(allure.Description("Something"), allure.Action(func(){         doSomethingNested()     })) }

Описание Теста с методом allure.Test:

func TestPassedExample(t *testing.T) {     allure.Test(t,                 allure.Description("This is a test to show allure implementation with a passing test"),                 allure.Action(func() {                     s := "Hello world"                     if len(s) == 0 {                       	t.Errorf("Expected 'hello world' string, but got %s ", s)                     }                 })) }

Больше примеров можно найти тут.

Далее опишу, как это выглядит в моих тестах.

Assertion-ы в go, выбор библиотеки

В тестах я использую assertion-ы. Assertion-ы — это функции для сравнения ожидаемых значений с реальными. Функции, с помощью которых мы понимаем, в какой момент упал тест и с какой ошибкой.

Я не стала использовать стандартные библиотеки для проверки значений, потому что в интеграционных сценариях сложные проверки объектов, и подходящая библиотека с assertion-ами облегчит читаемость и сам тест.

Также, одна из важных функций assertion-а в тестах, это неявные (умные) ожидания — когда ожидаешь какое-то событие в течение промежутка времени.

Критерии поиска библиотеки:

  1. Совместимость с Go, Allure

  2. Наличие документации

  3. Умные ожидания

  4. Возможность проверять несколько полей в объектах, сами объекты с разными условиями

  5. Вывод всего объекта, если есть ошибка в одном из полей (soft assertion)

Сравнительная таблица assertion библиотек по этим критериям

Библиотеки

General info

Документация

Cовместимость с Go, Allure

Умные ожидания

Сложные проверки объектов, soft assertion

Go стандартная библиотка

Для подержки автоматизаци тетстирования в Go

Есть

Есть

Нет

Нет

testify

Библиотека с assertion-ами и вспомогательными функциями для моков.

Есть

Есть

Нет

Нет

gocheck

Небольшая библиотека с assertion-ами. Позиционируется, как расширение стандартной библиотеки go test. Имеет похожую функциональность с testify.

Есть

Есть

Нет

Нет

gomega

Библиотека с assertion-ами. Адаптирована под работу с BDD фреймворком Ginkgo. Но может и работать и с другими фреймворками.

Есть

Есть

Есть

Есть

gocmp

Предназначена для сравнения значений. Более мощная и безопасная альтернатива рефлексии reflect.DeepEqual

Есть

Есть

Нет

Нет

go-testdeep

Предоставляет гибкие функции для сложного сравнения значений. Переписан и адаптирован из Test::Deep perl module

Есть

Есть

Нет

Есть

Из всего перечисленного мной была выбрана Gomegа — четко описанная документация с примерами, множество функций для сравнения объектов и, самое главное, функции eventually и consistently, которые позволяют делать умные ожидания.

Также, отмечу, что есть много BDD-тестовых фреймворков, которые адаптированы для go вместо Allure. Например. Gingko, Goconvey, Goblin. И если нет обязательств использовать Allure, то стоит их тоже рассмотреть. Выбирайте библиотеки исходя из своих собственных задач.

Статьи со сравнением тестовых фреймворков

O gomega

Что же такое Gomega?

Пример assertion-a

  • Можно использовать Ω:

Ω(ACTUAL).Should(Equal(EXPECTED)) Ω(ACTUAL).ShouldNot(Equal(EXPECTED))
  • Или Expect:

Expect(ACTUAL).To(Equal(EXPECTED)) Expect(ACTUAL).NotTo(Equal(EXPECTED)) Expect(ACTUAL).ToNot(Equal(EXPECTED))

Как выглядит в тесте с Allure:

allure.Test(t, allure.Action(func() {     allure.Step(allure.Description("Something"), allure.Action(func() {       gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "Text error")     })) }))

Как выглядит асинхронные ассершены (умные ожидания) в Gomega:

Eventually(func() []int {         return thing.SliceImMonitoring }, TIMEOUT, POLLING_INTERVAL).Should(HaveLen(2))

Функция eventually запрашивает указанную функцию с частотой (POLLING_INTERVAL), пока возвращаемое значение не будет удовлетворять условию или не истечет таймаут.

Удобно использовать при запросе данных из базы, которые должны появиться в течение какого-то времени. (В разделе «Работа с базой данных» есть пример)

Consistently — функция проверяет, что результат соответствует ожидаемому в течение периода времени c заданной частотой.

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

Пример:

Consistently(func() []int {         return thing.MemoryUsage() }).Should(BeNumerically("<", 10))

Как интегрировать gomega с allure

Gomega адаптирована для работы с BDD фреймворком Gingko (нам он не нужен, но в доке описана интеграция с ним, и это поможет нам поменять на allure фреймворк).

В качестве примера в документации описано, что для Gingko надо зарегистрировать обработчик, перед началом тест-сьюта:

gomega.RegisterFailHandler(ginkgo.Fail)

Когда gomega assertion фейлится, gomega вызывает GomegaFailHandler (эта функция вызывается с помощью gomega.RegisterFailHandler())

Мне нужен был Allure, поэтому нужно было написать свой FailHandler.

Какая была проблема. Изначально при внедрении gomega я регистрировала gomega c помощью RegisterTestingT, как на примере ниже

func TestFarmHasCow(t *testing.T) {     gomega.RegisterTestingT(t)      f := farm.New([]string{"Cow", "Horse"})     gomega.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow") }

Итог: столкнулась, что в Allure отчете отсутствовали ошибки от gomega. Выводился отчет с тестом, что шаг зафейлился (как на скрине), но причина ошибки не выводилась.

Как исправить. Чтобы добавить метод для обработки ошибок, нужно написать свою функцию wrapper, которая будет вызываться при фейле, и добавить туда метод allure.Fail (вызывает allure.error, сохраняет результат и статус теста — fail, сохраняет стектрейс ошибки)

//наш кастомный wrapper func BuildTestingTGomegaFailWrapper(t *testing.T) *types.GomegaFailWrapper {      //вызов функции fail             fail := func(message string, callerSkip ...int) {          // добавление вызова allure.fail для выгрузки в отчет ошибки         allure.Fail(errors.New(message))              t.Fatalf("\\n%s %s", message, debug.Stack())     }     return &types.GomegaFailWrapper{         Fail:        fail,         TWithHelper: t,     } }

Перед выполненим теста, нужно указать параметры для gomega и в FailHandler передать наш wrapper.BuildTestingTGomegaFailWrapper(t).Fail

func SetGomegaParameters(t *testing.T) { 		//регистрация T в gomega, чтоб не передавать t внутри методов для тестирования     gomega.RegisterTestingT(t)  		//регистрация кастомного обработчика     gomega.RegisterFailHandler(wrapper.BuildTestingTGomegaFailWrapper(t).Fail) }

В итоге в отчете появляется ошибка, и теперь понятно, что именно пошло не так.

Также еще хочется добавить, что Gomega в тестах удобна тем, что не нужно в методы передавать везде testing.T.

Про то, как устроены тесты

Для меня в самом начале был большой вопрос как выстроить удобную структуру проекта в Go для тестов. В итоге вот какая структура сформировалась:

  • api — пакеты с proto-файлами

  • pkg — пакет, где лежит весь код для тестов (шаги, которые мы вызываем для выполнения теста и assertion steps)

    • database — работа с базой

    • grpc — работа с сервисами по grpc

    • overall — хранятся верхнеуровневые шаги (если есть длинная последовательность шагов, и она постоянно повторяется, и это мешает читаемости теста, то выносим последовательность этих повторяющихся шагов и объединяем в верхнеуровневый шаг в этот пакет)

    • clients — внутри этих каталогов функционал, работающий с сервисами. Если нужно что-то отправить куда-то и получить ответ от сервиса, это клиент. Клиент берет параметры из конфига при инициализации

    • di — работа с dependency injection, чтоб не прописывать зависимости

    • config — описание работы конфига

    • assertion — проверки, каждый пакет отвечает за свою функциональную часть, если заканчивается на steps, значит там шаги теста (allure.step), если без steps, то там просто унарные проверки.

  • suites — только сами тесты, список и вызов шагов из pkg (пример указан ниже)

  • testdata — данные, используемые в тестах

  • tests — тут лежат файлы, описывающие запуск тестов, которые лежат в suites

    • runner_test.go — разные runner-ы для тестов

    • main_test.go — файл, в котором лежит func TestMain(m *testing.M) {} — это функция, в которой осуществляется запуск тестов с помощью m.Run

  • tmp — папка для выгрузки allure результатов

Как выглядит suite:

Тут два теста. Название, указаное в t.Run, будет указано в allure-отчете c нижним подчеркиванием. Либо можно дополнительно прописать allure.Name в тесте.

func TestRequest(t *testing.T, countryType string) {     t.Run("Test1: Успешная отправка запроса", func(t *testing.T) {         allure.Test(t, allure.Action(func() {           	SendRequestSuccеed(t)         }))     })     t.Run("Test2: Отправка запроса с ошибкой", func(t *testing.T) {         allure.Test(t, allure.Action(func() {           	SendingResuestWithErrorTest(t)         })) })

Как выглядит тест:

func SendRequestSuccеed(c *di.Components, t *testing.T) { // di c уже проинициализированными компонентами     common.SetGomegaParameters(t) // присвоение gomega параметров     clientID := c.Config.GetClientID()     reqD := c.apiSteps.SendRequest(clientID) // отправка запроса     req := c.DBSteps.GetRequestByID(reqID) // запрос из базы     c.assertSteps.CheckRequestFields(req) // проверка полей в базе }

Что такое шаг? Это выполнение действия для получения какого-то результата. Для каждого метода внутри будет конструкция вида allure.Step(allure.Description(), allure.Action(func() )

Пример шага с библиотекой allure-go

func (s *Steps) SendRequest(clientID string) {     allure.Step( // определение шага       allure.Description( // описание шага           fmt.Sprintf("Send request with clientID: %s", clientID)),       allure.Action(func() { // функция шага         	msg := parseJSON() // берем сообщение из шаблона         	msg.SetClient(clientID)         	s.reqClient.Send(msg)     })) }

Пример Assertion шага

func CheckRequestFields(req Request, expectedStatus req.Status, statusReason req.StatusReason){   	allure.Step(       allure.Description("Check fields of request in db"),        allure.Action(func() {       		gomega.Expect(trx.Status).Should(gomega.Equal(expectedStatus), "Status was not expected")       		gomega.Expect(trx.StatusReason).Should(gomega.Equal(statusReason), "Status reason was not expected") }

Внутри шага можно добавить еще шаги. Все это при выполнении складывается в отчет, и можно точно отследить, что происходило.

Пример теста в allure-отчете:

Также можно добавить attachment в виде json или текста (как на скрине выше — в Actual fields)

err = allure.AddAttachment(name, allure.*ApplicationJson*, []byte(fmt.Sprintf("%+v", string(aJSON)))) err := allure.AddAttachment(name, allure.*TextPlain*, []byte(fmt.Sprintf("%+v", a)))

Далее идет часть вглубь, я расскажу, как работаю с базой данных, rest-клиентом, конфигами, di и gitlab. Если интересно и еще совсем не заскучали, запаситесь чайком, кофейком)


Работа с REST-запросами

Для выполнения REST-запросов я использую библиотеку go-resty

Для того, чтобы выполнить запрос надо создать resty клиент

client := resty.New() authJSON, err := json.Marshal(authRequest) // выполнить запрос Post resp, err := client.R(). 		SetHeader("Content-Type", "application/json").     SetBody(authJSON). //тело     Post(c.url) //урл запроса

Создаем структуру Client

type Client struct {   	log *log.CtxLogger   	*resty.Client   	url string }

При выполнение программы вызывается инициализация клиента, в который передается аргументом лог и конфиг)

func NewClient(l log.Service, conf config.APIConfigResult) *Client {     return &Client{         Client: resty.New(),          log: l.NewPrefix("Сlient"),         url: conf.Auth.URL     } }

Отправка запроса

func (c *Client) Send(req Request) Response {     reqJSON, err := json.Marshal(req) //формируем json из объекта     gomega.Expect(err).ShouldNot(gomega.HaveOccurred())     resp, err := c.R().         SetHeader("Content-Type", "application/json").         SetBody(reqJSON).         Post(c.url) // поддерживает все типы GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS     gomega.Expect(err).ShouldNot(gomega.HaveOccurred())     c.log.InfofText("Response", resp.String())     // проверяем что статус 200     gomega.Expect(resp.StatusCode()).Should(gomega.Equal(http.*StatusOK*))      resp := Response{}     err = json.Unmarshal(resp.Body(), &resp) // из  json делаем объект       gomega.Expect(err).ShouldNot(gomega.HaveOccurred())     return resp // возвращаем объект ответа }

Работа с базой данных

Для работы с базами данных использую стандартную библиотеку database/sql. База данных в прокте postgresql.

Для начала работы необходимо указать импорт драйвера pq.

import (     "database/sql"     _ "github.com/lib/pq" )

Создаем структуру Client с *sql.DB

type Client struct {     *sql.DB     log *log.CtxLogger }

Инициализация клиента

func NewClient(conf config.APIConfigResult, l log.Service) *Client {         //берем из конфига  все параметры  для базы данных         dbConf := conf.DB         // создаем строку подключения, она выглядит так         dbURL := fmt.Sprintf("postgres://%v:%v@%v:%v/%v", dbConf.Username, dbConf.Password, dbConf.Host, dbConf.Port, dbConf.DBName)         //открываем соединение         db, err := sql.Open("postgres", dbURL)         gomega.Expect(err).ShouldNot(gomega.HaveOccurred())         return &Client{DB: db, log: l.NewPrefix("db.client.cardmanager")} }

Чтоб сделать запрос в базу (пример с умным ожиданием, ждем пока строка появится в таблице)

//select запрос const selectCardInfoByClient = "Select status from cards where client_id = $1"  func (c *Client) GetCardStatus(clientID string) (status string) {     gomega.Eventually(func() error {         // передаем строку запроса и параметр         err := c.QueryRow(selectCardInfoByClient, clientID).          Scan(&status) // ожидаем статус         return err     }, "60s", "1s").     //если вернулась ошибка, запрашиваем еще раз; как только ошибки нет, возвращаем статус   			Should(gomega.Succeed(), fmt.Sprintf("Not found card by client: %s in db", clientID))      c.log.InfofJSON("Card info", status)     return status }

Работа с конфигом

Все конфигурационные параметры лучше всегда хранить в отдельном файле. У меня есть несколько тестовых окружений, поэтому для каждого свои параметры хранятся в отдельных yaml файлах.

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

Пример yaml файла.

userService: 	url: "<https://testurl.org>"  dbProс:   host: dbhost.ru   username: user   dbname: userdb   password: pass   port: 5432

Далее объявляется структура, чтобы распарсить конфиг в объект

type DB struct {     Host     string `yaml:"host"`     Port     string `yaml:"port"`         DBName   string `yaml:"dbname"`     Username string `yaml:"username"`     Password Secret `yaml:"password"` }  type Client struct {           	URL string `yaml:"url"` }

И структура самого конфига

type APIConfig struct {     MainConfig       `yaml:"main"`     Logger           `yaml:"logger" validate:"required"`     DBProc     			  DB         `yaml:"dbProс" validate:"required"`     UserService       Client     `yaml:"clearing" validate:"required"` }  //результирующий конфиг, который везде будем передавать type APIConfigResult struct {     Logger     DBProcessing     DB     UserService      Client }

В параметре запуска указывается переменная env окружения

const (     LocalEnv  string = "local"     DevEnv    string = "dev"     StableEnv string = "stable" )  func getEnv() (string, error) {    if env := os.Getenv(Env); len(env) != 0 {        switch env {        caseDevEnv:          	return DevEnv, nil        caseLocalEnv:          	return LocalEnv, nil        caseStableEnv:          	return StableEnv, nil        }    }    return "", fmt.Errorf("cannot parse env variable") }

Создание APIConfigResult на основе yaml конфига

func NewAPIConfig() (APIConfigResult, error) {     var (         c      APIConfig         result APIConfigResult         err    error     )   	c.MainConfig.Env, err = getEnv() ///выбор окружения      if err != nil {       	return APIConfigResult{}, err     }      var confPath string      switch c.MainConfig.Env { //выбор конфига в зависимости от окружения         case StableEnv:       			confPath = "config-stable.yml"         case DevEnv:         		confPath = "config-dev.yml"         default:         		confPath = "config-local.yml"     }  	  //чтение конфига и запись в 'c' объект     if err := ReadConfig(confPath, &amp;c); err != nil {       	return result, errors.Wrap(err, `failed to read config file`)     }      result.Logger = c.Logger     result.DBProcessing = c.DBProcessing     result.UserService = c.UserService      return result, validator.New().Struct(c)  } func ReadConfig(configPath string, config interface{}) error {     if configPath == `` {      	 return fmt.Errorf(no config path)     }   	//чтение файла     configBytes, err := ioutil.ReadFile(configPath)       if err != nil {     	  return errors.Wrap(err, failed to read config file)     }   	//десериализация     if err = yaml.Unmarshal(configBytes, config); err != nil {        	 return errors.Wrap(err, failed to unmarshal yaml config)     }     return nil }

Выше в разделе «Работа с REST-запросами» был пример передачи конфигурационного объекта APIConfigResult в NewClient

Работа с dependency injection

Про dependency injection есть множество статей, вот тут подробно описано, что это такое, зачем он нужен, и как с ним взаимодействовать в Go.

В своем проекте использую dependency injection, потому что зависимостей становилось все больше и больше. A c помощью di удалось облегчить читаемость, избежать циклических зависимостей, упростить инициализацию. В тестах это особенно актуально, чтоб не заводить на каждый тест куча экземпляров объектов.

Для dependency injection использую библиотеку «go.uber.org/dig»

Как работает фреймворк

Фреймворк DI строит граф зависимостей на основе «поставщиков», о которых вы ему сообщаете, а затем определяет способ создания ваших объектов.

c := common.Before(t)

В ней происходит создание контейнера для инициализации компонент для dependency injection.

func Before(t *testing.T) *di.Components {     var c *di.Components     allure.BeforeTest(t,                       allure.Description("Init Components"),                       allure.Action(func() {                         	SetGomegaParameters(t)                         	var err error                         	//создание контейнера                         	c, err = di.BuildContainer()                         	gomega.Expect(err).Should(gomega.Not(gomega.HaveOccurred()),                                                   fmt.Sprintf("unable to build container: %v", dig.RootCause(err)))                       })) 		return c // возвращает все компоненты } 

В функции buildContainer

//структура с нужными нам компонентами type Components struct {     DBProcessing        *processing.Client     UserService         *user.Client     Logger log.Service     Config config.APIConfigResult }  func BuildContainer() (*Components, error) {     c := dig.New() //создание контейнера     servicesConstructors := []interface{}{         //передаем конструкторы нужных нам сервисов       config.NewAPIConfig,       log.NewLoggerService,       processing.NewClient,       user.NewClient,     } //продолжение ниже ...

В dig есть две функции provide и invoke. Первая используется для добавления поставщиков, вторая — для извлечения полностью готовых объектов из контейнера.

Список конструкторов для разных типов добавляется в контейнер с помощью метода Provide, то есть мы поставляем все зависимости в контейнер.

... 		//продолжение      for _, service := range servicesConstructors {       	//поставка конструкторов сервисов в di         err := c.Provide(service)         if err != nil {           return nil, err         }     }      comps, compsErr := initComponents(c) //инициализация     if compsErr != nil {         return nil, compsErr     }     return comps, nil }

В методе initComponents вызывается функция Invoke. Dig вызывает функцию с запрошенным типом, создавая экземпляры только тех типов, которые были запрошены функцией. Если какой-либо тип или его зависимости недоступны в контейнере, вызов завершается ошибкой.

func initComponents(c *dig.Container) (*Components, error) {     var err error     t := Components{}     err = c.Invoke(func( // вызов с запрашиваемыми типами         processingClient *processing.Client,          userService *user.Client,         logger log.Service,         conf config.APIConfigResult,     ) {         t.DBProcessing = processingClient         t.Config = conf         t.Logger = logger         t.UserService = userService     })     if err != nil {       	return nil, err     }      return &amp;t, nil }

После инициализации возвращаем наши проинициализированные компоненты.

Теперь можно использовать все созданные экземпляры объектов в тестах, обращаясь так: с.Config, с.Logger и т.д.

Генерация отчета локально и в Gitlab-CI

Allure-report локально

Чтобы сгенерировать Allure отчет локально, нужно:

  1. Скачать Allure

    brew install allure //on mac os
  2. Установить ALLURE_RESULTS_PATH в environments параметр. В этой папке будут храниться результаты после прогона тестов

    ALLURE_RESULTS_PATH=/Users/user/IdeaProjects/integration-tests/tmp/
  3. Запустить тесты с указанными environment переменными

  4. Далее необходимо сгенерировать отчет на основе результатов прогона. Для этого запускаем команду allure generate, указывая в параметре путь к папке с результатами(документация).

    allure generate /Users/user/IdeaProjects/integration-tests/tmp/allure-results --clean

И в папке allure-report теперь можно увидеть index.html с отчетом.

Gitlab-сi

Тесты запускаются в докере напротив стенда, где уже запущены все сервисы, и после прохождения результаты выгружаются на allure-сервер. Запускаются по scheduler в gitlab-ci.

Runner в Scheduler
Runner в Scheduler
Пример job-ы в .gitlab-ci.yml
Пример job-ы в .gitlab-ci.yml

В gitlab-ci.yml  в job в before-script для импорта результатов скачиваем allurectl из github

- wget <https://github.com/allure-framework/allurectl/releases/download/1.16.5/allurectl_linux_386> -O /usr/bin/allurectl

Создаем папку, куда будут выгружаться результаты.

- mkdir .allure - mkdir -p /usr/bin/allure-results

И создаем launch, в который будут записываться результаты.

- allurectl launch create --format "ID" --no-header > .allure/launch - export ALLURE_LAUNCH_ID=$(cat .allure/launch)

При запуске тестов в докере указываем  ALLURE_LAUNCH_ID.

- docker run --name tests -e ENV=stable -e ALLURE_LAUNCH_ID -e $IMAGE_NAME:dev || true true

Копируем результаты из докера

- docker cp tests:/allure-results/. /usr/bin/allure-results - ls /usr/bin/allure-results

Выгружаем с помощью команды allurectl

allurectl upload /usr/bin/allure-results 

Для запуска в гитлабе нужно прописать variables для работы с Allure

Для всех ci-систем примеры можно найти тут.

Дока по импорту результатов в гитлабе.

Пример от создателей allure в гитлабе gitlab-ci.yml


В заключение

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

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

Если есть какие-то вопросы, или о чем-то более подробно стоит написать, то жду комментариев =)

Привет всем. Меня зовут Таня. Я автоматизирую на Go уже около года в компании Vivid Money. До этого занималась 4 года автоматизацией тестов на Java.

ссылка на оригинал статьи https://habr.com/ru/company/vivid_money/blog/566940/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *