BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

EarlGreyを使ってiOSのUIテストを自動で行う

f:id:tomo358:20180417191124j:plain:w600

こんにちは。ショッピングアプリ「BASE」のiOSアプリを担当している竜口です。

背景:あの改修の効果測定用のログ、送られてる?

ショッピングアプリ「BASE」内で、施策の効果測定やKPIの経過観察で様々なログを使用しているのですが、細かい改修などで特定のログが送られない事象があり、効果測定が出来ずに多部署の作業が止まるということがありました。

そこでアプリ(今回はiOSのみ)でログが正しく送られていることを保証するために、ログのテストをするようにしました。

全体の流れ

次のことをしました。

  1. EarlGreyでUITestを実装
  2. テストの中で送られるログをMockに保持
  3. テスト完了後、Mockにあるログの有無を確認
  4. テストをCircleCIで自動化

これで改修によるログの影響を保証出来るようになります!!

1. EarlGreyでUITestを実装

EarlGrey Referenceは、Googleが作っているiOSのUI automation test フレームワークです。

書き方は、簡単で以下の例の場合UIButton.accessibilityIdentifierに特定のIDを指定して、テストではselectElementで指定したIDの要素を取得し、要素の有無の確認、要素をタップ等できます。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        button.accessibilityIdentifier = "BTN"
    }
}
class UIAutomationSpec: XCTestCase {
    func testTapButton() {
        EarlGrey
            .selectElement(with: grey_accessibilityID("BTN"))// 要素を指定
            .assert(grey_sufficientlyVisible())// 存在するかを確認
            .perform(grey_tap())// 指定した要素をタップする
    }
}

2. テストの中で送られるログをMockに保持

UITest内でログを送る際に、Mockにログを保存する処理を追加します。

class ViewController: UIViewController {
    @IBAction func didTouchUpButton(_ sender: UIButton) {
        LogManeger.send(log: Log(name: "tap_button"))
    }
}
struct LogManeger {
    static func send(log: Log) {
        isTest {
            LogMock.store(log)// Mockに保存
        }
        
        // Logを送る
    }

    static func isTest(_ doForTest:() -> Void) {
        #if DEBUG
            if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil && enabledUITest {
                doForTest()
            }
        #endif
    }
}

3. テスト完了後、Mockにあるログの有無を確認

テスト開始前に保証するログを指定して、終わった段階でログが揃っているかを確認する。

class UIAutomationSpec: XCTestCase {

    override func tearDown() {
        super.tearDown()
        LogMock.removeAll()
    }
    
    func testTapButton() {
        LogMock.addExpect(Log(name: "tap_button"))// 期待するログを指定
        
        EarlGrey
            .selectElement(with: grey_accessibilityID("BTN"))
            .perform(grey_tap())
        
        XCTAssert(LogMock.verify(), "Logが揃っていません。")// ログが揃っているか確認
    }
}

4. テストをCircleCIで自動化

実装したテストをCircleCIで動かすようにして、変更があってもログが送られることを保証できるか確認できるようにしました。 なかでもハマったところがいくつかありましたので紹介します。

ローカル環境では通るけどCircleCIでは通らなかった

一番長く戦った凡ミスです。 ローカル環境で実機確認してテスト通るのに、CircleCIで行うと通らないし何故か画面が真っ黒になっていました。

原因は、ローカル環境とCircleCIで実行している環境が違うので、UITestの結果が環境に依存する場合、結果が変わってしまうことでした。 CircleCIは、どこかの場所でこちらが指定したOS,端末のSimulatorで初回インストールとして起動されテストしています。 なのでローカル環境で確認する時に、CircleCIの環境を揃える必要があり、今回のようにローカル環境で実機確認していると結果が変わる場合があります。

テスト通らなかったのはわかるがなぜ通らなかったのかわからない

CircleCIでテスト通らなかった際に、どこの箇所で通らなかったのかはわかりますが、どの画面でなぜ通らなかったかはわかりません。 なので EarlGrey.setFailureHandler(handler: ) で通らなかった時のErrorMessageとその画面のスクショをSlackに送るようにしました。

f:id:tomo358:20180416192531p:plain

まとめ

まだカバーしてる部分は一部重要機能のみですが、テスト導入し今の所ログの不具合も起こらず、何よりUIの保証も最低限できるので安心して開発できるのが良いです!

BASEを日本一のサービスに成長させる仲間を募集しています!

BASEでは、Web、アプリ、SRE、データ解析など幅広い職種を募集しています。 ご興味を持った方、カジュアルな面談もできますので、一度オフィスに遊びに来てみませんか?

base.ac