Android Junitユニットテスト、非同期テスト方法の概要と非同期テストフレームワークガイド

6567 ワード

Android Junitユニットテスト、非同期テスト方法の概要と非同期テストフレームワークガイド
本文で解決した問題
1.junitを使ってAndroidユニットのテストをする方法
2.junitを使用してAndroid非同期インタフェースユニットのテストを行う方法
3.著者らがカプセル化したフレームワークを用いて、junitでAndroid非同期インタフェースユニットテストを優雅に行う[doge]
JunitはAndroid Studioがオリジナルでサポートしているテストフレームワークとしてユニットテストを容易に実行でき、注記@Testで直接caseをテストしてサブスレッドで実行する方法をマークすることができます.@UiThreadTestとマークされている場合、テストcaseはuiスレッドで実行されます.
しかしjunit自体の設計では,testメソッドごとに実行が終了すると,そのメソッドの実行スレッドが一括してkillされるため,非同期呼び出しのメソッドではサブスレッドが一括して回収され,コールバック関数も実行できない.
栗を挙げると、以下のテストcaseではコールバックが届かず、エラーが発生します.

@Runwith(Junit4.calss)

class Test1{

    public static final String TAG="sample test";

    @Test

    public void test1(){

        new YourAsyncJob().run(new YourAsyncTestCallback(){

            @Override

            public void onFinished(){

                Log.i(TAG, "async call back");

            }

        });

        Log.i(TAG, "run async ok");

    }

}


解決策ブロックtest case
テストスレッドが死んだ後、対応サブタスクが失敗する以上、最も直接的な方法は直接対応をブロックすることです.
1スレッドロック
Javaが極めて使いやすいオリジナルapiを提供してくれて嬉しいです.CountDownLatchはスレッドを直接ブロックし、完了を待つことができます.await()メソッドが呼び出されると、対応するスレッドがcountdownlatchのcountが0になるまでブロックされ、実行が再開されます.そこで私たちは以下の案を得ました

@Runwith(Junit4.calss)

class Test1{

    public static final String TAG="sample test";

    @Test

    public void test1(){

        final CountDownLatch mutex = new CountDownLatch(1);

        new YourAsyncJob().run(new YourAsyncTestCallback(){

            @Override

            public void onFinished(){

                Log.i(TAG, "async call back");

                mutex.countDown();

            }

        });

        Log.i(TAG, "run async ok");

        mutex.await();

    }

}


走ってみると、できるようで、ロゴが出てきました.
しかし、実際の使用では新たな問題に直面した.
2 Looperブロッキング(Handler thread)
sdkをしたことのある学生は、このようなニーズに直面する可能性があります.
ビジネス・エンドの同級生プライマリ・スレッド(またはhandlerスレッド)は、非同期リクエストを開始し、実行が完了すると(handlerによって)プライマリ・スレッド(またはhandlerスレッド)にコールバックします.
この問題を処理すると,[スキーム1]のコールバック関数も実際には非同期スレッドでしか実行できず,開始スレッド(テストスレッド)に戻って実行を切り替えることができないことが分かった.これは明らかに私たちの優雅な非同期インタフェースのテストニーズを満たすことができません.そこで,新しいスキーム2 Looperブロッキングがlooperブロッキングによって実現され,コールバック関数のスレッド切替が必要となる.
上記の問題の根本はhandlerスレッドのコールバックと切り替えの問題であり,この場合テストスレッドにはlooperがないため,このような環境を作る必要がある.同時にlooperが存在する以上,そのスピン機能も閉塞に対する我々のニーズを満たすことができ,この場合,以前のCountDownLatchを直接捨てることができるようになった.
次のコードを入手しました

@Runwith(Junit4.calss)

class Test1{

    public static final String TAG="sample test";

    @Test

    public void test1(){

       Looper.prepare();

        //final CountDownLatch mutex = new CountDownLatch(1);

        new YourAsyncJob().run(new YourAsyncTestCallback(){

            @Override

            public void onFinished(){

                Log.i(TAG, "async call back");

                //mutex.countDown();

                Looper.myLooper().quitSafely();

            }

        });

        Log.i(TAG, "run async ok");

        // mutex.await();

        Looper.loop();

    }

}


このような過程に適応できるように見え、楽しくテストを始めたが、すぐに新しい問題に出会った.
3 Handler thread+パッケージ
自動化テストの利点は、テストcaseを自動的に一括して実行することです.そして次の過程で

    @RunWith(Parameterized.class)


パラメータ化されたバッチ入力を実行するために、@Parameterized.Parameters注記が使用される.
そこで新たな問題が発生し,実際の実行時@Testメソッドは同じサブスレッドで実行されるため,Looper.prepare()を複数回実行することは明らかに現実的ではない(RuntimeExceptionがある).
そこで最も直接的に解決する方法は、最初からprepareでいいですか?
実際にはできませんが、このような場合は次のような問題があります.Looper.myLooper().quitSafely()をいつ実行するか
Looperに詳しい方はご存知ですが、quit以降はLooperのqueueは使えません.また、ブロック@Testのスレッドを終了まで再開するためには、[スキーム2]に基づいてloop()を解除する必要がある.
そこで,このような状況を満たすためには,スレッド間コールバックを必要とするインタフェーステストを別のHandlerThreadで実行するしかない.その後、コールバックの実行が完了する前に、最初のテストスレッド@Testスレッドをブロックし、HandlerThreadの生存を保証する.(ここではsetupのたびに新しいスレッドを作成します.なぜなら、TestCaseにまたがってこのスレッドを再利用することはできません.Caseが実行されると、このスレッドはシステムによって強制的に回収されます)
そこで以下のような内容が得られた

@RunWith(Parameterized.class)

class Test1{

    public static final String TAG="sample test";

    private HandlerThread t;

    private Handler tH;

    @Parameterized.Parameters

    public static Collection data() {

        //    

        return Arrays.asList(new Object[][]{

                {"TES-1085-7", "TES-1085-7"},

                {null, null},

        });

    }

    @Before

    public void setUp() throws Exception {

          t = new HandlerThread("test");

          t.start();

          tH = new Handler(t.getLooper());

    }

    @Test

    public void test1(){

        final CountDownLatch mutex = new CountDownLatch(1);

        tH.post(new Runnable(){

            @Override

            public void run(){

                new YourAsyncJob().run(new YourAsyncTestCallback(){

                    @Override

                    public void onFinished(){

                        Log.i(TAG, "async call back");

                        mutex.countDown();

                    }

            }

        });

        Log.i(TAG, "run async ok");

        });

       mutex.await();

    }

}


4異常の最適化と処理
[スキーム3]一般的なロットテストを基本的に処理できる.しかし、厳密なプログラマーとして、このようなコードは明らかに優雅ではありません.そこで、二次パッケージが必要です.パッケージされたコード呼び出しは、以下のように簡潔になります.

@RunWith(Parameterized.class)

class Test1 extend ZCCBase{

    public static final String TAG="sample test";

    @Parameterized.Parameters

    public static Collection data() {

        //    

        return Arrays.asList(new Object[][]{

                {"TES-1085-7", "TES-1085-7"},

                {null, null},

        });

    }

    @Before

    public void setUp() throws Exception {

         super.setUp();

    }

    @Test

    public void test1(){

        runAsyncTest(new AsyncTest(){

            @Override

            public void onRun(){

                new YourAsyncJob().run(new YourAsyncTestCallback(){

                    @Override

                    public void onFinished(){

                        onAsyncTestFinished();

                    }

            }

        });

    }

}


優雅になったのか、具体的なフレームワークとdemoは私のgithubを参考にすることができます.
まだ終わっていません.私たちはまだ一つの問題が残っています.実際の操作では,非同期スレッド中のAssert Errorが直接投げ出されるとAndroid StudioのRun Textウィンドウに直接表示されるのではなく,プロセスcrashのログとして表示されるので,実際の原因はlogcatで調べる必要がある.これは明らかに健全ではないので,対応するThrowableをテストスレッドに戻す必要がある.この機能も私のgithubにカプセル化されています.
That's all, thanks for your time