メルペイでのxcresult活用事例
Merpay Advent Calendar 2020 の19日目は、メルペイ iOS チーム EM(Engineering Manager)の 玉城がお送りします。
2020年はメルペイではBackend/Frontend/iOS/Androidの領域で、より素早いリリースイテレーションを達成するためにリグレッションテストの自動化が推進された年でした。
また、これまでスプレッドシートなどで管理されていたテストの項目、実績管理などもTestRailというテスト管理サービスに移行させることでテストの自動化との親和性が上がりつつあります。
私が持つiOSチームではリグレッションテストをXCUITestで自動化する取り組みをしていました。本記事ではXCUITestで作成したテストがどのように管理・サービス連携されているのか、そして連携するデータの元となっているResult Bundleの構造や、情報の取り出し方について説明します。
XCUITestを使ったUIテストの作成については弊社のエンジニアがiOSDCにて発表していますのでこちらのスライドや動画も是非ご覧ください。
メルペイのUIテストの全体像
メルペイでのUIテストの全体像について説明します。
はじめに特定のラベルのついたPRへのPushや夜間にスケジュールされたトリガによってCircleCIのワークフローが起動します。次にワークフローの中でテストが実行され、その結果をTestRailに連携しています。
TestRailとは
TestRailとはテスト管理ツールでテストケース、テストの実行履歴、実行結果の管理などを行えるツールです。APIが提供されておりプログラムからTestRailの操作することも出来ます。
メルペイではモバイルアプリのリグレッションテストについて手動・自動のいずれもTestRailで管理を行っています。
TestRail https://www.gurock.com/testrail/
TestRail API https://www.gurock.com/testrail/docs/api
TestRailとの連携
XCUITestの実行結果をTestRailに連携するためにメルペイではfastlaneのカスタムアクションを作成しています。カスタムアクションではResult Bundleを元にTestRailに連携するために必要な情報を取出し、データを整形してTestRail APIを使って連携しています。
カスタムアクションの中でどのようにResult Bundleの内容を取出しているのか?
それを理解するためにはまずResult Bundleについて詳しく知る必要があります。
Result Bundleを開いてみよう
早速Result BundleをXcodeで開いて内容を確認してみましょう。
XcodeのReport Navigatorタブから任意のテストのログを右クリック→Show in Finderを選択することでFinderアプリ上でResult Bundleである*.xcresultファイルが確認できます。
Xcodeでテストを実行するとResult Bundleは $HOME/Library/Developer/Xcode/DerivedData/${プロジェクト名-xxx}/Logs/Test
に生成されます。
またxcodebuild
コマンドでビルドを行ってる場合はxcodebuild
の-resultBundlePath
オプションを使って任意の場所にResult Bundleを出力して確認してみると良いでしょう。
xcresultとは
xcresultとはXcode11で採用された新しいResult Bundleのフォーマットです。
Result Bundleには以下のような情報が含まれています。
- ビルドログ
- テストレポート
- テストカバレッジ
- アタッチメント
パッケージの中身はplistとバイナリファイルで構成されているため、テキストエディタなどで内容を照会することは出来ません。
Result Bundleの内容を確認する
Resule BundleはXcodeを使って内容を確認できます。
xcresultファイルをダブルクリックして開いてみましょう。Report Navigatorタブのみ情報が表示されます。
Result Bundleの情報にアクセスする
Result Bundleの内容を人が読むだけであればXcodeで開く方法で十分かもしれません。しかしプログラムから結果を取り出したい場合には不向きです。そのような場合はxcresulttool
を使って情報を取り出します。
xcresulttoolはAppleから提供されている、xcresultを人が読める、または機械的に解析が可能な形式に変換できるツールです。
使い方はxcrun xcresulttool --help
またはman xcresulttool
で調べることができますが、本記事ではその内容に更にメルペイでの利用方法を例に添えて以下の4つのサブコマンドについて説明します。
- get
- formatDescription
- export
- merge
get
getはxcresultの内容を出力するサブコマンドです。基本的にはこのコマンドを使ってxcresultからデータを取出します。
以下の3つのオプションが用意されています。
オプション | 説明 |
---|---|
–path | 任意のxcresultへのパス |
–format | 出力フォーマット、JSONフォーマットまたはRAWな形式の2種類の出力をサポートしています --format json のように指定します |
–id | オブジェクトのID 指定しない場合はルートIDが使われます |
–version | Info.plist がないなどの不完全なものに対しては、バージョンを明示的に指定します。(指定しなければ最新のバージョンが使われます) |
以下のようにget
コマンドを実行するとxcresultのルートの階層の情報を得ることが出来ます。
※出力結果は長いので一部省略してあります。
$ xcrun xcresulttool get --path report.xcresult/ --format json
{
"_type": {
"_name": "ActionsInvocationRecord"
},
"actions": {
"_type": {…},
"_values": […]
},
"issues": {
"_type": {
"_name": "ResultIssueSummaries"
},
"testFailureSummaries": {
"_type": {…},
"_values": […]
}
},
"metadataRef": {…},
"metrics": {…}
}
getサブコマンドで得られた出力を理解するには次に説明するformatDescription
サブコマンドで得られる情報からスキーマを理解する必要があります。
formatDescription
formatDescription
サブコマンドはResult Bundleの形式記述(Format Description)を得ることができます。データがどのような型や構造を持ってるかを確認することができます。
formatDescription
サブコマンドには更に2つのサブコマンドがあります。
サブコマンドを指定しない場合、get
がデフォルトで採用されます。
サブコマンド | 説明 |
---|---|
get | Result BundleのFormat Descriptionが表示されます |
diff | 二つのResult Bundleのフォーマットの差分を表示します |
get
get
サブコマンドはFormat Descriptionを内容を指定したフォーマットで出力します。
オプション | 説明 |
---|---|
–format | 出力形式をjson、text、markdown、typeHierarchyのうちのいずれかを指定できます |
–hash | Format Descriptionのハッシュ値のみを出力します |
–include-event-stream-types | イベントストリームタイプの型を含めて出力します |
–version | Result Bundleが不完全な場合(Info.plistがないものなど)、バージョンを明示的に指定する必要があります |
--format
オプションでjson
/text
/markdown
を指定した場合は定義されたオブジェクトの親タイプや種類(value/array/object)、プロパティなどの情報を得る事ができます。
$ xcrun xcresulttool formatDescription get --format text
Name: Xcode Result Types
Version: 3.26
Signature: XtEXH3GNNNI=
Types:
- ActionAbstractTestSummary
* Kind: object
* Properties:
+ name: String?
...
$ xcrun xcresulttool formatDescription get --format json
{
"signature" : "XtEXH3GNNNI=",
"types" : [
{
"type" : {
"name" : "ActionAbstractTestSummary"
},
"kind" : "object",
"properties" : [
{
"isOptional" : true,
"wrappedType" : "String",
"name" : "name",
"type" : "Optional"
}
]
},
...
$ xcrun xcresulttool formatDescription get --format markdown
# Xcode Result Types Format Description
- version: 3.26
- signature: XtEXH3GNNNI=
## Types
### ActionAbstractTestSummary
- kind: object
- properties
- name: [String](#user-content-string)?
...
オブジェクトタイプについて
オブジェクトのタイプは全部で58個あり、現状その全てのタイプの意味を調べきれてはいないので基本的なタイプについて抜粋して説明します。
※--include-event-stream-types
オプションで更に追加で表示されるイベントストリームタイプを合わせると86個あります。
タイプは大きく以下の3つに別れます。
- Value
- Array
- Object
Valueには5つのプリミティブなタイプが用意されています。
- Bool
- Date
- Double
- Int
- String
Arrayには1つのタイプのみ用意されています。
- Array
Objectにはその他全てのタイプが該当します。
タイプ名やプロパティを持ち、他のタイプに依存します。
TestFailureIssueSummary
のようにスーパータイプにIssueSummary
を持つような継承関係にあるタイプも存在します。
{
"_type": {
"_name": "TestFailureIssueSummary",
"_supertype": {
"_name": "IssueSummary"
}
},
"documentLocationInCreatingWorkspace": {…},
"issueType": {…},
"message": {…},
"producingTarget": {…},
"testCaseName": {…}
},
typeHierarchy
オプションを指定すると継承関係がわかりやすく表示されます。
$ xcrun xcresulttool formatDescription get --format typeHierarchy
Name: Xcode Result Types
Version: 3.26
Signature: XtEXH3GNNNI=
Types:
ActionAbstractTestSummary
╠══ ActionTestPlanRunSummary
╠══ ActionTestSummaryIdentifiableObject
║ ╠══ ActionTestMetadata
║ ╠══ ActionTestSummary
║ ╚══ ActionTestSummaryGroup
╚══ ActionTestableSummary
ActionDeviceRecord
...
ActivityLogAnalyzerStep
╠══ ActivityLogAnalyzerControlFlowStep
╚══ ActivityLogAnalyzerEventStep
ActivityLogMessage
╠══ ActivityLogAnalyzerResultMessage
╚══ ActivityLogAnalyzerWarningMessage
ActivityLogMessageAnnotation
ActivityLogSection
╠══ ActivityLogCommandInvocationSection
╠══ ActivityLogMajorSection
║ ╚══ ActivityLogTargetBuildSection
╚══ ActivityLogUnitTestSection
ArchiveInfo
Array
Bool
CodeCoverageInfo
Date
DocumentLocation
Double
EntityIdentifier
Int
IssueSummary
╚══ TestFailureIssueSummary
ObjectID
Reference
...
String
TestAssociatedError
TypeDefinition
Referenceタイプもxcresultを理解するために重要なタイプなので理解しておきましょう。
xcresultのデータ構造は階層構造を持っており、各階層のデータはそれぞれObjectIDを持っています。get
サブコマンドを実行する際--id
オプションを指定しない場合はルートのオブジェクトIDが指定されます。
ルートからさらに下層の情報を取得するには下層のオブジェクトのid
を指定してget
サブコマンドで情報を取り出す必要があります。
Referenceはそのid
を持つオブジェクトです。
以下はルートの階層からアクセスできるReferenceタイプのmetadataRef
が持つ情報を出力したものです。
$ xcrun xcresulttool get --path foo.xcresult --format json
...
"metadataRef": {
"_type": {
"_name": "Reference"
},
"id": {
"_type": {…},
"_value": "0~ULBiIf-3dcJwhZgohrk-Knco5iKcQyRckwPuMdp1RumEe1JTmeYCaveZTFqeWVqKA5zUXWmhLS7s8T2EqrKYIQ=="
},
"targetType": {…}
}
...
metaデータの具体的な情報を取得するためにはReference
のid
を指定してget
サブコマンドを実行します。
$ xcrun xcresulttool get --path report.xcresult/ --format json --id 0~ULBiIf-3dcJwhZgohrk-Knco5iKcQyRckwPuMdp1RumEe1JTmeYCaveZTFqeWVqKA5zUXWmhLS7s8T2EqrKYIQ==
{
"_type": {
"_name": "ActionsInvocationMetadata"
},
"creatingWorkspaceFilePath": {…},
"schemeIdentifier": {…},
"uniqueIdentifier": {…}
}
次にメルペイではResult Bundleのデータをどのように取出して利用しているか例を挙げて説明します。
以下はあるテストについての実行結果をXcodeで開いたものと、TestRailにテスト結果として登録された内容です。
TestRailにはテストの実行ログとしてこのような情報をテスト結果として送っています。テストが失敗した場合はこの実行ログを確認しどのような操作が行われたかTestRail上で確認できるようにしています。
xcresulttoolを使って実行ログのはじめに出力されているTestRail-333513
という文字列を取出す操作を例にとってみましょう。
getサブコマンドを使ってActionsInvocationRecord
オブジェクトを取得します。
$ xcrun xcresulttool get --path sample.xcresult/ --format json
{
"_type": {
"_name": "ActionsInvocationRecord"
},
"actions": {
"_type": {…},
"_values": [
{
"_type": {
"_name": "ActionRecord"
},
"actionResult": {
"_type": {
"_name": "ActionResult"
},
...
"testsRef": {
"_type": {…},
"id": {
"_type": {…},
"_value": "0~FbFAilYKoJZ-Zf7_bs9M9oG7X_jyCa_bhtYyFhM8q-rKiVKVeDeV37PAH-m36KmUqcW5rJoFqEvouS6i1UMWDA=="
},
...
}
},
...
},
]
},
"issues": {…},
"metadataRef": {…},
"metrics": {…}
}
テストに関する情報はActionResultのtestRef
のidを指定し更にgetを実行しActionTestPlanRunSummaries
オブジェクトの情報を取出します。
以下はその実行結果です。長いため必要な情報のみ抜粋しています。
$ xcrun xcresulttool get --path report.xcresult/ --format json --id 0~FbFAilYKoJZ-Zf7_bs9M9oG7X_jyCa_bhtYyFhM8q-rKiVKVeDeV37PAH-m36KmUqcW5rJoFqEvouS6i1UMWDA==
{
"_type": {
"_name": "ActionTestPlanRunSummaries"
},
"summaries": {
"_values": [
{
"_type": {
"_name": "ActionTestPlanRunSummary",
},
"testableSummaries": {
"_values": [
{
"_type": {
"_name": "ActionTestableSummary",
},
"tests": {
"_values": [
{
"_type": {
"_name": "ActionTestSummaryGroup",
},
...
"subtests": {
"_values": [
{
"_type": {
"_name": "ActionTestSummaryGroup",
"_supertype": {…}
},
...
"subtests": {
"_values": [
{
"subtests": {
"_type": {…},
"_values": [
{
"_type": {
"_name": "ActionTestMetadata",
"_supertype": {…}
},
...
"summaryRef": {
"_type": {…},
"id": {
"_type": {…},
"_value": "0~0exjbpv4VFf0hPkx35rEvpvEAtHq7vCvs6B8qbBkrBP6h8rNauobr7XBhqv8P5G_WgqmcFF3auuDVchsNxikAg=="
},
},
}
]
}
},
]
}
}
]
}
}
]
}
}
]
}
}
]
}
}
ActionTestPlanRunSummaries
にはテストに関する情報が含まれていますがアクティビティの情報は含まれていません。
アクティビティの情報を得るためには更にActionTestMetadata
のsummaryRef
のidで情報を取り出す必要があります。
$ xcrun xcresulttool get --path report.xcresult/ --format json --id 0~0exjbpv4VFf0hPkx35rEvpvEAtHq7vCvs6B8qbBkrBP6h8rNauobr7XBhqv8P5G_WgqmcFF3auuDVchsNxikAg==
{
"_type": {
"_name": "ActionTestSummary",
"_supertype": {…}
},
"activitySummaries": {
"_values": [
{
...
"title": {
"_value": "Start Test at 2020-12-02 21:50:25.147"
},
...
},
{
...
"subactivities": {…},
"title": {
"_value": "Set Up"
},
...
},
{
...
"subactivities": {…},
"title": {
"_type": {…},
"_value": "TestRail-334452"
},
},
{
...
"subactivities": {…},
"title": {
"_type": {…},
"_value": "Tear Down"
},
...
}
]
},
...
}
ActionTestSummary
のactivitySummaries
からようやくTestRail-334452
を取得することができました🎉
subactivities
プロパティは入れ子になっており、構造化されたアクティビティの情報が入っています。あとはこれを再起的に検索することでTestRailに送ったようなログを作ることができます。
Tips xcresulttoolを使ってjsonを生で見るのはなかなかしんどいものがあります、筆者としてはjqやfxを導入すると比較的この作業が楽になるのでオススメです。
$ xcrun xcresulttool get --path report.xcresult/ --format json | fx
export
export
サブコマンドはファイルやディレクトリをxcresultから取り出すためのサブコマンドです。
例えばXCTAttachment
を使ったスクリーンショットなどの成果物を取り出すことができます。
オプション | 説明 |
---|---|
–id | オブジェクトのID 指定しない場合はルートIDが使われます |
–output-path | 出力するファイルのパス |
–path | 任意のResult Bundleへのパス |
–type | 出力するデータのタイプをfileまたはdirecotryで指定します |
–version | Info.plist がないなどの不完全なものに対しては、バージョンを明示的に指定します。(指定しなければ最新のバージョンが使われます) |
例えば以下のように失敗したテストのAttachmentとして保存されているスクリーンショットの取り出しを例にexportコマンドの実行までを説明します。
XCTAttachmentで保存したスクリーンショットはActionTestAttachment
のpayloadRef
プロパティからObjectIDを取得する必要があります。
getとformatDescriptionの具体例と同様にActionTestSummary
の情報を取得し、subactivitiesで階層を潜りつつ対象のアクティビティのattachments
からpayloadRef
のidを取得し、これをexport
サブコマンドの--id
オプションで使用します。
$xcrun xcresulttool get --path report.xcresult/ --format json --id 0~JYD4bn188TwEmx_KIIEgCHRUq6bfARALNayFxFUamBQiS-09B1R8wMpef3O5NEZmmDNgADVMTqKrRim2Y12LKg==
{
"_type": {…},
"activitySummaries": {
"_type": {…},
"_values": [
{
"subactivities": {
"_values": [
{
"subactivities": {
"_values": [
{
"subactivities": {
"_values": [
{
"attachments": {
"_values": [
{
"payloadRef": {
"_type": {…},
"id": {
"_type": {…},
"_value": "0~tCUck92hUZFq0aRqTyoNxJbnmI-M6UOwrM0ldhGrG7_Tg1oqAgtYgvdpKWRq8L5S0ZWbokz0rIAe_nsAyd5vmA=="
}
},
}
]
},
}
]
},
}
]
},
}
]
},
},
]
},
}
$ xcrun xcresulttool export --path sample.xcresult/ --type file --id 0~tCUck92hUZFq0aRqTyoNxJbnmI-M6UOwrM0ldhGrG7_Tg1oqAgtYgvdpKWRq8L5S0ZWbokz0rIAe_nsAyd5vmA== --output-path failed.jpeg
このようにして取得したスクリーンショットの画像をTestRail APIを使ってテスト結果に添付するようにしています。
merge
merge
サブコマンドは複数のResult Bundleを一つにマージすることができます。
オプション | 説明 |
---|---|
–output-path | マージ後のファイルパス |
引数 | 説明 |
---|---|
input paths | マージ対象のResult Bundleのパス(複数指定) |
実行例
$ xcrun xcresulttool merge A.xcresult B.xcresult --output-path AB.xcresult
メルペイではmerge
サブコマンドをCircleCIのワークフロー内で並列なジョブでテストを実行した後にResult Bundleをマージするジョブを動かしています。
これは後続で実行するXCTestHtmlReportやTestRail連携でシンプルに単一のResult Bundleを処理の対象として扱うためです。
ワークフロー内では以下の5つのジョブが実行されます。
- ビルド(build-for-testingオプションを使用)
- iOSバージョンを分けて並列にテスト実行(test-without-buildingオプションを使用)
- Result Bundleのマージ
- XCTestHTMLReportの実行
- TestRailへの連携
XCTestHTMLReportについては過去にメルカリエンジニアリングブログで取り上げたこちらの記事をご覧ください。
まとめ
本記事ではメルペイでの活用例を踏まえながらXcode11で新しくなったResult Bundleのフォーマットxcresultとそれにアクセスするためのツールであるxcresulttoolの使い方を説明しました。実際にはxcresulttoolをそのまま使うのでは使い勝手が悪いのでfastlaneのカスタムアクションを作ってxcresulttoolの実行とjsonのパース処理を行っています。
今後もiOSアプリ開発においてテストの実行結果を他のサービスとの連携する機会は増えるのではないかと思います。そのような時に本記事の紹介が一助になれば幸いです。
メルペイではミッション・バリューに共感できるエンジニアを募集しています。一緒に働ける仲間をお待ちしております。
明日の Merpay Advent Calendar 2020 はメルペイ VPoEの nozaqです。引き続きお楽しみください。
Appendix
xcresulttoolのその他のコマンドについてご紹介します。
diff
diff
サブコマンドは二つのxcreultのフォーマットの差分を得ることが出来ます。
オプション | 説明 |
---|---|
–format | 出力形式をtext、markdownのうちのいずれかを指定できます(デフォルトはtext) |
引数 | 説明 |
---|---|
input paths | 比較対象の2つjson形式のformatDescriptionへのパス |
以下はXcode11.6とXcode12.2にそれぞれ付属するxcresulttoolで得たformatDescription
を比較した例です。
このようにバージョンによって新たに追加されたタイプやプロパティなどの情報を確認できます。
$ xcrun xcresulttool formatDescription diff xcode11.6.json xcode12.2.json
Diff of versions: 3.24 -> 3.26
Changes:
* added type 'SourceCodeLocation'
* added type 'SourceCodeSymbolInfo'
* added type 'TestAssociatedError'
* added type 'SourceCodeContext'
* added type 'SourceCodeFrame'
* type 'ActionTestFailureSummary' added property 'associatedError'
* type 'ActionTestFailureSummary' added property 'isTopLevelFailure'
* type 'ActionTestFailureSummary' added property 'detailedDescription'
* type 'ActionTestFailureSummary' added property 'attachments'
* type 'ActionTestFailureSummary' added property 'uuid'
* type 'ActionTestFailureSummary' added property 'issueType'
* type 'ActionTestFailureSummary' added property 'sourceCodeContext'
* type 'ActionTestFailureSummary' added property 'timestamp'
* type 'ActionTestActivitySummary' added property 'failureSummaryIDs'
graph
graph
サブコマンドはResult Bundleのオブジェクトグラフの出力を得ることができます。
オプション | 説明 |
---|---|
–id | オブジェクトのID 指定しない場合はルートIDが使われます |
–path | 任意のxcresultへのパス |
–version | Info.plist がないなどの不完全なものに対しては、バージョンを明示的に指定します。(指定しなければ最新のバージョンが使われます) |
実行例
$ xcrun xcresulttool graph --path sample.xcresult
* ActionsInvocationRecord
- Id: 0~OqfQ_defnXPUr_3DFPqtj2eJF4LkQY2DQZUJICKeLtHGO8c-EZfTAkkHxP3VgledkMUWlX_C73KNf-9E6jUILw==
- Size: 1943188
- Refs: 7
* CASTree (file or dir)
...
* ActivityLogSection
...
* raw
...
* ActivityLogSection
...
* ActionsInvocationMetadata
...
* CASTree (file or dir)
...
* ActionTestPlanRunSummaries
- Id: 0~xaQ4mjZIgOclWs-dX1ZT-blBL-4WfTHqmuKAk2kGggxcDqgbv555nZ05dXbEzhCX55mNHPpbuc_P22-OjeFPqQ==
- Size: 2095
- Refs: 1
* ActionTestSummary
- Id: 0~fuf1OFdFoB78xMpwRVaHr-OsS66EM7dRKOaEH2pCOMIy-6-LApJ-3QbFWkPX23d3YntMbsceYXWvOAk0qIOEcA==
- Size: 92000
- Refs: 20
* raw
...
* raw
version
version
サブコマンドはxcresulttoolのバージョン情報を得ることが出来ます。
$ xcrun xcresulttool version
xcresulttool version 17017, format version 3.26 (current)