В 2018 году Apple в очередной (третий) раз обновили формат, в котором выдаётся информация о прогоне тестов. Если раньше это был plist файл, который представлял из себя большой xml, то теперь это большой файл с расширением xcresult, который открывается через Xcode и содержит в себе кучу полезной информации, начиная c результатов тестов с логами, скриншотами и заканчивая покрытием таргетов, диагностической информацией о сборке и многим другим. Большинство разработчиков не работает каждый день с этим, но инфраструктурщики в данной статье могут найти что-то полезное.
Разложим по полочкам плюсы и минусы обновления формата
В чем минусы обновления формата?
— Много весит, а это значит обмен такими файлами с CI сервером может оказаться долгим.
— Если нет Xcode, то его не открыть (сомнительно, что у тестировщика или разработчика не будет Xcode, но все же).
— Возможная поломка существующих инструментов интеграции. Снова учиться работать с чем-то новым.
Чем удобен новый xcresult?
— Открывается нативными средствами через Xcode.
— Можно передавать коллегам из QA и разработки, даже если у них нет локально проекта. Все откроется и покажет нужную информацию.
— Содержит исчерпывающую информацию о прогоне тестов.
— Можно читать не только через Xcode.
Вот о последнем пункте мы и будем говорить в этой статье.
Зачем читать XCResult не через Xcode?
Если у вас в компании настроены процессы CI&CD, то наверняка вы собираете метрики по сборкам проекта, по стабильности и количеству тестов, и, конечно, данные по тестовому покрытию. Скорее всего, где-нибудь на Bamboo, Jenkins, Github у вас рисуются упавшие тесты или статус CI, или процент покрытия. Такие операции принято автоматизировать и отдавать на откуп бездушным машинам. Какие инструменты есть у нас для этого?
Apple, вместе с релизом нового формата, выпустили и инструменты xcresulttool и xccov, с которыми можно работать из терминала.
Что мы можем достать, используя xccov?
xcrun xccov view --report --json /path/to/your/TestScheme.xcresult
Запрос вернёт исчерпывающую информацию о том, каким покрытием обладают все таргеты, какие методы и каких классов покрыты, сколько раз они были выполнены и какие строчки выполнялись. Объекты обладают схожей структурой. Всего там 4 уровня: корень, таргет, файл, функция. Все уровни, кроме корневого, имеют поле name. Во всех уровнях есть поля coveredLines и lineCoverage. Важно отметить, что объекты имеют какой-то собственный контекст. Всю структуру можно описать в несколько протоколов.
Помимо протоколов выделим следующие структуры: CoverageReport — агрегатор всего и корень. Он содержит в себе массив объектов Target. Каждый Target содержит в себе массив File, которые, в свою очередь, содержат массив Function. Эти объекты будут реализовывать протоколы, которые описаны выше.
Нас интересует поле lineCoverage. Для составления красивого отчета (как в fastlane) обратимся к полю lineCoverage и пройдем по всем объектам нехитрой функцией:
Получим что-то похожее на:
Coverage Report Summary:
• Utils.framework: 51,04 %
• NavigationAssistantKit.framework: 0,0 %
• NavigationKit.framework: 35,85 %
• Logger.framework: 20,32 %
• FTCCardData.framework: 78,21 %
• FTCFeeSDK.framework: 25,25 %
• ErrorPresenter.framework: 2,8 %
• MTUIKit.framework: 0,24 %
• AnalyticsKit.framework: 47,52 %
• EdaSDK.framework: 1,18 %
• Alerts.framework: 85,19 %
• Resources.framework: 39,16 %
• QpayApiTests.xctest: 88,37 %
• FTCFeeSDKTests.xctest: 97,91 %
P.S. Для того, чтобы coverage собирался, необходимо добавить в вашу команду тестирования параметр -enableCodeCoverage YES или включить в настройках схемы в Xcode.
Какие возможности даст xcresulttool?
На самом деле xcresulttool имеет не сильно большой интерфейс, но из него можно извлечь поистине много информации, если знать структуру xcresult. А это целая база данных, к которой можно делать запросы.
Для начала неплохо ознакомиться с самим интерфейсом:
xcrun xcresulttool --help
OVERVIEW: Xcode Result Bundle Tool (version 16015)
USAGE: xcresulttool subcommand [options] ...
SUBCOMMANDS:
export Export File or Directory from Result Bundle
formatDescription Result Bundle Format Description
get Get Result Bundle Object
graph Print Result Bundle Object Graph
merge Merge Result Bundles
metadata Result Bundle Metadata
version XCResultKit Version
Чтобы прочитать структуру, нам достаточно вызвать команду:
xcrun xcresulttool get --path /path/to/your/res.xcresult --format json
Здесь мы получим “оглавление” для нашего xcresult бандла. Что собиралось, какие тесты прогонялись, сколько времени заняло, где лежат скриншоты и логи, и какие были предупреждения компилятора. Для нас главное — достать идентификаторы файлов, в которых лежит информация о тестах.
xcrun xcresulttool get --path /path/to/your/res.xcresult --format json --id {id}
Тогда мы получим объекты с тест-таргетами, типом тестов, которые разбиты по тест-классам и test suits с отчётами с логами, скриншотами, временем выполнения и прочей информацией по каждому тесту.
К сожалению, причину падения красных тестов не получится вытащить просто — для этого придётся делать ещё один запрос на каждый упавший тест (а на самом деле даже не один! Если тест крэшнул, то крэшлоги вместе со стректрейсом лежат в другом месте и это ещё один запрос!
Для Failure Summary используется тот же запрос:
xcrun xcresulttool get --path /path/to/your/res.xcresult --format json --id {id}
А вот для крэшлогов нужно убрать --format json из запроса, т.к. там просто строка и при передаче форматтера инструмент выдаст ошибку.
Что делать с этими справочными знаниями дальше?
Автоматизировать, конечно же! Если вы попробуете выполнить эти команды, то увидите, что ответы гигантские и их тяжело читать. Как автоматизировать? Ruby, Python… Или Swift?
Конечно же, swift. Его знает любой современный iOS разработчик. Проект открывается в Xcode, доступна отладка, подсветка синтаксиса, строгая типизация. Короче, мечта! Особенно при появлении Swift package manager.
Ни для кого не секрет, что с помощью swift мы легко можем запускать процессы, слушать ошибки и получать выходные данные. В самом простом случае мы можем обойтись такой конструкцией:
Нам остается теперь только исследовать формат XCResult через уже знакомые нам xcrun xcov и xcrun xcresulttool. Например, чтобы прочитать покрытие тестами, мы используем:
А чтоб получить оглавление XCResult нам нужно выполнить:
Но как нам получить наши заветные структуры CoverageReport и XCResult?
Получаем строку из Data, которую вернет нам первая Shell команда и помещаем содержимое сюда: quicktype.io.
Сервис сгенерирует нам что-то похожее на нужные свифтовые структуры. Правда использовать результат “как есть” не получится. Придётся пристальнее изучать структуру ответа и выбрасывать дубли. Тем не менее такая работа не составляет большого труда. Можно отбрасывать ненужные части, а можно заняться исследованием и выделить несколько основных кирпичиков:
На основании этого описать уже остальные структуры, например:
или даже такие сведения о компьютерах, на которых совершался прогон:
Ну а этим-то как пользоваться?
Есть два пути, как пользоваться нашим скраппером. Первый — как executable, и здесь здорово помогает библиотека swift-argument-parser от Apple. До этого приходилось писать обработку аргументов самим, покрывать тестами, поддерживать. Сейчас эту работу взяла на себя популярная библиотека, меинтейнерам которой можно доверять.
Есть две команды: получить отчёт по покрытию тестами и сгенерировать junit отчёт о результатах тестирования. Нужно сбилдить проект и запускать бинарник, передавая необходимые аргументы:
Второй путь — использовать этот проект как библиотеку. У нас есть большой CI проект, который отвечает за сборку, тестирование и доставку нашего продукта KoronaPay. Например, мы можем по результатам прохождения тестов извлекать все assertion failures и крэши в тестах приблизительно так:
Или получать красные тесты, анализировать флаки и перезапускать только их.
А как анализировать? Всё просто и непросто одновременно. Чтобы достать детали причины падения теста, надо сделать дополнительный запрос к xcresult по идентификатору failure summary. А затем из failure summary вытаскивать информацию. На сегодняшний момент мы научились искать крэши в тестах и lost connection случаи, а также вытаскивать причины. Понять, что произошел крэш — несложно. Надо лишь найти в failureSummaries заветные слова crashed in.
Чуть сложнее вытащить причину крэша.
Здесь нам пригодится механизм рефлексии в swift, который хоть и несколько ограничен, но отлично подходит для решения этой задачи. Необходимо найти все объекты типа Attachment с именем kXCTAttachmentLegacyDiagnosticReportData.
В методе reflectProperties нет ничего магического, это простенький extension для Mirror:
Еще одна категория красных тестов — ассерты. В отличие от крэшей здесь не получится просто поискать строку “crashed in”. Такие тесты могут маскироваться под lost connection случаи. Чтобы докопаться до причины, придется пройтись по нескольким массивам внутри объекта TestCase примерно так:
Для таких операций в нашей библиотеке есть сущность TestsInspector, которая может предоставить сводку по красным тестам. Таким образом, красные тесты группируются в отчете по признакам.
Вместо заключения
Как и все существующие в этой области решения, наш скраппер не является исчерпывающим инструментом для анализа xcresult. Чтобы получить всю информацию и посмотреть скриншоты, все еще надо открывать xcresult через Xcode. Однако если у вас настроен CI и вы хотите видеть результаты тестов быстро, то, скорее всего, сможете оценить связку junit и нашего xcscrapper по достоинству.