連載
» 2014年06月02日 13時00分 UPDATE

Androidアプリ開発テスト入門(5):Android Mockを利用してHTTP通信をテストするには (1/3)

日本Androidの会テスト部が、いままで培ってきたAndroidアプリ開発におけるテストのノウハウを、実際のテストコード例とともに紹介していきます

[長谷川孝二,日本Androidの会テスト部]

テストしづらいものをテストする

 本連載「Androidアプリ開発テスト入門」では、Androidアプリを開発している方のためにテストの基本的なノウハウを解説しています。第5回では、Android Mockを利用したテストについて解説します。

注意! Android Mockについて(2014年6月2日追記)

本記事で紹介しているAndroid Mockは、2012年11月に開発・サポートの終了を宣言されました。より一般的な下記モックフレームワークのDalvik仮想マシン対応がほぼ完了しており、その役目を終えたためです。

  • Mockito:1.9.5rc-1でDalvik対応がマージされました
  • EasyMock:3.2でDalvik対応がマージされました

新規に作成するプロジェクトでは、これら他のモックフレームワークを使用することをお勧めします。@ITでは、他のモックフレームワーク導入方法を紹介する記事を掲載予定です。

なお、本記事のサンプルコードをMockito向けに書き換えたものを公開しています。以下をご参照ください。

さらに、EasyMock向けに書き換えたものも公開しています。


 第4回の「スマホアプリに必須なデータ永続化のためのDBテスト」では、データベース内部のデータによって実行結果が影響を受けず、常に同じ実行結果を得る工夫をすることによってテストの自動化を実現しました。

 それでは、Android端末の外に存在するWebサーバとの通信を伴う処理のテストはどのように自動化すべきでしょうか。外部のWebサーバが常に理想的なレスポンスを返してくれるとは限りませんし、異常系のテストも必要です。常に固定レスポンスを返すテスト専用サーバを立てることも可能ですが、テストと連動させるのは複雑であり、構築にコストが掛かってしまいます。

 この問題を解決するため、「モックオブジェクト」というテクニックがあります。例えば、HTTP通信では実際にWebサーバと通信するのは「org.apache.http.client.HttpClient」実装クラスのインスタンスですが、これを「モック(偽の)」オブジェクトに置き換えます。

 そして、「HttpClient#execute()」メソッドの戻り値をあたかも通信が失敗したように、もしくは成功したように振る舞うことで、実際にはWebサーバとまったく通信することなく製品コード(HttpClientを使うメソッドのコード)をテストできます。

Android Mockで動的にモックを生成

 モックオブジェクトについては、連載第2回「Androidでビジネスロジックのテストを自動化するには」で「android.test.mock.MockContext」のサブクラスを定義し特定メソッドオーバーライドする方法を紹介しています。

 しかし、テストケースごとに振る舞いの違うサブクラスを定義するのは大変なので、多くの言語向けにモッククラスを動的に生成するフレームワークが開発されています。そのうち、Java向けの「EasyMock」(2.4)をAndroid(DalvikVM)上で実行できるようにラップしたものがAndroid Mockです。

コラム その他のAndroid用モックフレームワーク

Javaだけでも、EasyMockのほか、「PowerMock」「JMockit」「Mockito」などが存在しますが、そのままAndroid(Dalvik仮想マシン)上では使えません。

ただ、2012年2月時点でMockitoにはAndroidをサポートするパッチが作られているため、近い将来、正式に使えるようになるかもしれません。Mockitoについては、以下の記事を参照してください。


 まずはコード例をご覧ください。HttpClientでWebサーバからフィードを取得するメソッド(リスト1)に対し、HttpClient#execute()が例外をスローするケースのテストコード(リスト2)です。

public class FxRateLoader extends AsyncTaskLoader<FxRate> {
 
    /** HttpClientインスタンス。モックによるテストを容易にするためインスタンスフィールドに持つ. */
    HttpClient mHttpclient;
 
    /**
     *  ECBに接続し、フィードを取得する. 
     */
    InputStream requestFeed() throws InvalidHttpStatusCodeException, ClientProtocolException,
            IOException {
        HttpGet get = new HttpGet(FEED_URL);
        HttpResponse response = this.mHttpclient.execute(get);
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == HttpStatus.SC_OK) {
            return response.getEntity().getContent();
        }
        throw new InvalidHttpStatusCodeException(statusCode);
    }
}
リスト1 テスト対象のコード

 これは、Webサーバからステータスコード200(OK)が返ったときにはレスポンス本文のInputStreamを返し、それ以外のコードではInvalidHttpStatusCodeExceptionを返すメソッドです。

    /**
     * 通信切断状態ではUnknownHostExceptionがスローされること.
     */
    @UsesMocks(HttpClient.class)
    public void testRequestFeed_notConnected() throws Exception {
        // 通信できない状態の例外インスタンス
        Exception expected = new UnknownHostException();
        // 通信できない状態の例外をスローするHttpClientモック
        HttpClient mockHttpClient = AndroidMock.createMock(HttpClient.class);
        AndroidMock.expect(mockHttpClient.execute((HttpUriRequest)AndroidMock.notNull()))
                .andThrow(expected);	// *1
        AndroidMock.replay(mockHttpClient);	// *2
        // テスト対象のインスタンスフィールドにあるHttpClientをモックに差し替える
        mFxRateLoader.mHttpclient = mockHttpClient;	// *3
        // テスト実行
        try {
            mFxRateLoader.requestFeed();
            fail("UnknownHostExceptionがthrowされていない");
        } catch (UnknownHostException e) {
            // 例外がスローされるのが正しい振る舞い
        }
        // モックの検証(メソッドの戻り値で判断できているので冗長な確認)
        AndroidMock.verify(mockHttpClient);
    }
リスト2 通信できなかった場合の異常系テストケース

 「@UsesMocks」というアノテーションとAndroidMockクラスが特徴的です。これによってHttpClientのモックオブジェクトを動的に生成できます。

 *1の行では、モックのHttpClientに対し、「execute()」メソッドは引数の内容を問わず(notNull)例外expectedをスローすることを定義しています。

 *2の行では、AndroidMock#replay()によって定義したモックオブジェクトの振る舞いが有効になります。

 *3の行では、テスト対象のインスタンスが保持するHttpClient(mHttpclient)をモックオブジェクトに差し替えます。

 これを実行すると、モックオブジェクトの振る舞いによって「FxRateLoader#requestFeed()」内部から「UnknownHostException」がスローされます。このテストは正しくパスしたといえるでしょう。

 「AndroidMock.expect()」にはさまざまな書式があり、引数を厳密に判定したり、複雑な振る舞いを持たせることも可能です。詳細はAndroid MockプロジェクトWikiにある「WritingTestsUsingAndroidMock」をご覧ください。

       1|2|3 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。