Спасибо за подробный деф, но, честно говоря, ваши аргументы только подтвердили то, о чем я писал) Вместо решения системных проблем вы пытаетесь выдать баги проектирования за фичи
Давайте разберем вашу защиту по пунктам, потому что дьявол кроется в исходниках :)
1. Пустые параметры: вы не решили проблему, а переложили ее на пользователя
Вы пишете:
если запуск с нуля параметрами является нежеланным поведением (это не всегда так), то достаточно лишь сделать на это проверку:
В этом-то и соль. Фреймворк стоит в точке (collector.go), где он точно знает, что конкретный параметризованный тест прогонится ровно 0 раз. Но вместо Fail, Skip или паники он просто тихонько выплевывает варнинг в stderr и возвращает nil.
if len(values) == 0 {
fmt.Fprintf(os.Stderr, "testo: warning: ... will not run\n")
return nil
}
Совет "ну допишите руками проверку в BeforeAll на каждый сьют" - это классический перевод стрелок. Зачем мне тогда вообще ваш фреймворк, если я должен сам руками страховать его логику?)))
Ваше сравнение с обычным table-driven тестом некорректно. В обычном Go-коде цикл у меня перед глазами, и я сам полностью контролирую семантику выполнения:
cases := fetchCases()
if len(cases) == 0 {
t.Fatal("no cases")
}
for _, tt := range cases { ... }
В Testo же параметризация автоматизирована магией фреймворка. Он сам собирает CasesXXX, сам строит матрицу, сам планирует тесты и сам решает, запускать их или нет. В итоге ваш лозунг "критичные тесты не могут молча пропасть" превращается в тыкву: матрица параметров пустая, в stderr улетел варнинг, который на CI никто не читает, билд зеленый, мастер катится в прод без проверок.
2. Тип T — это не обычный Go-код, а рантайм-схема
Вы пишете, что структура T предназначена исключительно для подключения плагинов.
Но это как раз и доказывает мою мысль: T в Testo - это не обычный пользовательский тип. Это декларативная runtime-схема для вашего скрытого DI-контейнера. Фреймворк бесцеремонно лезет рефлексией в поля, требует только указатели, сам все аллоцирует и сам строит граф зависимостей. Шаг влево - рантайм-паника на старте:
if field.Kind() != reflect.Pointer {
panic("testo: all exported fields in T must be pointers")
}
setPlugins(field.Addr(), plugins, specs)
Ссылка на go vet - это вообще мимо. go vet - это внешний статический линтер, а не система типов Go и не компиляторные гарантии. Его можно отключить, проигнорировать, криво настроить на CI или тупо не знать про его существование :) Он никак не превращает ваши рантайм-костыли в легитимный контракт языка. Если корректность структуры проверяется не компилятором, а вашим reflection-конструктором - это рантайм-модель, как ее ни называй.
3. Два одинаковых плагина - это не антипаттерн, а нормальная композиция
Вы пишете:
Я не думаю, что есть реальный сценарий, когда необходимо иметь две регистрации одного плагина.
Говорить так - значит расписаться в непонимании гибкой архитектуры и композиции. Такой сценарий элементарен: последовательная фильтрация тестов несколькими независимыми условиями. В том же Axiom можно спокойно сделать так:
Мне нужно подключить один и тот же тип плагина тегов дважды: первый раз с конфигом из одного окружения, второй - из другого. Это один и тот же тип плагина, но две разные конфигурации и две разные стадии обработки.
В Axiom плагины - это просто слайс функций type Plugin func(cfg *Config), они хранятся в массиве и предсказуемо применяются в том порядке, в котором их передал инженер:
for _, p := range c.Runner.Plugins {
p(c)
}
В Testo это сделать невозможно физически, потому что у вас identity плагина - это reflect.Type:
Один тип - один инстанс. Все. Это не "безгранично гибкая система плагинов", это кастрированный Singleton-by-type Service Locator.
4. Потеря порядка плагинов из-за Go map
Вот тут начинается самое прекрасное))) Вы рассказываете в докладах про приоритеты и SortStableFunc, но ваш код теряет исходный порядок плагинов еще до того, как доходит до сортировки.
Вы собираете плагины в map[reflect.Type], потом спецификации складываете в map[reflect.Type]testoplugin.Spec, а потом передаете их дальше через итерацию по мапе. Как мы все знаем, порядок обхода map в Go рандомизируется на уровне рантайма языка.
Если у пользователя объявлено три плагина с дефолтным приоритетом 0, порядок выполнения их хуков превращается в рулетку. Порядок их работы - это не порядок их объявления инженером в структуре T, а следствие случайного обхода мапы. Чтобы это починить, пользователю придется руками расставлять приоритеты там, где все изначально должно было работать по цепочке.
В Axiom такой проблемы нет физически. Передал плагины в слайс - они ровно в этом порядке друг за другом и выполнились:
WithRunnerPlugins(p1, p2, p3)
// и получил применение:
p1(cfg)
p2(cfg)
p3(cfg)
Без рефлексии, без итерации по мапам и без костылей с ручной сортировкой приоритетов.
5. Сравнение с Axiom по reflection - это ложная эквивалентность
Ваша попытка ткнуть пальцем в Axiom:
Не считаю это проблемой, но справедливости ради, это же не так:
Да, в Axiom есть рефлексия в методе RunSuite. Но RunSuite в Axiom - это опциональный адаптер-переходник для тех, кто привык к старым xUnit-сьютам. Дока тык. Основной движок Axiom полностью прозрачен, декларативен и работает на чистом Go:
r := axiom.NewRunner(...)
c := axiom.NewCase(...)
r.RunCase(t, c, func(cfg *axiom.Config) { ... })
Здесь нет неявного сканирования структур, нет DI-контейнеров, нет магии завязки на имена методов CasesXXX.
В Axiom рефлексия - это сбоку прикрученный адаптер, который еще и полностью опционален
В Testo рефлексия - это фундамент, без которого фреймворк вообще не работает (даже ваш RunTest под капотом заворачивается в синглтон-сьют и идет по reflection-пути).
Архитектурный диагноз
Вы пытаетесь представить это как набор мелких, изолированных компромиссов: тут подкрутим, там пользователь сам проверит, здесь go vet подскажет. Но если сложить все эти костыли вместе, перед нами открывается совершенно четкая и пугающая архитектурная картина)
Testo - это не "тонкий фасад" и не "пара контролируемых мест с рефлексией". Вы построили полноценный, тяжелый reflection-driven runtime, который живет поверх Go и полностью игнорирует его философию:
Проход по сьютам - через неявную рефлексию
Биндинг параметров - через угадывание имен методов (CasesXXX);
Инъекция плагинов - через бесцеремонное сканирование и мутацию полей структуры
Идентификация плагинов - через жесткий singleton-by-type, убивающий композицию
Сортировка плагинов - завязан на случайный порядок обхода Go-мап.
Вы утверждаете, что проблема в одном баге с пустыми параметрами, который можно закрыть опцией. Нет. Проблема фундаментальна: вся архитектура Testo держится на неявной рантайм-интерпретации пользовательского кода.
Тот же Axiom решает абсолютно аналогичную задачу в разы чище и честнее: через явный Runner, явный Case, явный слайс плагинов и явное получение параметров в точке использования. Компилятор все видит, компилятор все проверяет :)
Главный вывод
Знаете, почему этот спор зашел в тупик? Потому что вы написали фреймворк не для Go. Вы написали его вопреки Go.
Язык Go создавался Робом Пайком и командой именно для того, чтобы навсегда избавить индустрию от неявной магии, скрытых DI-контейнеров, рантайм-паник и развесистых enterprise-абстракций в стиле Java Spring. Инженеры уходят в Go ради предсказуемости, явности (explicit over implicit) и строгого контроля компилятора.
Вы же взяли Go и попытались насильно превратить его в Pytest или Spring. В итоге получился инструмент, который борется с собственной экосистемой.
Для внутренней автоматизации Ozon под административным ресурсом это может работать годами - спору нет, там люди подневольные. Но для независимого open-source и продакшена крупных компаний строить критически важный тестовый фундамент на рантайм-магии - это огромный, неоправданный риск.
За доклад на митапе и за эту дискуссию еще раз спасибо, она получилась максимально вскрывающей. Но мы все равно выбираем предсказуемость и остаемся на Axiom
Раз уж вы пришли с кодом и логами, я не поленился, залез в ваш репозиторий Testo и детально изучил исходники :) И знаете что? Все оказалось гораздо хуже, чем просто "неидиоматичная рефлексия". Вы зашили в ядро фреймворка тяжелейшие рантайм-костыли, которые прямо противоречат вашим же заявлениям о безопасности)))
Давайте разберём по фактам то, что лежит у вас в main-ветке.
1. Дырявый CI и молча пропадающие тесты (collector.go)
Вы утверждаете, что "CI точно не будет зеленым при проблемах с параметрами". Смотрим в ваш код collector.go:
if len(values) == 0 {
fmt.Fprintf(
os.Stderr,
"testo: warning: ... will not run\n",
...
)
return nil
}
Что это значит? Если по какой-то причине (баг в логике, не доехавшие данные из бэка, пустой слайс окружения) ваш метод CasesOS() вернет пустой список - параметризованный тест просто молча не запустится. Он не падает, не помечается как Failed, не уходит в Skipped. Он просто тихо возвращает nil, плюнув варнингом в stderr
Для любого нормального CI варнинг в логах - это абсолютно зеленый билд. Критичный тест пропал из прогона, проверки не было, но пайплайн успешно прошел. Ваша "гарантия безопасности" сломалась в первом же файле
2. Это не "удобный фасад", это агрессивный DI-контейнер (construct.go)
Вы говорите, что рефлексия у вас "в паре контролируемых мест". На деле Testo пытается быть скрытым Spring-подобным DI-контейнером, который бесцеремонно вторгается во внутренности пользовательских структур. Смотрим в construct.go:
for i := range v.NumField() {
field := v.Field(i)
if !v.Type().Field(i).IsExported() {
continue
}
if field.Kind() != reflect.Pointer {
panic("testo: all exported fields in T must be pointers")
}
setPlugins(field.Addr(), plugins, specs)
}
Фреймворк рекурсивно обходит структуру T, сам аллоцирует указатели, сам подменяет поля и требует, чтобы все exported fields были строго указателями. Шаг влево, забыл звtздочку - получай runtime panic прямо на старте. Экспортируемые поля внутри T перестают быть обычным Go-кодом, они превращаются в жесткую рантайм-схему вашего фреймворка
3. Иллюзия гибкости плагинов
Вы заявляете, что благодаря плагинам Testo никогда не устареет. Но смотрим, как эти плагины хранятся в том же construct.go:
Ключом вашей мапы является reflect.Type. То есть ваша архитектура плагинов - это обычный Singleton-by-type Service Locator. Из-за этого два независимых экземпляра одного и того же плагина (например, с разной конфигурацией) внутри одной структуры T физически невозможно выразить. Они просто схлопнутся по типу. О какой "безграничной гибкости" тут речь?)))
В чем разница с нормальным подходом?
В том же Axiom, о котором статья, runtime-check используется локально и честно - только для проверки пользовательских данных в точке вызова: params := axiom.GetParams[LoginParams](cfg). Код прозрачен. Инженер видит, что и где он запрашивает.
Axiom не сканирует структуру тестов рефлексией, не строит неявный DI-контейнер по exported-полям, не паникует из-за отсутствия указателей и не завязывает логику плагинов на reflect.Type
Поэтому сорри, ребят, но вы облажались. Вы попытались исправить болячки allure-go, но вместо прозрачного Go-кода построили переусложненный комбайн, который держится на неявных рантайм-контрактах. Про такой софт обычно говорят: "работает — не трогай, сломалось — не починишь". Тащить это в продакшен крупных компаний - это лютый харам, и не только по-гошному, а вообще по любым канонам нормальной разработки.
За доклад на митапе все равно спасибо, но мы точно остаемся на предсказуемом Axiom
UPD: И да, ваш аргумент про "10 тысяч тестов в сотне команд внутри Ozon" не доказывает вообще ничего. В бигтехе внедрение внутренних инструментов слишком часто работает по принципу административного ресурса: сверху спустили архитектурную директиву "все новые сервисы пишем на Testo", и сотня команд берет под козырек, даже если плюется от рантайм-паник и переписывания общих хелперов. Реальное качество опенсорса проверяется свободным рынком и независимыми инженерами, а не корпоративной обязаловкой)))
Вы сами скинули цитату, которая полностью хоронит ваш же аргумент про "надежность крупных компаний". Ozon официально расписался в том, что забросил allure-go на жизнеобеспечение, потому что сами же завели его в архитектурный тупик. И их обещание "с Testo такого точно не повторится" - это чистой воды маркетинг. Еще как повторится, как только их новая неявная магия на рефлексии начнет сыпаться на объемах реальных компаний. Команду мейнтейнеров переведут на другие проекты, и они выпустят третий фреймворк. Доверия к такой "поддержке" ноль)))
По поводу рефлексии: если вам "в последнюю очередь важно" наличие compile-time проверок, вы просто никогда не ловили полностью зеленый CI в мастере при молча пропущенных критичных тестах из-за банальной опечатки в регистре имени метода (CasesOs вместо CasesOS):
Для пет-проектов и вебинаров "удобный фасад" важнее надежности, спору нет. Но для нормального продакшена сознательно ломать строгую типизацию Go в тест-рантайме - это профнепригодность. Посмотрите доклад внимательнее)
Поверьте, тут это еще вообще не магия. Вот ниже в комментах товарищ притащил ссылку на Testo - вот там реально ящик Пандоры)
А насчет линейности - для простых юнитов это работает, спору нет. Но на сложных интеграционных тестах с базами и контейнерами вам в любом случае придется что-то придумывать и городить обвязки
Ага, посмотрел его - тащит абсолютно ту же самую скрытую магию в гошку. Это для Go вообще харам, спасибо, не надо) Доверие к их архитектурным решениям было полностью потеряно еще на allure-go. Уж лучше сидеть на чем-то предсказуемом, где хотя бы в рантайм прозрачный и компилятор все проверяет, чем снова дебажить чужую рефлексию)))))
Информация
В рейтинге
3 728-й
Зарегистрирован
Активность
Специализация
Инженер по автоматизации тестирования, Инженер по обеспечению качества
Спасибо за подробный деф, но, честно говоря, ваши аргументы только подтвердили то, о чем я писал) Вместо решения системных проблем вы пытаетесь выдать баги проектирования за фичи
Давайте разберем вашу защиту по пунктам, потому что дьявол кроется в исходниках :)
1. Пустые параметры: вы не решили проблему, а переложили ее на пользователя
Вы пишете:
В этом-то и соль. Фреймворк стоит в точке (
collector.go), где он точно знает, что конкретный параметризованный тест прогонится ровно 0 раз. Но вместоFail,Skipили паники он просто тихонько выплевывает варнинг вstderrи возвращаетnil.Совет "ну допишите руками проверку в
BeforeAllна каждый сьют" - это классический перевод стрелок. Зачем мне тогда вообще ваш фреймворк, если я должен сам руками страховать его логику?)))Ваше сравнение с обычным table-driven тестом некорректно. В обычном Go-коде цикл у меня перед глазами, и я сам полностью контролирую семантику выполнения:
В Testo же параметризация автоматизирована магией фреймворка. Он сам собирает
CasesXXX, сам строит матрицу, сам планирует тесты и сам решает, запускать их или нет. В итоге ваш лозунг "критичные тесты не могут молча пропасть" превращается в тыкву: матрица параметров пустая, вstderrулетел варнинг, который на CI никто не читает, билд зеленый, мастер катится в прод без проверок.2. Тип
T— это не обычный Go-код, а рантайм-схемаВы пишете, что структура
Tпредназначена исключительно для подключения плагинов.Но это как раз и доказывает мою мысль:
Tв Testo - это не обычный пользовательский тип. Это декларативная runtime-схема для вашего скрытого DI-контейнера. Фреймворк бесцеремонно лезет рефлексией в поля, требует только указатели, сам все аллоцирует и сам строит граф зависимостей. Шаг влево - рантайм-паника на старте:Ссылка на
go vet- это вообще мимо.go vet- это внешний статический линтер, а не система типов Go и не компиляторные гарантии. Его можно отключить, проигнорировать, криво настроить на CI или тупо не знать про его существование :) Он никак не превращает ваши рантайм-костыли в легитимный контракт языка. Если корректность структуры проверяется не компилятором, а вашим reflection-конструктором - это рантайм-модель, как ее ни называй.3. Два одинаковых плагина - это не антипаттерн, а нормальная композиция
Вы пишете:
Говорить так - значит расписаться в непонимании гибкой архитектуры и композиции. Такой сценарий элементарен: последовательная фильтрация тестов несколькими независимыми условиями. В том же Axiom можно спокойно сделать так:
Мне нужно подключить один и тот же тип плагина тегов дважды: первый раз с конфигом из одного окружения, второй - из другого. Это один и тот же тип плагина, но две разные конфигурации и две разные стадии обработки.
В Axiom плагины - это просто слайс функций
type Plugin func(cfg *Config), они хранятся в массиве и предсказуемо применяются в том порядке, в котором их передал инженер:В Testo это сделать невозможно физически, потому что у вас identity плагина - это
reflect.Type:Один тип - один инстанс. Все. Это не "безгранично гибкая система плагинов", это кастрированный Singleton-by-type Service Locator.
4. Потеря порядка плагинов из-за Go map
Вот тут начинается самое прекрасное))) Вы рассказываете в докладах про приоритеты и
SortStableFunc, но ваш код теряет исходный порядок плагинов еще до того, как доходит до сортировки.Вы собираете плагины в
map[reflect.Type], потом спецификации складываете вmap[reflect.Type]testoplugin.Spec, а потом передаете их дальше через итерацию по мапе. Как мы все знаем, порядок обхода map в Go рандомизируется на уровне рантайма языка.Если у пользователя объявлено три плагина с дефолтным приоритетом
0, порядок выполнения их хуков превращается в рулетку. Порядок их работы - это не порядок их объявления инженером в структуреT, а следствие случайного обхода мапы. Чтобы это починить, пользователю придется руками расставлять приоритеты там, где все изначально должно было работать по цепочке.В Axiom такой проблемы нет физически. Передал плагины в слайс - они ровно в этом порядке друг за другом и выполнились:
Без рефлексии, без итерации по мапам и без костылей с ручной сортировкой приоритетов.
5. Сравнение с Axiom по reflection - это ложная эквивалентность
Ваша попытка ткнуть пальцем в Axiom:
это классический прием подмены понятий :)
Да, в Axiom есть рефлексия в методе
RunSuite. НоRunSuiteв Axiom - это опциональный адаптер-переходник для тех, кто привык к старым xUnit-сьютам. Дока тык. Основной движок Axiom полностью прозрачен, декларативен и работает на чистом Go:Здесь нет неявного сканирования структур, нет DI-контейнеров, нет магии завязки на имена методов
CasesXXX.В Axiom рефлексия - это сбоку прикрученный адаптер, который еще и полностью опционален
В Testo рефлексия - это фундамент, без которого фреймворк вообще не работает (даже ваш
RunTestпод капотом заворачивается в синглтон-сьют и идет по reflection-пути).Архитектурный диагноз
Вы пытаетесь представить это как набор мелких, изолированных компромиссов: тут подкрутим, там пользователь сам проверит, здесь
go vetподскажет. Но если сложить все эти костыли вместе, перед нами открывается совершенно четкая и пугающая архитектурная картина)Testo - это не "тонкий фасад" и не "пара контролируемых мест с рефлексией". Вы построили полноценный, тяжелый reflection-driven runtime, который живет поверх Go и полностью игнорирует его философию:
Проход по сьютам - через неявную рефлексию
Биндинг параметров - через угадывание имен методов (
CasesXXX);Инъекция плагинов - через бесцеремонное сканирование и мутацию полей структуры
Идентификация плагинов - через жесткий
singleton-by-type, убивающий композициюСортировка плагинов - завязан на случайный порядок обхода Go-мап.
Вы утверждаете, что проблема в одном баге с пустыми параметрами, который можно закрыть опцией. Нет. Проблема фундаментальна: вся архитектура Testo держится на неявной рантайм-интерпретации пользовательского кода.
Тот же Axiom решает абсолютно аналогичную задачу в разы чище и честнее: через явный
Runner, явныйCase, явный слайс плагинов и явное получение параметров в точке использования. Компилятор все видит, компилятор все проверяет :)Главный вывод
Знаете, почему этот спор зашел в тупик? Потому что вы написали фреймворк не для Go. Вы написали его вопреки Go.
Язык Go создавался Робом Пайком и командой именно для того, чтобы навсегда избавить индустрию от неявной магии, скрытых DI-контейнеров, рантайм-паник и развесистых enterprise-абстракций в стиле Java Spring. Инженеры уходят в Go ради предсказуемости, явности (
explicit over implicit) и строгого контроля компилятора.Вы же взяли Go и попытались насильно превратить его в Pytest или Spring. В итоге получился инструмент, который борется с собственной экосистемой.
Для внутренней автоматизации Ozon под административным ресурсом это может работать годами - спору нет, там люди подневольные. Но для независимого open-source и продакшена крупных компаний строить критически важный тестовый фундамент на рантайм-магии - это огромный, неоправданный риск.
За доклад на митапе и за эту дискуссию еще раз спасибо, она получилась максимально вскрывающей. Но мы все равно выбираем предсказуемость и остаемся на Axiom
Раз уж вы пришли с кодом и логами, я не поленился, залез в ваш репозиторий Testo и детально изучил исходники :) И знаете что? Все оказалось гораздо хуже, чем просто "неидиоматичная рефлексия". Вы зашили в ядро фреймворка тяжелейшие рантайм-костыли, которые прямо противоречат вашим же заявлениям о безопасности)))
Давайте разберём по фактам то, что лежит у вас в main-ветке.
1. Дырявый CI и молча пропадающие тесты (
collector.go)Вы утверждаете, что "CI точно не будет зеленым при проблемах с параметрами". Смотрим в ваш код
collector.go:Что это значит? Если по какой-то причине (баг в логике, не доехавшие данные из бэка, пустой слайс окружения) ваш метод
CasesOS()вернет пустой список - параметризованный тест просто молча не запустится. Он не падает, не помечается какFailed, не уходит вSkipped. Он просто тихо возвращаетnil, плюнув варнингом вstderrДля любого нормального CI варнинг в логах - это абсолютно зеленый билд. Критичный тест пропал из прогона, проверки не было, но пайплайн успешно прошел. Ваша "гарантия безопасности" сломалась в первом же файле
2. Это не "удобный фасад", это агрессивный DI-контейнер (
construct.go)Вы говорите, что рефлексия у вас "в паре контролируемых мест". На деле Testo пытается быть скрытым Spring-подобным DI-контейнером, который бесцеремонно вторгается во внутренности пользовательских структур. Смотрим в
construct.go:Фреймворк рекурсивно обходит структуру
T, сам аллоцирует указатели, сам подменяет поля и требует, чтобы все exported fields были строго указателями. Шаг влево, забыл звtздочку - получайruntime panicпрямо на старте. Экспортируемые поля внутриTперестают быть обычным Go-кодом, они превращаются в жесткую рантайм-схему вашего фреймворка3. Иллюзия гибкости плагинов
Вы заявляете, что благодаря плагинам Testo никогда не устареет. Но смотрим, как эти плагины хранятся в том же
construct.go:Ключом вашей мапы является
reflect.Type. То есть ваша архитектура плагинов - это обычный Singleton-by-type Service Locator. Из-за этого два независимых экземпляра одного и того же плагина (например, с разной конфигурацией) внутри одной структурыTфизически невозможно выразить. Они просто схлопнутся по типу. О какой "безграничной гибкости" тут речь?)))В чем разница с нормальным подходом?
В том же Axiom, о котором статья, runtime-check используется локально и честно - только для проверки пользовательских данных в точке вызова:
params := axiom.GetParams[LoginParams](cfg). Код прозрачен. Инженер видит, что и где он запрашивает.Axiom не сканирует структуру тестов рефлексией, не строит неявный DI-контейнер по exported-полям, не паникует из-за отсутствия указателей и не завязывает логику плагинов на
reflect.TypeПоэтому сорри, ребят, но вы облажались. Вы попытались исправить болячки
allure-go, но вместо прозрачного Go-кода построили переусложненный комбайн, который держится на неявных рантайм-контрактах. Про такой софт обычно говорят: "работает — не трогай, сломалось — не починишь". Тащить это в продакшен крупных компаний - это лютый харам, и не только по-гошному, а вообще по любым канонам нормальной разработки.За доклад на митапе все равно спасибо, но мы точно остаемся на предсказуемом Axiom
UPD: И да, ваш аргумент про "10 тысяч тестов в сотне команд внутри Ozon" не доказывает вообще ничего. В бигтехе внедрение внутренних инструментов слишком часто работает по принципу административного ресурса: сверху спустили архитектурную директиву "все новые сервисы пишем на Testo", и сотня команд берет под козырек, даже если плюется от рантайм-паник и переписывания общих хелперов. Реальное качество опенсорса проверяется свободным рынком и независимыми инженерами, а не корпоративной обязаловкой)))
Вы сами скинули цитату, которая полностью хоронит ваш же аргумент про "надежность крупных компаний". Ozon официально расписался в том, что забросил allure-go на жизнеобеспечение, потому что сами же завели его в архитектурный тупик. И их обещание "с Testo такого точно не повторится" - это чистой воды маркетинг. Еще как повторится, как только их новая неявная магия на рефлексии начнет сыпаться на объемах реальных компаний. Команду мейнтейнеров переведут на другие проекты, и они выпустят третий фреймворк. Доверия к такой "поддержке" ноль)))
По поводу рефлексии: если вам "в последнюю очередь важно" наличие compile-time проверок, вы просто никогда не ловили полностью зеленый CI в мастере при молча пропущенных критичных тестах из-за банальной опечатки в регистре имени метода (
CasesOsвместоCasesOS):Для пет-проектов и вебинаров "удобный фасад" важнее надежности, спору нет. Но для нормального продакшена сознательно ломать строгую типизацию Go в тест-рантайме - это профнепригодность. Посмотрите доклад внимательнее)
Testify топчик, сам юзаю, но https://github.com/uber-go/mock это про юниты, в статье речь про E2E / интегры
Поверьте, тут это еще вообще не магия. Вот ниже в комментах товарищ притащил ссылку на Testo - вот там реально ящик Пандоры)
А насчет линейности - для простых юнитов это работает, спору нет. Но на сложных интеграционных тестах с базами и контейнерами вам в любом случае придется что-то придумывать и городить обвязки
Ага, посмотрел его - тащит абсолютно ту же самую скрытую магию в гошку. Это для Go вообще харам, спасибо, не надо) Доверие к их архитектурным решениям было полностью потеряно еще на allure-go. Уж лучше сидеть на чем-то предсказуемом, где хотя бы в рантайм прозрачный и компилятор все проверяет, чем снова дебажить чужую рефлексию)))))