Airtest + Poco(Unity) であなたが欲しいかもしれない情報


## 概要
テストが全く存在しないUnityPJにて、半年間必死でAirtest+Pocoを用いて厳密なシナリオテストの導入を行いました。
公式ドキュメントがざっくりとしか書かれていなかったため、あらゆる記事から情報をかき集めた内容をここにまとめます。
あらゆる情報をごった煮で載せるので、頑張ってカテゴライズはしようと思います。
以下の内容で困っている人には助けになると思います。
- ログのHTML出力をコードで行いたい
- 他の.airファイルを呼び出したい
- Androidの実機に対してコマンドラインでテスト実行したい
- AirtestからAndroid実機に対してアプリインストール・起動コマンドを実行したい
- AirtestIDE上でWindowsアプリがDeviceタブに埋め込まれないようにしたい


## まずはじめに
私が半年間かけて探し出した優良記事をまずまとめておきます。
- https://gitee.com/AirtestProject/Poco
    - Pocoの使い方が丁寧に記載されている
- https://github.com/AirtestProject/Airtest/blob/master/docs/wiki/code/code_example.md
    - Airtestの公式Github内で例も交えて紹介されているREADME
- https://qiita.com/mmm_hiro/items/f6776d3458f767cf9d09
    - Unityの関数をPoco経由でAirtestから呼び出す事が出来る

公式ドキュメント集
[Airtestのインスト](https://airtest.doc.io.netease.com/en/tutorial/3_Poco_introduction/)
[Airtestの公式ドキュメント](https://airtest.readthedocs.io/en/latest/README_MORE.html)
[Pocoの公式ドキュメント](https://poco.readthedocs.io/en/latest/source/poco.sdk.html)

この記事では上記の優良記事で、個人的に欲しかった部分を翻訳してまとめてる程度です。
+aで私の経験的に便利だったものを少しだけ添えています。

# エディタ(IDE)設定関連
## Windowsアプリの埋め込み無し設定
デフォルト設定でWindowsアプリを指定するとDevicesタブに埋め込まれてしまう。
それを防ぐためにSettingsを開いて「Connect windows without embedding」をチェックすれば開発しやすくなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141541/6510831d-2b23-d6ca-3cf7-cf1fc42129c0.png)

# 実機操作関連
## Androidアプリのテスト
### 実機でテストを実行
WindowsとAndroidの実機をUSB接続している前提でお話します。
adbコマンドが使えるように設定をお願いします。
```shell:gitbash
$ adb devices
List of devices attached
00adbe3a8489b958        device 

$ /d/AirtestIDE/AirtestIDE.exe runner {.airまでの絶対パス}/Sample.air --device Android://127.0.0.1:5073/00adbe3a8489b958
```
`Android://127.0.0.1:5073/00adbe3a8489b958`
USB実機の場合はローカルホスト指定
特別設定していない限りポートも5073固定のはず
最後のシリアル番号は`adb devices`で取得したデバイスIDを入れる

### auto_setupでデバイス指定も可能
auto_setup関数のdevices引数にデバイス情報を渡している場合は、コマンドでデバイス指定無しに出来る
```python
# -*- encoding=utf8 -*-
__author__ = "ohyoso"

auto_setup(__file__, devices=["android://127.0.0.1:5037/00adbe3a8489b958"])
# テストコード
```

### 複数端末をコード上で切り替えて操作可能(らしい)
先述のauto_setupでのdevicesで2つ以上指定していた場合、IndexまたはデバイスIDで現在操作したい端末が切り替え可能らしい(試したことは無いです)
```python
auto_setup(__file__, devices=["android://127.0.0.1:5037/00001", "android://127.0.0.1:5037/00002"])
# devicesでの登録順で多分Indexが振られていて、切り替え可能
set_current(0)
set_current(1)

# デバイスIDを指定して、固定デバイスで操作可能(だと思う)
set_current("00001")
set_current("00002")
```

### アプリのインストール/アンインストール
```python
uninstall("com.example.myapp") # アプリケーションIDを指定する
install(r"D:\TestApp\SampleApp.apk") # apkのパスを指定する
```

### アプリの実行/停止
```py
#スタート・ストップ両方アプリケーションIDを指定
start_app("com.example.myapp")
stop_app("com.example.myapp")
```

# Poco(Unity)関連
## クリック位置の調整
Unityの場合、Anchor設定によってクリック位置の調整が必要となる。
focus関数を間に挟むことも可能なので、状況に応じて使い分けることが出来る。
```py
poco("hoge").click([0.5, 0.5]) #UIパーツの中心を押す
poco("hoge").click("center") #↑と同じ挙動

hoge_item = poco("hoge").focus([0.5, 0.5]) #中心にフォーカスされたUIパーツを取得可能
hoge_item.click()
hoge_item.long_click(duration=3) #3秒間クリック
```

## Unityの関数を呼び出す
UnityへのPoco導入及び、Unityの関数を実行できるようになるまでを別記事にしている。
そちらを参照してほしい。
(FIXME: 後々URL記載予定)

# Airtestの特殊操作

## 別の.airファイルを呼び出したい
usingで呼び出し可能
```py:Utility.air
def hello():
    log("Hello world")

class Dog:
    def __init__(self):
        
    def cry(self):
        log("bow")
```

```py:Main.air
using('Utility.air')

from Utility import hello, Dog
hello() # Hello world

dog = Dog()
dog.Bow() # bow
```

# ログ関連
## ログファイルの生成方法
### AirtestIDE上
AirtestIDE上で Ctrl+L を押すと、テストの一番最後の実行履歴からHTMLのログデータを出力してブラウザ上で確認可能

### テキストログファイルの生成方法
auto_setup関数でlogdirをTrueにすると、スクリプトのディレクトリにlogフォルダが作成される
logフォルダには、log.txtとスナップショットの画像が格納されている。
```python
# -*- encoding=utf8 -*-
__author__ = "ohyoso"
auto_setup(__file__, logdir=True) 
```

### テキストログファイルの場所を変更したい
テキストファイル名と、保存先を任意に変更可能
auto_setupの記述は必要無し
```python
# -*- encoding=utf8 -*-
__author__ = "ohyoso"
from airtest.core.settings import Settings
from airtest.core.helper import set_logdir

Settings.LOG_FILE = "sample_log.txt"
set_logdir(r'D:\LogTest')
```

### HTMLレポートの作成
log.txtをHTML形式に変換してくれる、simple_report関数を呼び出す
以下のコードの場合、Sample.air/log/log.txtテストコード実行後に実行ログをHTMLに変換する
```python:Sample.air
from airtest.report.report import simple_report
auto_setup(__file__, logdir=True)

# テストコード

simple_report(__file__,logpath=True,output=r"D:\Hogehoge\Sample.air\log\log.html")
```
simple_reportの設定可能な引数は以下の通りである
```python
simple_report(filepath, logpath=True, logfile='', output='')
```
filepath : 対象の.airパス
logpath : Trueの場合、filepath内のlogフォルダが指定される
logfile : ログファイルが別の場所に出力されている場合、指定する際に使う
output : HTMLの出力パス

先程記述したサンプルコードは、Sample.air実行時の`Sample.air/log/log.txt`を`log/log.html`に変換している。

更にカスタマイズ可能な`LogToHtml`関数が用意されているが、個人的には上記で満足だと考える。
もし詳細を見たい場合は[__こちら__](https://github.com/AirtestProject/Airtest/blob/master/docs/wiki/code/code_example.md#report-generation-logtohtml)を参照してほしい。

## スナップショットを取る方法
「ログファイルの生成方法」で紹介したログフォルダに保存されるスナップショットを撮れる
```python
snapshot(filename="123.jpg", msg="Homepage screenshot", quality=90, max_size=800)
```
filename : ファイル名を指定可能
msg : HTMLログファイルでスナップショットに紐づくメッセージを仕込むことが出来る
quality : 1~99でクオリティを指定出来る
max_size : 最大サイズ

## ログ出力
### log関数
log関数を使うことで、標準出力+ログファイルで確認可能
```python
# ログに対して説明を付与することが可能
character_name = "Hogehoge"
log(character_name,desc="キャラクター名")

# String以外のデータ構造を渡すことが可能
data = {"name": "Hogehoge", "power": 123456}
log(data,desc="キャラクターの現在のパラメータ")

# Exceptionの詳細をトレースバックすることも
try:
    1/0
except Exception as e:
    log(e)
    
# ログにタイムスタンプやスナップショットを付与することも
import datetime
log("123", timestamp=datetime.datetime.now(), desc="〇〇の現在状況スナップショット", snapshot=True)
```

試しにメモ帳でデモテストを組み、紫一色にした後に以下のログを仕込んだ際の結果を示す
(もっと良いデモを組みたかったが、ちょうどいいサンプルが無かった)
```py
log("test", timestamp=datetime.datetime.now(), desc="紫一色変更した際の時間と状況", snapshot=True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141541/7ade58a7-578a-74f2-e91f-a7648e679f0f.png)

### ログレベルの変更
出力するログレベルを制御することで、エラーの早期発見に役立つだろう
```python
__author__ = "Airtest"

import logging
logger = logging.getLogger("airtest")
logger.setLevel(logging.ERROR)
```

# おまけとまとめ
pocoのAutoRecording機能って使ったことありますでしょうか。
![pocoauto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141541/e96b3a05-86b8-fa29-4b82-71bd1e4749b6.png)
Unityで制作中のゲームアプリで使ってみたのですが、個人的にあまり芳しくなかったです・・。
- まずAndroidアプリでしか使えない
- アプリ内のレイヤー順を考慮してくれず、前レイヤーの透明パネル等を選択してしまう
- 更にPocoのアンカー設定で判定されるため、当たり判定がそもそものUIと全く別のところにあることも

正直この機能があるから簡単に導入出来そうと積極的になっていましたが、使い勝手が悪い事から結果シナリオテストの導入をオミットしてしまいました。
HTMLのログビュワー等素敵なツールが揃っているので、局所的な繰り返しテスト、モンキータップテストには最適だと思います。
良きAirtestライフを～～