Androidで良い感じにテストするために、たくさんあるテスティングフレームワークを試して選定してみる。
環境
OS X
Android Studio 1.0.2
テスト用サンプルアプリ
入力された値を足して表示するだけのサンプルアプリ(アクティビティを跨いだテストもしたいので2画面構成)。
リポジトリ
https://github.com/shikato/AndroidTestSample
今回はこのアプリに対してテストする。
ロジックのテスト
Android Testing Framework
標準でJUnitベースのAndroid Testing Frameworkが使える。
これまではJUnit3ベースだったけど、最近JUnit4がAndroid support libraryに含まれるようになり、JUnit4な記述でも簡単に書けるようになった。
JUnit4の導入
- Android Support Repositoryのバージョンを上げる(Rev11以上)。
- build.gradleに追記
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
packagingOptions {
exclude 'LICENSE.txt'
}
dependencies {
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
サンプルテストコード
@RunWith(AndroidJUnit4.class)
public class JUnit4Test {
private Context mContext;
@Before
public void setUp() throws Exception {
// Contextを取得
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
}
@Test
public void add() {
assertThat(Calc.add(3, 5), is(8));
}
@Test
public void context() {
// Contextを使ったテスト
assertThat(mContext.getString(R.string.app_name), is("AndroidTestSample"));
}
}
テストの実行
※実機を接続、もしくはエミュレータを起動しておく
./gradlew connectedAndroidTest
テスト結果レポート
app/build/outputs/reports/androidTests/connected/index.html
感想
ロジックのテストするだけなら、これで十分な気がする。
参考
http://vividcode.hatenablog.com/entry/android-app/library/espresso-2.0
※ 2015-07-30追記
この記事の内容は少し古いです。以下記事で情報をupdateしています。
2015年7月時点でのJUnit4やEspressoを使ったAndroidアプリのテストについて
Robolectric
JVM上で動作するテスティングフレームワーク
http://robolectric.org/
メリット
JVM上で動作し、テスト実行時にエミュレータや実機にアプリをインストールするフローが入らないため速い。
JUnit4が使える(Android Testing Frameworkでもサポートライブラリから導入できるようになったので、今はありがたみは少ない)
デメリット
あくまでもJVM上でのモック(Shadow Objects)を使ったテストなので信憑性は低くなる。
導入
テンプレートプロジェクトがあるので、それを見れば導入しやすい。
https://github.com/robolectric/deckard-gradle
- Projectのbuild.gradleに以下を追記
classpath 'org.robolectric:robolectric-gradle-plugin:0.14.+'
- Appのbuild.gradleに以下を追記
apply plugin: 'robolectric'
testCompile('junit:junit:4.12') {
exclude module: "hamcrest-core"
}
testCompile('org.robolectric:robolectric:2.4') {
exclude module: "classworlds"
exclude module: "maven-artifact"
exclude module: "maven-artifact-manager"
exclude module: "maven-error-diagnostics"
exclude module: "maven-model"
exclude module: "maven-plugin-registry"
exclude module: "maven-profile"
exclude module: "maven-project"
exclude module: "maven-settings"
exclude module: "nekohtml"
exclude module: "plexus-container-default"
exclude module: "plexus-interpolation"
exclude module: "plexus-utils"
exclude module: "wagon-file"
exclude module: "wagon-http-lightweight"
exclude module: "wagon-http-shared"
exclude module: "wagon-provider-api"
}
/* 以下は各々の環境に合わせる */
robolectric {
include '**/java/**/*.class'
exclude '**/android/**/*.class'
}
サンプルコード
@Config(emulateSdk = 18)
@RunWith(RobolectricTestRunner.class)
public class RobolectricTest {
@Before
public void setup() {
}
@After
public void teardown() {
}
@Test
public void sample() {
assertThat(
Robolectric.application.getApplicationContext().getString(R.string.app_name), is("AndroidTestSample"));
}
}
テストの実行
※実機を接続したりエミュレータを起動する必要はない
./gradlew clean test
テスト結果レポート
app/build/test-report/index.html
感想
テストが早く終わるのは良いけど、信憑性が気になる。
UIのテスト
UIテスティングフレームワークの種類としては主に以下の2タイプがある。
テスト対象アプリの署名がテストコードと合致する必要があるもの
自身がビルドできるアプリしかテストできない。
- Android Instrumentation
- Espresso
- Robotium
- etc..
署名に関係なくテストできるもの
他者のアプリでもテストできる。 ただ、できることは署名合致タイプと比較すると多分少ない。
- UIAutomator
- Appium
- etc..
ActivityInstrumentationTestCase2(Android Instrumentation)
最初から使える、アクティビティをテストするためのクラス。
導入
最初から使える
テストコード
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ActivityInstrumentationTestCaseTest extends ActivityInstrumentationTestCase2<TopActivity> {
private Activity mActivity;
public ActivityInstrumentationTestCaseTest() {
super(TopActivity.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
}
@Test
public void testAddition() {
// 数字入力(num1)
EditText num1 = (EditText)mActivity.findViewById(R.id.num1);
TouchUtils.clickView(this, num1);
sendKeys(KeyEvent.KEYCODE_1);
// 数字入力(num2)
EditText num2 = (EditText)mActivity.findViewById(R.id.num2);
TouchUtils.clickView(this, num2);
sendKeys(KeyEvent.KEYCODE_1);
sendKeys(KeyEvent.KEYCODE_0);
// ResultActivityの起動を監視
Instrumentation.ActivityMonitor monitor =
new Instrumentation.ActivityMonitor(ResultActivity.class.getCanonicalName(), null, false);
getInstrumentation().addMonitor(monitor);
// =ボタンクリック
Button addBtn = (Button)mActivity.findViewById(R.id.equal_button);
TouchUtils.clickView(this, addBtn);
// 起動待ち
Activity resultActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 3000L);
// ResultActivityが起動したか確認
assertThat(monitor.getHits(), is(1));
assertThat(resultActivity, notNullValue());
// 計算結果確認
TextView result = (TextView)resultActivity.findViewById(R.id.result);
assertThat(result.getText().toString(), is("11"));
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
テストの実行
※実機を接続、もしくはエミュレータを起動しておく
./gradlew connectedAndroidTest
テスト結果レポート
app/build/outputs/reports/androidTests/connected/index.html
感想
導入は簡単だけど、記述が冗長な気がする。
Espresso
Googleが開発している「スクリーンの中の対象UIをみつけ」「何かアクションをし」「その結果を確認する」という、ユーザのアクションに沿ったUIテスティングフレームワーク。
Android Instrumentationベース。
https://code.google.com/p/android-test-kit/wiki/Espresso
http://wazanova.jp/post/62856507585/espresso-android-ui-gtac-2013
最近ver2.0がリリースされAndroid support libraryに含まれるようになり、導入が楽になった(JUnit4もこれの恩恵)。
https://code.google.com/p/android-test-kit/wiki/ReleaseNotes
導入
- Android Support Repositoryのバージョンを上げる(Rev11以上)。
- build.gradleに追記
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
packagingOptions {
exclude 'LICENSE.txt'
}
dependencies {
compile 'com.android.support:support-annotations:21.0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
テストコード
@RunWith(AndroidJUnit4.class)
public class EspressoTest extends ActivityInstrumentationTestCase2<TopActivity> {
private Activity mActivity;
public EspressoTest() {
super(TopActivity.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
}
@Test
public void testAddition() {
// 数字入力
onView(ViewMatchers.withId(R.id.num1)).perform(typeText("2"));
onView(withId(R.id.num2)).perform(typeText("20"));
// 足すボタンクリック
onView(withId(R.id.equal_button)).perform(click());
// 計算結果確認
onView(withId(R.id.result)).check(matches(withText("22")));
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
}
テストの実行
※実機を接続、もしくはエミュレータを起動しておく
./gradlew connectedAndroidTest
テスト結果レポート
app/build/outputs/reports/androidTests/connected/index.html
感想
ActivityInstrumentationTestCase2を使用した時よりもかなり簡潔に書けた。
support libraryにも含まれたし、今はEspressoの流れ?
参考
http://vividcode.hatenablog.com/entry/android-app/library/espresso-2.0
※ 2015-07-30追記
この記事の内容は少し古いです。以下記事で情報をupdateしています。
2015年7月時点でのJUnit4やEspressoを使ったAndroidアプリのテストについて
Robotium
レイアウトの構造を知らなくてもUIを操作できるUIテスティングフレームワーク。
Android Instrumentationベース。
https://code.google.com/p/robotium/
導入
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
dependencies {
androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.2.1'
}
テストコード
@RunWith(AndroidJUnit4.class)
@LargeTest
public class RobotiumTest extends ActivityInstrumentationTestCase2<TopActivity> {
private Activity mActivity;
public RobotiumTest() {
super(TopActivity.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
}
@Test
public void testAddition() {
Solo solo = new Solo(getInstrumentation(), mActivity);
// 1つめのEditTextに1と入力
solo.enterText(0, "3");
// ID指定も可能
// solo.enterText((EditText)mActivity.findViewById(R.id.num1), "3");
// 2つめのEditTextに30と入力
solo.enterText(1, "30");
// 足すと書かれたボタンを押す
solo.clickOnButton("=");
// ResultActivity の起動を確認
solo.assertCurrentActivity("ResultActivity now.", ResultActivity.class);
// 計算結果である33と書かれたテキストを確認
assertThat(solo.searchText("33"), is(true));
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
}
テストの実行
※実機を接続、もしくはエミュレータを起動しておく
./gradlew connectedAndroidTest
テスト結果レポート
app/build/outputs/reports/androidTests/connected/index.html
感想
以下記事では、コアの開発が止まっているなど、あまり良いことが書かれていないのが気になる。
http://wazanova.jp/items/1623
UIAutomator
Android SDK付属の、Android 4.1 以降で動作するUIテスティングフレームワーク。
http://developer.android.com/tools/help/uiautomator/index.html
他者が署名したアプリでもテストできるので、広く配布しているライブラリのテストとかに良さそう。
試そうかと思ったけど、Appiumのが面白そうなので、そっちを試す。
導入するならmixiのAndroidTrainingが参考になりそう(Eclipseだけど)
http://mixi-inc.github.io/AndroidTraining/fundamentals/2.11.testing.html#UIAutomator
Appium
Android、iOS、FireFoxOSに対応しているUIテスティングフレームワーク。
テストコードはJavaだけでなく、Node.jsやRubyなどいろんな言語で書ける。
http://appium.io/
使える言語
- Node.js
- Ruby
- Python
- Java
- JavaWcript
- PHP
- C#
AppiumはAndroidに関してはUIAutomatorを内部で使用している。
http://www.publickey1.jp/blog/14/javascriptselenium1_selenium_1.html
導入
以下を参考に、もろもろ構築しました。
http://qiita.com/kzm7/items/4386f37434c39b6b8095
http://qiita.com/kzm7/items/d3bdebd3930860d0b473
http://appium.io/slate/en/master/?ruby#running-appium-on-windows
はまったとこ
Javaの文字コードをセットするため以下を.zshrcとかに記述していると、
export _JAVA_OPTIONS="-Dfile.encoding=UTF-8"
Javaコマンドを実行する際、一行目に毎度
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
という出力がされるけど、これのせいではまった。
Appium.appの虫眼鏡ボタンをクリックすると、Inspectorが起動するはずなんだけど、そこで以下の様なエラーがでて起動できなかった。
Error: Could not get the Java version. Is Java installed?
appium-doctorツールでは「All Checks were successful」と表示されているのに何故だろうとソースコードを確認してみると、
androidCommon.getJavaVersion = function (cb) {
exec("java -version", function (err, stdout, stderr) {
var javaVersion = null;
if (err) {
return cb(new Error("'java -version' failed. " + err));
} else if (stderr) {
var firstLine = stderr.split("\n")[0];
if (new RegExp("java version").test(firstLine)) {
javaVersion = firstLine.split(" ")[2].replace(/"/g, '');
}
}
if (javaVersion === null) {
return cb(new Error("Could not get the Java version. Is Java installed?"));
}
return cb(null, javaVersion);
});
};
となっていて、java -versionコマンド実行結果の1行目でチェックしており、自分の環境だと上述したように一行目には
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
が出力されていたため、チェックに失敗していたことがわかった。
なので、以下をコメントアウトしたらInspectorが起動できた。
if (javaVersion === null) {
return cb(new Error("Could not get the Java version. Is Java installed?"));
}
本来であれば
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
が表示されないようにしたいところだけど、文字コードを設定したまま消す、というのは面倒そうな感じなのでやめました。。。
http://stackoverflow.com/questions/11683715/suppressing-the-picked-up-java-options-message
Appium.app(GUI)の実行
以下が参考になります。
http://qiita.com/kzm7/items/d3bdebd3930860d0b473
Appium.appのInspectorでテスト対象アプリを操作すると以下のようなコードが出力される(言語はNode.jsを指定しました)。
"use strict";
var wd = require("wd");
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
var desired = {
"appium-version": "1.0",
platformName: "Android",
platformVersion: "4.4",
deviceName: "mydevice",
app: "{{apkのpath}}",
"app-package": "org.shikato.androidtestsample",
"app-activity": ".TopActivity"
};
var browser = wd.promiseChainRemote("0.0.0.0", 4723);
browser.init(desired).then(function() {
return browser
.elementByXPath("//android.view.View[1]/android.widget.FrameLayout[2]/android.widget.LinearLayout[1]/android.widget.EditText[1]").sendKeys("12")
.elementByXPath("//android.view.View[1]/android.widget.FrameLayout[2]/android.widget.LinearLayout[1]/android.widget.EditText[2]").sendKeys("5")
.elementByXPath("//android.view.View[1]/android.widget.FrameLayout[2]/android.widget.LinearLayout[1]/android.widget.Button[1]").click()
.fin(function() {
return browser.quit();
});
}).done();
これを実行してやると、Inspectorで操作した通りにアプリが自動的に動く。
node appium_sample.js
ただ、これだけではテストにならないので、Node.jsならNode.jsのテスティングフレームワークを使って、テストできる形式に修正していく。
長くなってきたので、Appiumに関しては続きを別の記事にでも書きます。。。
感想
Node.jsとかでテストを書けたり、他者のアプリでもテストできるのが良い。
結局どれを使うのか
とりあえず、以下を使ってみる予定です。
ロジックのテスト
Android Testing Framework (JUnit4)
JUnit4が簡単に使えるようになったし、Robolectricだと信憑性の問題から結局、他でもテストしないといけなそうなので。
UIのテスト
Espresso (自分でビルドできるアプリをテストする場合)
簡潔に記述できるし、Android support libraryにも含まれるようになり、今後もある程度安心して使えそうなので。
Appium(自分でビルドできないアプリをテストする場合)
最近ライブラリを開発する機会があり、そのライブラリを導入しているアプリのテストをするのが楽になりそうなので。