В этой статье расскажу:
-
как писала интеграционные тесты на 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-а в тестах, это неявные (умные) ожидания — когда ожидаешь какое-то событие в течение промежутка времени.
Критерии поиска библиотеки:
-
Совместимость с Go, Allure
-
Наличие документации
-
Умные ожидания
-
Возможность проверять несколько полей в объектах, сами объекты с разными условиями
-
Вывод всего объекта, если есть ошибка в одном из полей (soft assertion)
Сравнительная таблица assertion библиотек по этим критериям
|
Библиотеки |
General info |
Документация |
Cовместимость с Go, Allure |
Умные ожидания |
Сложные проверки объектов, soft assertion |
|
Go стандартная библиотка |
Для подержки автоматизаци тетстирования в Go |
Есть |
Есть |
Нет |
Нет |
|
Библиотека с assertion-ами и вспомогательными функциями для моков. |
Есть |
Есть |
Нет |
Нет |
|
|
Небольшая библиотека с assertion-ами. Позиционируется, как расширение стандартной библиотеки go test. Имеет похожую функциональность с testify. |
Есть |
Есть |
Нет |
Нет |
|
|
Библиотека с assertion-ами. Адаптирована под работу с BDD фреймворком Ginkgo. Но может и работать и с другими фреймворками. |
Есть |
Есть |
Есть |
Есть |
|
|
Предназначена для сравнения значений. Более мощная и безопасная альтернатива рефлексии reflect.DeepEqual |
Есть |
Есть |
Нет |
Нет |
|
|
Предоставляет гибкие функции для сложного сравнения значений. Переписан и адаптирован из 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, &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 &t, nil }
После инициализации возвращаем наши проинициализированные компоненты.
Теперь можно использовать все созданные экземпляры объектов в тестах, обращаясь так: с.Config, с.Logger и т.д.
Генерация отчета локально и в Gitlab-CI
Allure-report локально
Чтобы сгенерировать Allure отчет локально, нужно:
-
Скачать Allure
brew install allure //on mac os -
Установить ALLURE_RESULTS_PATH в environments параметр. В этой папке будут храниться результаты после прогона тестов
ALLURE_RESULTS_PATH=/Users/user/IdeaProjects/integration-tests/tmp/ -
Запустить тесты с указанными environment переменными

-
Далее необходимо сгенерировать отчет на основе результатов прогона. Для этого запускаем команду allure generate, указывая в параметре путь к папке с результатами(документация).
allure generate /Users/user/IdeaProjects/integration-tests/tmp/allure-results --clean
И в папке allure-report теперь можно увидеть index.html с отчетом.

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


В 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/
Добавить комментарий