CI で実行した Xcode のテスト結果を HTMLに出力する

iOSアプリをCIでビルドしたりテストするのに fastlane を使っています。fastlane には snapshot というアクションが用意されており、UIテスト中にスクリーンショットを撮影して結果をHTMLで見ることができます。
便利なのですが、Xcode 9 から XCUITest自体にスクリーンショットを取得する機能が導入されました

snapshot の代わりにこちら使っていこうと思ったのですが、snapshot を使わないと fastlane でテストを実行したときにスクリーンショットを含んだ HTML ファイルを生成することが現状できないんですよね……。そこで、作ってみました。使い方は README.md を読んでください。

こんな感じのHTMLができます。

image.png

以下は実装のメモになります!

XCUITest でのスクリーンショットの撮影

XCTAttachment を使います。XCUItest で以下のコードを実行します。

let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot())
attachment.lifetime = .keepAlways
attachment.name = "MyScreenshot"
add(attachment)

lifetyme に .keepAlways を指定するとテスト成功時にもスクリーンショットが残るようになります。デフォルトではテスト失敗時にのみ残ります。name にはどの画面かなどの名前を指定します。

テスト中に毎回このコードを書くのは面倒なので、extension にメソッドを定義しておくと良いと思います。

extension XCTestCase {
  func capture(_ name: String, lifeTime: XCTAttachment.Lifetime = .deleteOnSuccess) {
    let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot())
    attachment.lifetime = lifeTime
    attachment.name = name
    add(attachment)
  }
}

テストを実行すると、テスト結果画面のクリップアイコンからスクリーンショットを見れるようになります。
image.png

テスト結果は plist で以下に保存されています。

~/Library/Developer/Xcode/DerivedData/<your_app>/Logs/Test

またスクリーンショットは

~/Library/Developer/Xcode/DerivedData/<your_app>/Logs/Test/Attachments

に残っています。plist の中を見るとテストの成否や attachments の情報、実行時間などの情報が記録されており十分な情報がありそうです。

plist をパースし HTML を生成するツールの作成

https://github.com/taisukeh/xcode-test-reporter

plistをパースする処理は swift なら簡単にかけそう & Codable 使えば楽に書けそうと思ったので swift で作成しました。

CLI

swift での CLI ツールを作成には Swift Package Manager を使います。

swift package init --type executable

を実行するとプロジェクトの雛形ができます。あとは Sources 以下に実装を Tests 以下に書いていきます。

CLI のオプションのパースに使うツールを探してみると、Swift Package Manager に添付されているものを使う方法があるとのことで ArgumentParserが便利すぎる件 [SwiftPM] を使いました。

plistのパース

plist のパースには Codable を使いました。

public struct Report: Codable {
   let FormatVersion: String
   let RunDestination: RunDestination
   let TestableSummaries: [TestableSummary]
 }

 public struct RunDestination: Codable {
   let LocalComputer: LocalComputer
   let Name: String
   let TargetArchitecture: String
   let TargetDevice: TargetDevice
 }

public struct TestableSummary: Codable {
   let ProjectPath: String
   let TargetName: String
   let TestName: String
   let Tests: [Test]
 }
...

このように plist の構造そのままの定義を書いておくと、

func decodePlist<T>(_ type: T.Type, plistUrl: Foundation.URL) throws -> T where T : Decodable {
   let data = try Data(contentsOf: plistUrl)
   let decoder = PropertyListDecoder()
   return try decoder.decode(type, from: data)
 }

let report: Report = try decodePlist(Report.self, plistUrl: plistUrl)

だけでパースが完了します。あとはこの情報を元に HTML を生成するだけです。

単体テスト実行

Tests ディレクトリ以下にテストを書くと

swift test

だけでテストが実行できるのは良いのですが……、よくあるテストツールのようにテスト結果を色付きでわかりやすく表示してくれたりはしません。xcprettyを使うといい感じになりました。

swift test 2>&1 | xcpretty

image.png

また今回 CircleCI の siwft docker image でテストを実行するようにしました。Mac上でテストを実行した際は、テストケースの test という名前がついたメソッドを自動で見つけて実行くれるのですが、Linux で実行する際には自分でテストメソッドを一覧にしなければなりませんでした。

LinuxMain.swift というファイルを用意して、

import XCTest
 @testable import LibTests

 XCTMain([
     testCase(PlistParserTests.allTests),
 ])

各テストクラスで allTests を定義しいちいちメソッドを配列にして渡す必要があります。

class PlistParserTests: XCTestCase {
   override func setUp() {
     super.setUp()
   }

   static var allTests = [
     ("testParse", testParse),
   ]
...

これはテストケースが多くなってくると面倒そうですね……。

fastlane plugin

fastlane には作者直々の trainer という、Xcodeのテスト結果を JUnit の XML に変換してくれる plugin があります。plist をパースするすればよいということはこの trainer から知りました。今回作成したツールと trainer はやっている処理自体は同じです。HTML を出力するかどうかという違いがあるだけです。

作成した plugin のオプションの指定などは trainer を真似しました。また、JUnit の XML の出力処理も実装しているため、trainer を置き換えることもできます。

まとめ

snapshot を使わなくても XCUITest でスクリーンショットを撮影 & HTML 出力できるようにしました
Swift Package Manager を使ったツールと、fastlane pluginを作成しました。trainer を置き換える感じで使用できます。

もし良ければいいねや GitHub のスターをください!

以上、

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.