Androidからサーバアプリにアクセスするようなアプリを開発する場合、テスト用のサーバと本番用のサーバの設定を変更したいことがよくあります。AndroidTestCaseとモックオブジェクトを利用すると、テスト実行時に利用するリソースファイルを切り替えることも可能です。今回は、サーバへの接続情報を取得クラスに対するテストを記述していきます。
ここでは、テスト対象プロジェクトとして「BusinessLogicSample」を用意しました。このプロジェクトを利用してテストを書いていくことにしましょう。
package com.example.atec.business;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
public class SecretResource {
private static final String TAG = "ATEC";
private String twitterConsumer;
private String twitterSecret;
public String getTwitterConsumer() {
return twitterConsumer;
}
public String getTwitterSecret() {
return twitterSecret;
}
public boolean isAvailable() {
return !TextUtils.isEmpty(twitterConsumer) && !TextUtils.isEmpty(twitterSecret);
}
public static SecretResource load(Context context, int resourceId) {
SecretResource secretResource = new SecretResource();
InputStream in = context.getResources().openRawResource(resourceId);
try {
Properties prop = new Properties();
prop.load(in);
secretResource.twitterConsumer = prop.getProperty("twitter.consumer");
secretResource.twitterSecret = prop.getProperty("twitter.secret");
} catch (IOException e) {
Log.w(TAG, "properties load error", e);
} finally {
try {
in.close();
} catch (IOException e) {
}
}
return secretResource;
}
}
SecretResources.javaは「config.properties」からサーバへの接続情報を読み込んで保持するクラスです。
twitter.consumer=consumer_key
twitter.secret=secret_key
config.propertiesにはサーバへの接続情報が設定されています。
テストプロジェクトに以下のファイルを作成します。
まず、ServerConfigTestクラスを作成します。このクラスはAndroidTestCaseを拡張させます。
public class SecretResourceTest extends AndroidTestCase {
}
config.propertiesファイルを読み込む代わりに、テスト中で定義した文字列をInputStreamとして返すよう実装します。「MockResources」の拡張クラスTestResourcesは、例外を投げるだけなので、ここで必要なopenRawResources()だけをオーバーライドして使用します。
public class SecretResourceTest extends AndroidTestCase {
class TestResources extends MockResources {
@Override
public InputStream openRawResource(int id)
throws NotFoundException {
byte[] buf = "twitter.consumer=11111\ntwitter.secret=22222"
.getBytes();
InputStream in = new ByteArrayInputStream(buf);
return in;
}
}
}
先ほど定義したモックのリソースクラスを返すコンテキストを実装します。「MockContext」クラスの拡張クラスも例外を投げるだけとなっています。テストで必要なgetResources()のみオーバーライドします。
class TestContext extends MockContext {
@Override
public Resources getResources() {
return new TestResources();
}
}
さらに、setContext()を利用してモックのコンテキストを設定します。ここではsetUp()メソッド内でモックコンテキストを設定し、テストメソッドでgetContext()を呼び出すことでモックのコンテキストを取得しています。
そして、テストメソッドを記述し、TestResourcesで定義した値が取得できているかテストします。 「twitter.consumer=11111」「twitter.secret=22222」が期待値となりますので、テスト結果が同様の値となればテストは成功です。
@Override
protected void setUp() throws Exception {
super.setUp();
setContext(new TestContext());
}
public void testLoad() throws Exception {
SecretResource resource = SecretResource.load(getContext(), R.raw.config);
assertNotNull(resource);
assertEquals("consumer key", "11111", resource.getTwitterConsumer());
assertEquals("secret key", "22222", resource.getTwitterSecret());
}
package com.example.atec.business;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import android.content.res.Resources;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;
import android.test.mock.MockResources;
import com.example.atec.business.test.R;
public class SecretResourceTest extends AndroidTestCase {
/*
* (non-Javadoc)
*
* @see android.test.AndroidTestCase#setUp()
*/
@Override
protected void setUp() throws Exception {
super.setUp();
// モックのコンテキストを設定
setContext(new TestContext());
}
/**
* config.properties読み込みのテスト
*
* @throws Exception
*/
public void testLoad() throws Exception {
// config.properties読み込み
SecretResource resource = SecretResource.load(getContext(),
R.raw.config);
assertNotNull(resource);
assertEquals("consumer key", "11111", resource.getTwitterConsumer());
assertEquals("secret key", "22222", resource.getTwitterSecret());
}
/**
* MockResourcesを返すContext
*/
class TestContext extends MockContext {
/*
* (non-Javadoc)
*
* @see android.test.mock.MockContext#getResources()
*/
@Override
public Resources getResources() {
return new TestResources();
}
}
/**
* 文字列からリソースを返すMockResources
*/
class TestResources extends MockResources {
/*
* (non-Javadoc)
* @see android.test.mock.MockResources#openRawResource(int)
*/
@Override
public InputStream openRawResource(int id)
throws NotFoundException {
byte[] buf = "twitter.consumer=11111\ntwitter.secret=22222"
.getBytes();
InputStream in = new ByteArrayInputStream(buf);
return in;
}
}
}
テストを実行し、グリーンーバーが表示されるかを確認します。
このようにモックオブジェクトを利用すると、実際にアプリを動かす場合とテストを実行する場合で環境を切り替えたい場合に、テスト対象プロジェクトのソースコードなどを変えることなくテスト用の環境に切り替えることができます。
このサンプルは、テスト部で作成している「testter」というTwitterクライアントに対するテストから抜粋したものです。
testterはテスト部でテストを書く対象のために作成したAndroidアプリです。ソースコードも公開されていますのでぜひ1度ご覧ください。
さて、今回のビジネスロジックのテストはいかがだったでしょうか。ビジネスロジックのテストケースを記述することで、ビジネスロジックがAndroid固有のコンポーネントから自然と分離されるようになります。そうすると、記述が面倒なAndroid固有のコンポーネントにかかわる処理のテストケースが自然と減ります。ぜひAndroidアプリのビジネスロジックテストを自動化してみてください。
また、本稿で掲載したソースコードは以下のリポジトリで公開されています。
次回は、Androidテスト部で議論にのぼることが多いUIのテストケースの書き方について解説します。ご期待ください。
宮田友美
株式会社オープンストリームにおいて、アーキテクトとしてAndroidの調査・研究および案件支援に従事。趣味はAndroid系端末をはじめとしたガジェット収集。しかし最近は、心踊るガジェットが最近あまり発売されないのが悩みの種
Copyright © ITmedia, Inc. All Rights Reserved.