なんだかんだでプログラムが好き

私 すずきかつーき が主にプログラムに関する事を書いたり書かなかったりします

adbコマンドを使って実機で楽々モンキーテスト

adbコマンドを使って実機で楽々モンキーテスト

モンキーテストとは

モンキーテストとは、機器やソフトウェア、システムのテスト手法の一つで、対象箇所や操作手順などを事前に定めず、実施者がその場の思いつきで操作してみる方式。開発者の意図などを一切考慮しないことを「猿に使わせてみたらどうなるか」に例えた名称である。 http://e-words.jp/w/モンキーテスト.html より引用

すなわち「猿のように画面も見ずに連打」すればいいわけです。 が、人力はちょっとツライですね。もちろんアプリ自体にそういうモードを用意してあげることもできますが、結構骨が折れます。

そんな貴方に朗報です、Androidにはこれを自動でやってくれる仕組みが既にあるんです!

コマンド

adb shell monkey -v -p パッケージ名 --throttle イベント間隔(ms) --pct-touch 100 回数

これで、指定したパッケージアプリが自動で起動し(先に起動しておいても良い)、指定したイベント間隔で指定回数ランダムで画面を連打してくれます。 (パッケージ名はUnityのProjectSetting→Player→OtherSettings→PackageNameで指定したものです) image.png

このイベント間隔と回数をかけ合わせれば、終わる時間が分かるので、寝る前から次の日の朝まで、仮に10時間モンキーテストさせたい。

という場合は

10時間=600分=36000秒

なので、イベント間隔が100ms(=0.1秒)であれば、360000 を回数に指定すれば良いことになります。

<例>

adb shell monkey -v -p jp.com.MyApp --throttle 100 --pct-touch 100 3600000

注意

なお途中で辞めたくなってCtrl+Cで止めても、adbのshell上ではプロセスが動いてしまっているので、モンキーテストは止まりません。

止めるにはプロセスを指定してkillする必要があるため

adb shell kill $(pgrep monkey)

とやる必要があります。

また、例えばアプリに終了ボタンなんかがある場合はモンキーテストにより押されてしまい、終了してしまう可能性があります。 なので、そのあたりは先に無効化するなどの考慮が必要です。 (※間違っても課金が発生するような処理をそのままにしないように!)

準備

adbとは Android Debug Bridge からくる略称のツールで、Android実機とPCを接続しデバッグの補助をしてくれるものです。 これは AndroidSDK に含まれています。

コマンドプロンプトから adb とコマンドを打って、何かズラズラとadbのヘルプテキストが表示される場合は問題ありませんが、

>adb
'adb' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

こんな感じになってしまう方はadb.exeへのパスが通っていませんので、先にパスを通す必要があります。

自分でAndroidSDKを用意した方は AndroidSDKをインストールしたフォルダ\android-sdk\platform-tools なんですが、 Unityをインストールする際にAndroidSDKのインストールにチェック入れてついでにセットアップした場合は [UnityHubで設定したUnityのインストール先]\Hub\Editor\[Unityのバージョン]\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platform-tools と、少々奥深くに置いてあるはずです。

UnityHubで設定したUnityのインストール先

これをパスに設定、もしくはフルパスでadbコマンドを実行するようにしてください。

まとめ

このadbコマンドによるモンキーテストにより

  • 上記の意図しない操作による不具合を発見する

に加え、人間ではなかなか現実的でない

  • 24時間ゲームを起動し(遊び)続けても大丈夫か

というような、耐久テストも兼ねることができます。 (エイジングテストともいうかな?) 手元のアプリも公開する前にちょっと24時間ぐらい走らせてみたらどうでしょうか。 思わぬ進行不能バグ。 思わぬメモリリークが見つかるかもしれませんよ。

ではでは。

UnityRoom 開催 Unity1week 一週間ゲームジャム お題「あつめる」に参加した話

おじさんなので、まず普通に「集める」ゲームにはせず、ダジャレ駆動開発にいきなりなだれ込む事にしました。

「あ」を詰める

「あつめるあつめるあつめるあつめる・・・あ・つめる。 あ詰める」

というわけで、まず「あ」を(瓶とかに)詰める事にしました。

なお、ギューギューに詰めてる感を出すためにくにゃんくにゃんの物体を作りたくて「Unity SoftBody」で検索すると 何個かの有料のAsseetと Interactive Cloth で出来るよ。 という記事なんかがひっかかるんですが、今回Unity2019を採択したところ、InteractiveClothなんてものは無くなっていました。 (Clothはある) よくわからないので、BoxCollider2Dを各頂点毎に付けてSpringJointでつなげました。 後は

qiita.com

の要領でMeshをゆがませて表示してみました。

思ったよりもそれっぽく出来ましたが。 「で?」ってなったので、プロジェクトは捨てました。 グッバイ。

「A」「積める?」

次はローマ字です。

「ATSUMERUATSUMERUATSUMERUATSUMERUATSUMERU...A...TSUMERU、A Tsumeru???」

次に 「A」を積むことにしました。

なんだか、Unity初心者がまず作りそうな感じにしかならなそうだったので、これもボツ・・・

・・・にしたかったんですが、それ以降、まったく何一つ案が出てこなくなり(というか、大体のゲームって何かしらを集めてるじゃねぇかコノヤロウという気持ちになってきた)

「世に出ない神ゲーより、世に出るクソゲー」 「世に出ない神ゲーより、世に出るクソゲー」 「世に出ない神ゲーより、世に出るクソゲー

と3回つぶやきこっちを伸ばすことにしました。

伸ばす

Aだけではゲーム性皆無なので、Aの他に「a」「あ」「ア」「亜」「阿」を積めるようにしてみました。 正直「で?」って気持ちにこっちもなりましたが、ぐっと我慢します。

次になんとなくエモい絵にしたかったので、ここに書いてあることほとんどそのまま 丸パクリ インスパイアしてみました。 マジリスペクト。 パクリスペクト。

qiita.com

それだけで見た目がこんな感じに。

f:id:divide-by-zero-divide-by-zero:20190708162140p:plain

うーん。エモい?エモいって何? エモいっつーか、ぼやけたね。うん。

おじさんにはエモいとかよくわからないけど、若い子はこういうのが良いのかな。ハハハ。

完成?

娘が寝てくれなかったり、娘がAM5時くらいに起きてきてしまったり、猫がちょっかい出してきたりしましたが、後はスコア付けてレベル付けてランキング処理付けて・・・。 なんとか日曜日の17時くらいに完成。

題材が題材なので「完成」と思ったときが完成なんですが、まさか3時間ほど余ってしまったので急遽 成長要素 を足すことに。

LevelUpと同時に床がにょきにょきと伸びるようにしました。

  • 真ん中の床が伸びる
  • 真ん中の床がすごく伸びる
  • 右の床が伸びる
  • 右の床がすごく伸びる
  • 右の床の角度があがる
  • 左の床が伸びる
  • 左の床がすごく伸びる
  • 左の床の角度があがる

成長要素は上記8つ。

本当はユーザーに選択させたかったんですが、そのUIを作る余裕は流石になかったので、LevelUp時にランダムでどれかが発動するようにしています。

そんなところでタイムアップ。 締め切り5分前ぐらいに滑り込みで登録しました。

unityroom.com

あとがき

なんてことの無い、平凡なゲームですが、それでも「一つの作品として完成させる」ことには意味があると思っています。 というか、そう思わないとやってられないです。

今(2019年7月8日 17:00)時点で、登録数は293作品です。 僕の観測範囲内だけでも遅刻勢をチラホラ見かけるので、300は確実に届くでしょう。

たった1週間で300もの作品が出来上がる。 そこにかかった熱量、そしてそれをプレイする人たちの可処分時間の消費を考えるだけでも、これはもう一つの一大カーニバルです。(そうなの?)

このビッグウェーブに乗らない理由がないです。 そこの貴方。 次回参加してみるのはいかがでしょうか。 あ、別に今回参加でも良いみたいですよ? 遅刻大歓迎だそうですから・・・。

クリックしたときに先にUIに当たっているかどうか

UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject();

を使うと色々書いてあるが、これだとAndroid buildなんかでは使えない。

UnityEngine.EventSystem.current.currentSelectedGameObject != null

こっちの方がマルチプラットフォームで使えるのでは?と思っている。

配列(IEnumerable)の中からランダムで一つ返却するLinq拡張

作っておくと、ちょっと便利

自分はテストなんかにすごく使ってます。無いと地味に困るレベル。

public static class LinqExtensions
{
    public static T RandomAt<T>(this IEnumerable<T> ie)
    {
        if (ie.Any() == false) return default(T);
        return ie.ElementAt(Random.Range(0, ie.Count()));
    }
}

使い方

   public void Start()
    {
        var data = new []{ 1, 2, 3, 4, 5, 10, 20, 31, 32, 33, 34, 35 };
        var r1 = data.RandomAt();
        Debug.Log("r1:" + r1); 
        var r2 = data.Where(num => num % 2 == 0).RandomAt();
        Debug.Log("r2:" + r2);
    }

IEnumerable<T>なので

var r1 = data.RandomAt(); のように、配列に直接使ってもよいですし、

var r2 = data.Where(num => num % 2 == 0).RandomAt(); のように、他のLinqと組み合わせてもよいです(この例では、int配列の偶数の値からランダムで一つ)

Xamarin.Android で BOOT_COMPLETED のテスト

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED

上手くいかない場合は STOP状態 を疑う。 参考: yuki312.blogspot.jp

ちなみに、ここにはSTOP状態でも強制でbroadcastインテントでreceiverのテストが出来ると書いてあるが、僕の環境だと例外が発生してしまった。

色々調べた結果、フラグを直接10進で入れればよさそう

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -f 32

なお、このSTOP状態、インストール後一度も起動していない状態みたいに書いてあるので、一度起動すれば良いと思いがちだが、アプリを強制停止(履歴からのフリックやごみ箱ボタンでの削除など、メーカーによってまちまち)すると、容易にSTOP状態に戻ってしまうっぽい。

確認の為に、packages-stopped.xml というファイルを見るとよいと色々なところに書いてあったが、自分の環境だと見当たらなかった。 (仮に見つかっても Permission denied って言われるのは目に見えているのだけれど)

なお、プログラム内部からBOOT_COMPLETEDを受け取るかどうかを変更する方法もある。

<例>

                var pm = Activity.PackageManager;
                if (isStartup)
                {
                    pm.SetComponentEnabledSetting(new ComponentName(Activity.ApplicationContext, Java.Lang.Class.FromType(typeof(DeviceBootReceiver))),ComponentEnabledState.Enabled,ComponentEnableOption.DontKillApp);
                }else{
                    pm.SetComponentEnabledSetting(new ComponentName(Activity.ApplicationContext, Java.Lang.Class.FromType(typeof(DeviceBootReceiver))), ComponentEnabledState.Disabled, ComponentEnableOption.DontKillApp);
                }

YieldableNcmbQuery

なんとNCMBのUnityPluginがWebGLビルド対応したそうです。(未テスト)

github.com

ところで、このNCMBの検索処理(NCMBQueryのFindAsync)って、Asyncと付いてるだけあって非同期だと思うんですけど、コールバックでResultとError(Errorじゃなければnull)が格納される仕組みになってるんですよ。

でも、コールバックってネストが深くなって辛いですよね

Unityでコールバック形式の処理をコルーチンを使ってさも手続き処理風にやりたい場合

これを

       var query = new NCMBQuery<NCMBObject>("Ranking");
        query.FindAsync((_result, _error) =>
        {
            //後続処理 
            if (_error == null)
            {
                foreach (var data in _result)
                {
                    Debug.Log(data);
                }
            }
        });
        Debug.Log("Finis....h????"); //ここはすぐ処理されちゃう

こんな感じで

   void Start ()
    {
        StartCoroutine(ScoreLoadIterator());
    }

    private IEnumerator ScoreLoadIterator()
    {
        var query = new NCMBQuery<NCMBObject>("Ranking");
        List<NCMBObject> result = null;
        NCMBException error = null;
        query.FindAsync((_result, _error) =>
        {
            result = _result;
            error = _error;
        });
    
        //resultもしくはerrorが入るまで待機
        yield return new WaitWhile(() => result == null && error == null);
    
        //後続処理 
        if (error == null)
        {
            foreach (var data in result)
            {
                Debug.Log(data);
            }
        }
        Debug.Log("Finish!!!");
    }

やったりしますが、これはこれで、resultとerror格納用の変数を用意しなきゃいけないし、null初期化しておく必要もあってvarで変数宣言も出来ないので嫌な感じ。

もう継承してそういうクラス作っちゃおう

public class YieldableNcmbQuery<T> : NCMBQuery<T> where T:NCMBObject
{
    public List<T> Result { private set; get; }
    public NCMBException Error { private set; get; }

    public CustomYieldInstruction FindAsync()
    {
        Result = null;
        Error = null;
        FindAsync((objects, error) =>
        {
            Result = objects;
            Error = error;
        });     
        return new WaitWhile(() => Result == null && Error == null);
    }

    public YieldableNcmbQuery(string theClassName) : base(theClassName)
    {
    }
}

こんな感じで薄く拡張したサブクラスを作っておくと

   void Start ()
    {
        StartCoroutine(ScoreLoadIterator());
    }

    private IEnumerator ScoreLoadIterator()
    {
        var query = new YieldableNcmbQuery<NCMBObject>("Ranking");
        yield return query.FindAsync();
    
        //後続処理 
        if (query.Error == null)
        {
            foreach (var data in query.Result)
            {
                Debug.Log(data);
            }
        }
        Debug.Log("Finish!!!");
    }

非常にすっきりします。 やったね。

AndroidでXamarin.FormsのListViewのItemTemplateを20回追加すると配列範囲外で落ちる話

var listView = new ListView();
listView.ItemTemplate = new DataTemplate(typeof(TextCell));

よく見る形ですが、

この、ItemTemplateを20回ほどsetすると例外発生して死にます。

まぁ、普通は初期化の時1回だけやりそうなもんですが、画面遷移を高速化しようとか思って、ListViewのInstance使いまわしで、ItemSourceの更新と同時に毎回ItemTemplateをセットしてたりすると時限爆弾のように20回目で死ぬのでつらいです(経験談

なお、

var listView = new ListView();
for(var i = 0;i < 20;++i){
    listView.ItemTemplate = new DataTemplate(typeof(TextCell));
}

こんな風に一気に20回入れても死にません。

var btn = new Button() { Text = "ボタン" };
btn.Clicked += (sender, args) =>
{
    listView.ItemTemplate = new DataTemplate(typeof(TextCell));
};

これでボタンを20回ほど押すと死亡(例外発生)です。

原因まで調べられてないですし、使用しているバージョンなんかによっては発生しなかもですが、そこまで調べてません。

とりあえず、Xamarin.FormsのListView使ってて、急にJava.Lang.ArrayIndexOutOfBoundsException: length=20; index=20なんて例外出てきた人は疑ってみるとよいのでは。

ではでは。