Привет! Меня зовут Антон, я ведущий инженер по тестированию в Ozon: занимаюсь созданием и поддержкой end-to-end Go-тестов бэкенда для QA.

Мы довольно долго писали тесты в основном на Python. Go – молодой язык, и популярных устоявшихся инструментов у него пока немного. В Python есть pytest, в Java – JUnit и TestNG, в Go – пока что весьма свободно. 

Однажды, в очередной раз переписав группу старых Python-тестов, я решил, что надо что-то менять. Эта мысль в итоге привела меня к созданию нашей собственной опенсорс-библиотеки – с поддержкой Allure без перегрузки интерфейса, инфраструктурой для хранения тестов как в одних репозиториях с сервисами, так и в отдельных, репортами в Slack и разными другими штуками.

Почему мы всё-таки решили создать своё решение, с какими сложностями пришлось разбираться в процессе и как это может пригодиться вам для тестов на Go, я расскажу в этой и следующих статьях. Сегодня – об интеграции с Allure.

Для контекста: Go в Ozon 

Большая часть сервисов в Ozon написана на Go, и со временем их количество только растёт. 

Профит от Go в сервисах:

  • инфраструктура: относительно лёгкий докер, отлаженные джобы в GitLab CI для запуска интеграционных и юнит-тестов, линтеринг и код-ревью;

  • пакетная система на Git-репозиториях: не нужно забивать себе голову хранением и импортом вспомогательных библиотек; удобная система версионирования, основанная на Git-тегах;

  • относительно низкий порог входа: можно нанимать людей и доучивать их до нужного уровня в Go – довольно легко и почти безболезненно.

Всё это на руку нам в E2E-тестах: можно поднимать вспомогательные сервисы, писать библиотеки и службы – и использовать для этого инструменты разработчиков.

Вот только потенциал этот мы использовали не в полной мере: разработку вели на Go, а тесты писали на Python. Это был не всегда удобный, но привычный сценарий для E2E-тестирования на фоне небольшого числа готовых инструментов, которые предоставлял язык на тот момент. 

Кроме того, хотелось достичь большего взаимопонимания между QA-специалистами и разработчиками, вовлекая последних в поддержку и развитие инструментов тестирования: с переходом QA-специалистов на Go проблема решается автоматически. 

Первым делом: поддержка Allure 

Итак, мы задались вопросом об альтернативе Python для тестирования Go-бэкенда. 

Первый шаг – сформулировать требования, чего мы хотим от альтернативного инструментария.

Тесты должны писаться легко и быстро (а значит, быть недорогими в поддержке). Инструменты должны быть простыми и понятными для людей из разных команд с разным бэкграундом.

Но главное – поддержка Allure. Мы в Ozon активно его используем для сбора статистики и аналитики по тестам. 

Изучив опыт коллег (привет, Vivid Money и Lamoda тут на Хабре) и проведя исследование, я обнаружил следующее:

  • официального решения Allure для Go на тот момент не существовало;

  • провайдеры есть (например, dailymotion/allure-go), но мне решительно не нравился их интерфейс, или они давно не обновлялись, как будто их перестали поддерживать (GabbyyLS/allure-go-common);

  • фреймворков тестирования не так много, самым перспективным выглядел Testify.

Ну что ж, тогда напишем свой провайдер для ​​Allure. 

Нам потребуется:

  • пробрасывать Allure-отчёты;

  • компоновать тесты;

  • запускать тесты.

Была надежда, что компоновку и запуск удастся сделать на Testify, но выяснилось, что тот не поддерживает плагины. Окей, напишем сами.

Так и появился наш опенсорс-проект Allure-Go.

Покажу несколько примеров его использования.

Что именно делает Allure-Go

Во-первых, как и Testify, наша библиотека позволяет организовывать тесты в тестовые классы структуры. Это делает код тестов понятнее и помогает явно выделять тест-комплекты, использовать Before и After-хуки. При этом сохраняется возможность более привычного для Go написания тестов как функций (также с использованием этих хуков).

Пример тестовой структуры:

type SuiteStruct struct {
  suite.Suite
}

func (s *SuiteStruct) Test1() {
}

func (s *SuiteStruct) Test2() {
}

func TestRun(t *testing.T) {
  runner.RunSuite(t, new(SuiteStruct))
}

Пример теста-функции: 

func TestSampleDemo(t *testing.T) {
  r := runner.NewTestRunner(realT)

  r.WithBeforeEach(func(t *provider.T) {})

  r.WithAfterEach(func(t *provider.T) {})

  r.Run("My test 1", func(t *provider.T) {})
  r.Run("My test 2", func(t *provider.T) {})
  r.Run("My test 3", func(t *provider.T) {})
}

Кроме того, Allure-Go предоставляет разные возможности для работы с шагами и Allure-лейблами. Интерфейс мы постарались сделать максимально лаконичным и user-friendly, прокинули через обёртки provider.T и suite.Suite.

Пример простого теста с вложенными шагами:

type StepTreeDemoSuite struct {
  suite.Suite
}

func (s *StepTreeDemoSuite) TestInnerSteps() {
  s.Epic("Demo")
  s.Feature("Inner Steps")
  s.Title("Simple Nesting")
  s.Description(`
     Step A is parent step for Step B and Step C
     Call order will be saved in allure report
     A -> (B, C)`)
  
  s.Tags("Steps", "Nesting")

  s.WithNewStep("Step A", func() {
     s.NewStep("Step B")
     s.NewStep("Step C")
  })
}

Ответ от Allure:

Важно: не перегрузить интерфейс

Особое внимание мы уделили тому, чтобы не перегрузить интерфейс вложенными вызовами.

Давайте сравним с dailymotion:

func TestNewTest(t *testing.T) {
  allure.Test(
     t,
     allure.Description("New Test Description"),
     allure.Action(func() {
        allure.Step(
           allure.Description("Step description"),
           allure.Action(func() {
             
           }))
     }))
}

То же самое в Allure-Go:

type SuiteStruct struct {
  suite.Suite
}

func (s *SuiteStruct) TestNewTest() {
  s.Description("New Test Description")
  s.WithNewStep("Step description", func() {

  })
}

Кажется, стало лаконичнее.

Что мы получили

За полгода в продакшене — 0 проблем с заливкой отчётов, минимальный порог вхождения. Как следствие, коллеги стали интересоваться решением — на сегодняшний день втянулось и переехало более 30 команд в Ozon.

Прямо сейчас Allure-Go умеет следующее:

  • интегрировать Allure в Go-тесты (поддерживает шаги и аттачи, предлагает лаконичный интерфейс);

  • собирать тесты в сьюты;

  • запускать тесты в Go.

Планы:

  • зарелизить нашу библиотеку в официальный репозиторий Allure (Qameta Software);

  • добавить новые фичи: асинхронные степы через конструкцию запуска goroutine; обёртка ассертов в шаги (на базе ассертов Testify), прокидывание интерфейсов для упрощения расширения и другие.

Библиотека пережила три рефакторинга, сменила два репозитория и активно развивается. Система репортов обеспечивает графиками и статистикой более 40 сервисов. Основываясь на положительных отзывах, нашу библиотеку добавили в конвенцию для Go-тестов как рекомендованную к использованию. 

Главный результат, на мой взгляд: мы сделали ещё один шаг в сторону реальности, в которой Dev и QA говорят на одном языке. Разработчики помогают с поддержкой и развитием тестов, тестировщики шире и глубже ориентируются в кодовой базе. 

В следующих эпизодах поговорим об инфраструктуре, репортах и безопасности. Оставайтесь с нами! 

Короче, встречайте

Allure-Go на GitHub. Надеюсь, будет полезно. Ждём ваших пулл-реквестов. 

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


  1. Tan_tan
    22.02.2022 16:05
    +2

    Ухты, интересно)) Какие-то подводные камни при использовании библиотеки, ограничения? Нет ли проблем с параллелизацией тестов с вашей библиотекой?


    1. koodeex Автор
      22.02.2022 17:00
      +1

      Привет!

      Хороший вопрос, постараюсь ответить развернуто :)

      По скольку сьюты практически полностью вдохновлены аналогом с testify, то при их использовании действительно нельзя параллелить тесты в рамках одного сьюта, как и в том самом testify. Вот ссылка на оригинальную issue.

      Однако, если важно держать тесты в рамках сьютов, то эту проблему можно легко обойти запуская параллельно сьюты между собой.

      Также, если важно запускать отдельные тесты параллельно, есть функционал из пакета runner, который позволяет запускать тесты параллельно между собой.

      То есть, решить эту проблему на уровне имплементации тестов более чем реально и даже никаких костылей не понадобится :)

      Причины же проблем параллелизации в рамках тест-сьюта достаточно глубокие, и строится вокруг спецфики работы с тестовым контекстом (указатель testing.T). Что в testify, что в нашей allure-go, этот самый указатель один на сьют, от которого и запускаются все тесты под капотом. Асинхронная работа приводит к тому что несколько рутин одновременно пытаются изменить состояние исходного указателя testing.T, от которого и был запущен тест, что приводит к конфликтам и ошибкам, описанным в исходной ишью.

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