[Android]バグの再現は手でやらない

# バグの手元での再現を自動化したい
最近バグ系のissue潰しが多く，こんなことをたくさんしてます．

1. バグを手元の端末で再現
2. コードの修正
3. バグが消えたか端末上で確認
4. 様々な条件(異なるOS等)で，バグが消えたか確認
5. PullRequestを出す

特に，バグの再現手順に
文字入力(ログイン処理等)なんかがあると何度もやるのつらいです．．

そこで，**MonkeyRecorder**と**MonkeyRunner**を使って，
繰り返し行う端末操作をスクリプト化してみました．
今回は，その方法について書きます. 

話の流れは以下のようになります．
- **MonkeyRecorder**を使った端末操作の記録
- **MonkeyRunner**による端末操作の再現

# **MonkeyRecorder**を使った端末操作の記録
## 端末の用意
まず，バグを再現する端末を用意します．
実機で行うなら，実機をPCにUSBでつなげばOKです．
エミュレータなら，エミュレータを起動してください．(以下，エミュレータ起動参考コード)

```bash
ls ~/.android/avd
# Nexus_5_API_22_x86.avd	Nexus_5_API_22_x86.ini	metrics
<path_to_android-sdks>/tools/emulator -avd Nexus_5_API_22_x86
```

対象アプリも起動しておきましょう．(手動で起動してもokです)

```bash
# adb shell am start -n <package名>/<対象Activityクラス名>
$ adb shell am start -n com.ikota.flickrclient/.ui.PopularListActivity
```

## MonkeyRecorderで操作を記録
**MonkeyRecorder**の起動はスクリプトから行います. 
以下スクリプト(`run_monkeyrecorder.py`)を手元に用意してください. 

```py:run_monkeyrecorder.py
from com.android.monkeyrunner import MonkeyRunner
from com.android.monkeyrunner.recorder import MonkeyRecorder
device = MonkeyRunner.waitForConnection()
MonkeyRecorder.start(device)
```
`run_monkeyrecorder.py`はこちらの[gist](https://gist.github.com/ishikota/74750cc4bd92e50c71ab#file-run_monkeyrecorder-py)にも置いてあります．
(以下 `~/Desktop/run_monkeyrecorder.py` にこのファイルがあるとして進めます)

それでは，**MonkeyRecorder**を起動しましょう．

```bash
$ <android-sdk path>/tools/monkeyrunner ~/Desktop/run_monkeyrecorder.py 
```

起動すると，以下画面右側にあるようなwindowが表示されるはずです．(これが*MonkeyRecorder*です)![monkeyrecorder_init_s.png](https://qiita-image-store.s3.amazonaws.com/0/80986/15b9cd77-a781-b2c3-6642-11e0be617c11.png)

MonkeyRecorder上には，以下2つの画面が表示されています．

- 記録対象端末のスクリーンキャプチャ (左側)
- 行われた端末操作のログ (右側)

では，MonkeyRecorder上で記録したい操作を行いましょう．
MonkeyRecorder上での主な操作は以下になります．

- タップ : MonkeyRecorder上のスクリーンキャプチャの任意の場所をクリック
- 文字入力 : MonkeyRecorder上の`TypeSomething`を押して，入力したい文字を入力
- スワイプ : MonkeyRecorder上の`Fling`を押して，スワイプ方向の情報を入力

MonkeyRecorder上の操作は，端末上で実行され，
以下画像のように右側の領域に操作ログが追加されていきます．

![monkeyrecorder_after_takelog_s.png](https://qiita-image-store.s3.amazonaws.com/0/80986/51afd60c-058d-b3fe-b9cd-81d8b94929f3.png)

記録したい操作が終わったら,MonkeyRecorder上の`Export Actions`をクリックして，
ログファイルを任意の場所に作成してください．
(以下，`~/Desktop/bug_reproduce.txt`に作成したとして進めます)

# **MonkeyRunner**による端末操作の再現
## その前に...
MonkeyRecorderで作成したログファイルは，そのままではMonkeyRunnerに渡せません．
以下のような，スクリプトへの変換が必要になります．

![log2script.png](https://qiita-image-store.s3.amazonaws.com/0/80986/e4ef543e-23ae-306b-ea8e-fc7909c18c72.png)
(左が元のログファイル，右がスクリプト化したもの)

今回，この変換操作を行うスクリプト`monkey_converter.py`を作成したので，
これを使って再現スクリプトへ変換します．
変換スクリプトは，こちらの[gist](https://gist.github.com/ishikota/74750cc4bd92e50c71ab#file-monkey_converter-py)に置いてあります．
(以下，変換スクリプトは，`~/Desktop/monkey_converter.py`にあるものとします)

使い方は以下のようになります．

```bash
cd ~/Desktop
# python monkey_converter.py <log file> <script_name>
$ python monkey_converter.py bug_reproduce.txt bug_reproduce.py
```

これで,`~/Desktop/bug_reproduce.py`という名前で再現スクリプトができました．

## 再現スクリプトの実行
あとは，以下コマンドで再現スクリプトを実行すれば，記録した動作が端末上で実行されます．

```
$ <path to android-sdks>/tools/monkeyrunner bug_reproduce.py
```
コードを修正したら上のコマンドを実行するだけで，
修正が上手くいったか確認できるので便利です．

# 最後に
この再現スクリプトは，端末の座標系に依存しているので，
**作成した端末と異なる端末で実行すると期待した通りに動かない**ので注意してください．
ただし，「実機のNexus5で再現スクリプトを作成して，Nexus5のエミュレータで再現スクリプトを実行」は可能です．

開発アプリのログイン/ログアウト処理をスクリプト化しておくと，
Emailアドレス+パスワードの入力+ログイン処理が自動化できて，
個人的に便利だなと感じています．
### 追記
「MonkeyRecorderのログファイル」から「再現スクリプト」への変換を行う
`monkey_converter.py`で行っていることについて以下に簡単に書きました．

## 変換スクリプトでやっていること
変換スクリプトでは，以下のような変換を行っています．
`TOUCH|{'x':864,'y':152,'type':'downAndUp',}`
↓

```
device.touch(864, 152, MonkeyDevice.DOWN_AND_UP)
print 'Executing : device.touch(864, 152, MonkeyDevice.DOWN_AND_UP)'
MonkeyRunner.sleep(5.0)
```
本来なら上の１行だけで十分ですが，
2行目のログを吐くコードと，
3行目の`ACTION_INTERVAL_IN_SECONDS`にセットされた秒数(デフォルトは5秒)待つコード
が追加してあります．

3行目のコードがないと，MonkeyRunnerから送った命令が端末で実行されている最中に
次の命令が送られてしまい，期待した動作にならない場合があります．
特にエミュレータで再現する場合は，この秒数を5秒など大きめにとっておくのがオススメです．

端末で行われる操作のタイミングが合わない場合は，
作成されたスクリプトの'MonkeyRunner.sleep(5.0)'の部分を調整してみてください．