Airtest+Pocoで始めるUnityの自動テスト

## はじめに

本記事は[QualiArts Advent Calender 2019](https://qiita.com/advent-calendar/2019/qualiarts) 6日目の記事です。

皆さんはUnityで開発を行う上で、UIテストの自動化はされてますでしょうか？
運用で開発を進めていくと、既存のUIフローで進行不能になってしまった、仕様と違う挙動になってしまった、表示崩れが発生してしまった、などなど実装によるデグレが発生してしまうというのはよく聞く問題だと思います。
こうした既存のUIフローを回帰的にテストし、問題を検出するというのは大切なことですが、同時に人力でやるにはとても労力のかかることでもあります。
そこで役に立つのがUIテストの自動化です。単純な検証フローであれば、人間の手ではなく機械的にそれらを行うことによって、かかるコストを削減しつつヒューマンエラーを取り除くことができます。

本記事ではそんなUnityでのUIテストの自動化を行う上で有用なAirtestというツール及びフレームワークと、それに付随して効果を発揮するPocoというフレームワークについて説明していきます。

## Airtest
![34292813.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/362e5e4b-c760-7dca-9372-cfbb1ddb56db.png)
Airtestは中国のNetEase社より提供されているアプリケーションのテスト用のツールになります。
ゲームを主にターゲットとして作られており、Android、iOS、Webなど、様々なプラットフォームにおけるアプリケーションに対応しています。
使用するスクリプトの言語はpythonで、特にアプリケーション側で用意など要さずにテストを実行することが可能です。

http://airtest.netease.com/

AirtestはIDEが提供されており、IDE上で端末の接続、テストスクリプトの構築、実行など様々な作業が快適に行えるようになっています。

<img width="800" alt="ide.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/9c6b13dd-66d5-4f9f-9051-b2681d7dbc82.png">


上が実際のIDEの画像です。ダーク調な感じで個人的にはとても使いやすいです。
中央のScript Editorが実際のテストのスクリプトを記述する場所です。デフォルトで必要なセットアップのスクリプトが記述されています。
テストのスクリプトを保存する時は.airという形式のプロジェクトとして保存します。テストの実行はこの.air単位で選択を行います。
テストを実行した際には.airの配下に結果などが出力されます。

### 環境の準備
Airtestのスクリプトの説明に入る前に、Airtestを使う上でのテスト環境の準備を行いましょう。
iOS、Android、Webなど様々な環境でテストを行うことが可能ですが、本記事ではAndroid実機をUSB接続しての環境構築について説明していきます。

<img width="400" alt="connection.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/99b83e52-fa08-17c4-c74c-0fa80c89cff6.png">


接続されているandroid端末は上の画像のConnection Panelという画面で確認することができます。
端末を接続し、refresh ADBというところを押すと、接続した端末が一覧に表示されます。(シリアル番号は消しています。)

![panel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/81ad7e05-e8bf-b6e8-7aed-f1ef3def6fce.png)


後はテストを実行する端末に対してconnectというボタンを押すと接続を行うことができます。簡単ですね。
一覧に端末が表示されない、connectボタンが押せないという場合にはUSB接続周りの設定やデバッグ接続の権限などを見直してみてください。

<img width="800" alt="connected.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/78163224-abf1-743a-3083-818ff71f10ac.png">

接続が完了すると、接続した端末のキャプチャ画面が表示されるようになります。
以上で接続は完了です。

## Poco
PocoはAirtestと共に提供されている様々なゲーム開発プラットフォームでのUI周りのテストを記述するためのフレームワークになります。
https://github.com/AirtestProject/Poco

Pocoを使うことで、iOS/Androidのネイティブアプリ、Cocos-2dx、Unityといった様々なプラットフォームでのUIテストをより実装に沿った形で記述することができます。

Unityであればヒエラルキー情報やTextといった指定のUIコンポーネントのオブジェクトを参照することが可能です。
例えば「決定」という文字のボタンを押す、特定のオブジェクトの子にある「Button」というコンポーネントがついているものを長押しする、など、かなり実装目線でのテストスクリプトを記述することができるので大変便利です。

Pocoを使用するには、アプリケーション側でSDKの導入とその対応が必要になります。Unity側の対応については後述しています。

## スクリプトの構築
Airtest+Pocoを使ったスクリプトはシンプルで、Python自体に知見がなくとも直感的に記述することが可能です。
基本的に、ユーザーのタップや長押し、UIがあるかどうかの確認といった1アクション単位で記述を行います。

```py:スクリプトの例
__author__ = "udon"

from airtest.core.api import *
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

# タップして
touch([0.0, 1.0])
#スワイプして
swipe([0.0, 1.0], [0.0, 2.0])

# キーポードから文字を入力して
keyevent("Test")
# 待機して
sleep(1.0)

# 「ボタン」というオブジェクトをタップ
poco("button").click()
```

### Airtest API
Airtestの提供するAPIは、端末上でのアクションのテストを直感的に書くためのものです。

```py:import_Poco
from airtest.core.api import *
```

機能を使用するには上のimportをスクリプトに追加します。
AirtestのAPIを使用することで、タップ、スクリーンショット、テキスト入力など、端末上で行いたいアクションをスクリプトで記述することができます。

```py:AirtestAPIの例
# 座標を指定してタップ
touch([0.0, 1.0])

# テキストの入力
text("hoge")

# スクリーンショット
snapshot(msg="hogehoge")

# 待機
sleep(1.0)
```

スクリプトは直接記述することももちろん可能ですが、IDEのGUIベースでボタンをぽちぽちしながら組み立てることも可能です。

<img width="326" alt="assistant.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/9030bbd9-4f21-61b9-e275-89dc91012b13.png">

上画像のように、IDEにAirtest AssistantというGUIが用意されており、ここにあるボタンを押すことでスクリプトに処理を加えることができます。
Airtest Assistantは端末を接続することで、タップなど端末のUIに依存したメニューを選択することができます。
そしてAirtestで面白いのが、タッチ操作において画像ベースの処理を直接的にスクリプトに起こすことができることです。

<img width="191" alt="touch.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/8167beb2-012b-e932-261e-11f9b2b9d423.png">


例えば上の画像はChromeのロゴと周りのピンクの部分も含めた画像を認識し、その座標をタップするという処理になります。
画像の設定もお手軽で、Airtest Assistantから画像を使用するメニュー（touch、swipe、existsなどUIに関わるもの）を選択し、接続した端末のキャプチャ画面からドラッグ&ドロップで行うことができます。

<img width="544" alt="select.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/802d6647-7b50-a434-d1d7-052113f091ca.png">


画像ベースでタップしたり、指定のUIがあるかどうか知りたいのような簡単なテストであれば、これだけで組めてしまいます。
他にも様々な機能があるので、詳しく機能が知りたいという方は公式のドキュメントを読むことを推奨します。

Airtest APIドキュメント
https://airtest.readthedocs.io/en/latest/all_module/airtest.core.api.html

iOSやandroidなどのプラットフォームごとのAPIも用意されているので、apkのインストールなどのシナリオから組み立てる場合には活用することができます。


### Poco API
Pocoは前述の通り、プラットフォームに対応したテストを書くためのフレームワークです。

```py:import_Poco
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
```

上のimportをスクリプトに追加することでPocoの機能を利用することができます。

```py:Pocoで使う基本的なUIProxy
poco("hoge")
```

Pocoでは上のスクリプトのようにGameObjectの名前を引数としたオブジェクトを多用します。
これをUI Proxyというもので、UI上のオブジェクトをスクリプト上のオブジェクトとして扱えるものです。
UI Proxyには様々な機能が用意してあり、それらを使うことで色んなアクションをそのオブジェクトに対して働きかけることができます。

```py:buttonのタップ
poco("button").click()
```

例えば上の１行で、「button」という名前のGame Objectを検索し、その位置をタップするという意味のスクリプトになります。

```py:PocoAPIの例
#テキストコンポーネントのオブジェクトの取得
poco("hoge", text="huga")
 
#オブジェクトのロングタップ(2秒)
poco("hoge").long_click(duration=2)
 
#オブジェクトが出現するのを待つ
poco("hoge").wait_for_appearance
 
#オブジェクトが消失するのを待つ
poco("hoge").wait_for_disappearance()
 
#オブジェクトの存在フラグ(bool)
poco("hoge").exists()

#子オブジェクト
poco("hoge").child("huga")

#子オブジェクトをタップ
poco("hoge").child("huga").click()
```

上がUI Proxyの機能の一例になります。UI Proxyを使ったアクションは色々用意されており、様々なUIテストのケースに対応することが可能です。
コンポーネントや階層構造など、Unity上の構造に合わせられるため、少しレイアウトやデザインが変更したことによるテストの破綻も防ぐことができます。
Pocoに関しても、詳しく機能が知りたい方は公式のドキュメントを読んでみてください。

Poco APIドキュメント
https://poco.readthedocs.io/en/latest/source/poco.pocofw.html

特定のGame Objectが出てきた後に、ボタンのGame Objectを押したい！特定のGame Objectにぶら下がってるTextを押したい！などUnityライクなテストをやるときに本当に便利なフレームワークになっています。

### スクリプトのサンプル
APIの説明だけじゃいまいち具体的なスクリプトのイメージがつかないかと思います。そんな時に役立つのが公式のサンプルです。
公式ではそれぞれのAPIの説明とともに、動画付きで具体的なスクリプトの説明を行っています。
実際にスクリプトを書いてみよう！となった方はこちらを参考にしつつ書くのがベターです。（筆者もかなり参考にしました。）

Airtest APIを利用したサンプル
https://airtest.readthedocs.io/en/latest/README_MORE.html#example

Poco APIを利用したサンプル
https://poco.readthedocs.io/en/latest/source/README.html#tutorials-and-examples

## テストする
AirtestやPocoのスクリプトについて説明したところで、どうやってテストを実行するか説明していきます。

### Poco SDKの準備
前述にもある通り、Pocoの機能を使うにはアプリケーション側にPocoのSDKを組み込む必要があります。
Poco SDK自体はGithubで公開されているので、そちらからクローンするかダウンロードをしてください。

Poco-SDK
https://github.com/AirtestProject/Poco-SDK

落としてきた中に「Unity3D」というフォルダがあるので、それをUnityのプロジェクトに持ってこればSDKの導入は完了です。
コンパイルエラーが起きてしまう場合には使っていないGUIシステム用スクリプトのフォルダを消してやると解決します。
筆者はuGUI環境だったので、「ngui」「fairygui」の2つのフォルダを削除しました。

次にPoco SDKがアプリケーション側に適応されるようにする必要がありますが、これについてはシンプルでPocoManagerというスクリプトをシーン上の適当なオブジェクトにアタッチすれば大丈夫です。

### テストの実行
IDEの再生ボタンを押すと、Script Editorにあるテストスクリプトが実行されます。

<img width="800" alt="test.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/47df915a-830c-472c-1241-dbac47126064.png">

実行中は現在の実行箇所が青くハイライトされ、ログは下のViewerに出力されます。
後はテストが終了するまで、見守れば大丈夫です。

ちなみにAirtestはCLIも用意されており、IDEからではなくCLIで実行することも可能です。
CLIはpythonのライブラリなので、使う際にはpipでインストールしてやってください。

```shell:CLIの導入
pip install -U airtest
```

```shell:CLIでテストの実行
airtest run {.airのパス} --device Android:///
```

テストの実行結果はlogと画像によって出力されます。
1つのコマンドを実行するたびにAirtest側でスクリーンショットをとっており、テストの状況がどうなっていたかを確認することができます。
また、logと画像を元にhtml形式のレポートを出力することも可能です。出力するにはIDE上でCmd+Lを押すと実行されます。

<img width="347" alt="report.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141004/167ab265-0daa-d8f7-cd71-0174f9438c3d.png">

出力すると、上の画像のようにいい感じに整形された見た目になります。
画像の参照パスが絶対パスになっているため、Jenkinsなど自動化したルーチンでhtmlを出力する際には注意が必要になります。

ちなみにhtmlの出力についてもCLIで実行可能です。

```shell:CLIでhtmlの出力
airtest report {.airのパス}
```

上記のコマンドを叩くことで、同様のhtmlが出力されます。

## まとめ
スクリプトの記述から実行まで説明したのでめちゃくちゃ長くなってしまいましたが、要約するとAirtestはUnityのアプリケーションのテストを行う上でかなり便利なツールということです。

画像やレイアウトベースのテストであれば、前述のように直感的なスクリプトを用意するだけで、いい感じのテストを作ることができます。
また、画像やレイアウトベースのテストではレイアウトの変更によってテストのメンテナンスが必要になるという懸念がありますが、Pocoという存在のおかげでヒエラルキーベースのテストを構成しそのリスクを緩和することができます。
前述にもあるようにCLIも用意されているため、Jenkinsなどのインテグレーションツールと組み合わせることでシステム全体を自動化したりできます。

もっと詳細なAirtestの機能が見たい方は、とにかく公式ドキュメントを読んでみてください。
本当に多機能なツールで、ここでは紹介しきれなかった有用なものも多く存在します。
Android以外で活用したい！という方も公式ドキュメントには各プラットフォームの対応が書いてあります。

https://airtest.readthedocs.io/en/latest/index.html