86
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android #2Advent Calendar 2018

Day 12

2018年までのAndroidテスト総まとめ - 今年の変更と来年の対策

Last updated at Posted at 2018-12-12

Androidが10年の節目の年ですが
去年からKotlin対応, Jetpack対応, AndroidX対応と怒涛の更新が続いています。

11月7-8日に開催されたAndroid Dev Summit '18の動画を見て、
Androidテストは来年やばいことになりそうだ!(←今年もやばかった (←今までも..))
と思ったので過去-現在-未来を一気に見れるようにまとめました。
2018年12月版です。

2018年までのAndroidテストまとめ

0.png 画像: Android Dev Summit '18スライドより

Androidテスト周辺は毎年いろいろなものが出たりなくなったり、正直分かりづらいです。

公式の「Fundamentals of Testing」やGoogle I/Oの以下動画に
しっかりまとまっているので、まだ見ていない方を見ることを強くおすすめします。

Fundamentals of Testing
https://developer.android.com/training/testing/fundamentals

YouTube: TDD on Android with the Android Testing Support Library (Google I/O '17)
https://youtu.be/pK7W5npkhho

YouTube: Frictionless Android testing: write once, run everywhere (Google I/O '18) 0:00 〜 19:00
https://youtu.be/wYMIadv9iF8

今知りたい方は以下をどうぞ!

UnitTest (on JVM)

Androidプロジェクトの test フォルダ内にあたります。
JVM上で実行するので高速ですがAndroidライブラリは使用できません。

JUnit4

Javaで開発された、プログラムにおいてUnitTestを行うフレームワークです。
AndroidStudioにはGUIのテストランナーがビルドインされています。

Androidライブラリに依存しないクラスのUnitTestに使用できます。

@RunWith(JUnit4.class) //省略可能
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
:

Mockito

Javaのモックフレームワークです。
Android自体ではサポートしていないものの、
サンプルコードやドキュメントなどでは使用が推奨されています。

指定した値を返すモッククラスを作成することにより、他クラスに依存しないUnitTestにします。

private const val FAKE_STRING = "HELLO_WORLD"

@RunWith(MockitoJUnitRunner::class)
class UnitTestSample {

    @Mock
    private lateinit var mockContext: Context

    @Test
    fun readStringFromContext_LocalizedString() {
        `when`(mockContext.getString(R.string.hello_word))
                .thenReturn(FAKE_STRING)
        val myObjectUnderTest = ClassUnderTest(mockContext)
        val result: String = myObjectUnderTest.getHelloWorldString()
        assertEquals(result, FAKE_STRING)
    }
}

Robolectrics

RobolectricsはUnitTestのためにJVM上でAndroidをSimulateする仕組みです。
下記問題を解決します。

  • JVM上でAndroidライブラリをすべてMockitoでモック化しようとすると大変
  • Android Testは実際にデバイスを立ち上げて行うため低速

後述に超進化したRobolectrics 4.0 (10/25リリース)の説明があるので詳細はそちらで。
下記サンプルは参考程度に。

@RunWith(RobolectricTestRunner::class)
class RobolectricTest {
  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    val activity = Robolectric.setupActivity(NoteListActivity::class.java)
    ShadowView.clickOn(activity.findViewById(R.id.title));
    assertThat(ShadowApplication.getInstance().peekNextStartedActivity().action)
            .isEqualTo("android.intent.action.EDIT")
  }
}

Android Test (on Device/Emulator)

1.png 画像: Google I/O '17スライドより

Instrumentation testと同義です。
Androidプロジェクトの androidTest フォルダ内にあたります。
テスト対象のapk本体とテストコードapkをデバイスに転送してテストします。
(このためテスト実行は低速となります)
"Android Testing Support Library"とは本項の機能を指します。

AndroidJUnitRunner

2.png 画像: Google I/O '17スライドより

AndroidJUnitRunnerクラスは、後述のEspresso/UI Automatorの実行を含むJUnitテストランナーです。
クラスの前に @RunWith(AndroidJUnit4.class)を付けて使用します。
AndroidJUnitRunnerを実行すると以下を行います。

  1. テスト対象のapkとテストapkをAndroidデバイスにロード
  2. テストを実行
  3. テスト結果をレポート

Android Test Orchestrator

Android Testを最適に実行するツールです。
Android Studio3.0+とFirebase Test Labにはプリインストール済み。
AndroidJUnitRunner 1.0+で使用可。以下の機能を提供します。

  • Instrumentationの独自の呼び出し内で各アプリケーションのテストを実行できます。
  • clearPackageData の設定で各テスト後にデバイスのCPUとメモリから状態削除可。
  • クラッシュの隔離。 1つのテストがクラッシュしても他のテストは引き続き実行されます。

上記の通り、最近追加された機能ですがAndroidテストでは必須といえます。
Android Studio 3.0+では以下のようにgradleに追記して有効にします。
コマンドラインの設定で有効にすることも可能です。

build.gradle
android {
  defaultConfig {
   ...
   testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

   // The following argument makes the Android Test Orchestrator run its
   // "pm clear" command after each test invocation. This command ensures
   // that the app's state is completely cleared between tests.
   testInstrumentationRunnerArguments clearPackageData: 'true'
 }

  testOptions {
    execution 'ANDROIDX_TEST_ORCHESTRATOR'
  }
}

dependencies {
  androidTestImplementation 'androidx.test:runner:1.1.0'
  androidTestUtil 'androidx.test:orchestrator:1.1.0'
}

Espresso

アプリ内で完結するUIテストを行うためのフレームワークです。

最も基本的なテスト方法は以下手順で行います。

  1. ActivityTestRule にてActivityを指定
  2. onView(matcher)でView指定
  3. perform(viewAction)でクリックやテキスト入力などのアクション設定
  4. check(viewAssertion)で状態や値を確認

ActivityTestRule はかつて ActivityInstrumentationTestCase2 だったものです。2.1で廃止。

また、IntentやLifecycleなど新機能を含めたサンプルは こちら にあります

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {
  @get:Rule val rule = ActivityTestRule(NoteListActivity::class.java)

  @Test
  fun changeText_sameActivity() {
    // Type text and then press the button.
    onView(withId(R.id.editTextUserInput).perform(typeText(stringToBetyped), closeSoftKeyboard())
    onView(withId(R.id.changeTextBt)).perform(click())

    // Check that the text was changed.
    onView(withId(R.id.textToBeChanged)).check(matches(withText(stringToBetyped)))
  }

  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    onView(withId(R.id.button)).perform(click())
    intended(hasAction(equalTo("android.intent.action.EDIT")))
  }
}

UI Automator

アプリ外のUIテストを行うためのフレームワークです。

テスト対象のアプリ外で発生する、デバイス操作(回転やホームボタン)や他アプリでの操作
を含むテストを行うことができます。


@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {
    private UiDevice mDevice;

    @Before
    public void startMainActivityFromHomeScreen() {
        // Initialize UiDevice instance
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        // Start from the home screen
        mDevice.pressHome();

        // Wait for launcher
        final String launcherPackage = mDevice.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 5000);
:

2018年までのAndroidテスト一覧

ざっくりこれまで(かつ現役の)フレームワーク・ツールを一覧にしました。
単語と概要が頭の中で結びついていただけたら本望です。

名称 概要
JUnit4 Javaのテストフレームワーク(Assertion, Runner)
Mockito Javaのモック作成
Robolectrics JVM上でAndroidをSimulate
AndroidJUnitRunner Androidテスト実行
Android Test Orchestrator Androidテスト実行の最適化ツール
Espresso アプリ内のUIテスト
UI Automator アプリ外のUIテスト

2018年の変更 - AndroidX Testing Library

2018年11月、AndroidDevSummitにて「Testing Rebooted (with AndroidX Test)」という発表がありました。

Testing Rebooted (with AndroidX Test) (Android Dev Summit '18)
https://youtu.be/4m2yYSTdvIg

AndroidX Testing Libraryが1.0になり、
Core(Application, Activity, Fragment)のテスト方法や、
新たにサポートされるライブラリ「Truth」「Robolectrics 4.0」について紹介されました。

セットアップ方法は以下資料になります。
androidxパッケージとなりました。

参考) Set up project for AndroidX Test
https://developer.android.com/training/testing/set-up-project

app.gradle
dependencies {
  // Core library
  androidTestImplementation 'androidx.test:core:1.0.0'

  // AndroidJUnitRunner and JUnit Rules
  androidTestImplementation 'androidx.test:runner:1.1.0'
  androidTestImplementation 'androidx.test:rules:1.1.0'

  // Assertions
  androidTestImplementation 'androidx.test.ext:junit:1.0.0'
  androidTestImplementation 'androidx.test.ext:truth:1.0.0'
  androidTestImplementation 'com.google.truth:truth:0.42'

  // Espresso dependencies
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
  androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0'

  // The following Espresso dependency can be either "implementation"
  // or "androidTestImplementation", depending on whether you want the
  // dependency to appear on your APK's compile classpath or the test APK
  // classpath.
  androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0'
}

「Truth」「Robolectrics 4.0」の概要は以下となります。

Truth

google発、比較文を書きやすくするassertionライブラリです。

素のassertEqualsと比較してアサートメッセージが追加できたり、
どれが実行結果でどれが期待値か、可読性が上るメリットがあります。
IDEの予測変換で比較文がでるのもありがたいです。

val string = "awesome";
assertThat(string).startsWith("awe");
assertWithMessage("Without me, it's just aweso").that(string).contains("me");

また、Truthでは各Objectの比較が容易になっており、
例えばStringではisEmpty()startsWith(string)が使用できます。
これは型別にSubjectという比較方法の提供で実現しており、
例えばStringでは以下StringSubjectがあります。
http://google.github.io/truth/api/latest/com/google/common/truth/StringSubject.html

今回、AndroidXではTruthのExtensionにて、IntentやMotionEventのSubjectをサポートしています。
使用できるSubjectについては以下に掲載されています。
https://developer.android.com/training/testing/fundamentals#assertions

assertThat(object).hasFlags(FLAGS)
assertThat(object).doesNotHaveFlags(FLAGS)
assertThat(intent).hasData(URI)
assertThat(extras).string(string_key).equals(EXPECTED)

Robolectrics 4.0

Robolectrics 4.0がandroidx.test1.0.0でのサポート対象となりました。
Robolectricsは公式サイトにリリースノートや機能のドキュメントがあります。
(= Androidドキュメント内にはありません)

4.0の特に大きな機能はAndroidテストのAndroidJUnitRunner, Espressoに対応したことです。

With today’s release of Robolectric 4.0 and androidx.test 1.0.0, both testing environments are converging on a set of common test APIs. Robolectric now supports the AndroidJUnit4 test runner, ActivityTestRule, and Espresso for interacting with UI components.

これによって、app.gradle
androidTestImplementationtestImplementation に切り替えるだけで
同じソースコードでテストできるようになりました。

app.gradle
dependencies {
  // Core library
  testImplementation 'androidx.test:core:1.0.0'

  // AndroidJUnitRunner and JUnit Rules
  testImplementation 'androidx.test:runner:1.1.0'
  testImplementation 'androidx.test:rules:1.1.0'

  // Assertions
  testImplementation 'androidx.test.ext:junit:1.0.0'
  testImplementation 'androidx.test.ext:truth:1.0.0'
  testImplementation 'com.google.truth:truth:0.42'

  // Robolectric
  testImplementation 'org.robolectric:robolectric:4.0.2'

  // Espresso dependencies
  testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
:

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {
  @get:Rule val rule = ActivityTestRule(NoteListActivity::class.java)

  @Test
  fun changeText_sameActivity() {
    // Type text and then press the button.
    onView(withId(R.id.editTextUserInput).perform(typeText(stringToBetyped), closeSoftKeyboard())
    onView(withId(R.id.changeTextBt)).perform(click())

    // Check that the text was changed.
    onView(withId(R.id.textToBeChanged)).check(matches(withText(stringToBetyped)))
  }

  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    onView(withId(R.id.button)).perform(click())
    intended(hasAction(equalTo("android.intent.action.EDIT")))
  }
}

「同じソースコードでテストできる」は重要なキーワードです!
ぜひ、このまま次の「2019年に起こること - Nitrogen Project」まで読み進めてください。

2019年に起こること - Nitrogen Project

以下動画でNitrogen Projectについて説明されています。

Frictionless Android testing: write once, run everywhere (Google I/O '18) 19:01 〜
https://youtu.be/wYMIadv9iF8?t=1141

Testing Android Apps at Scale with Nitrogen (Android Dev Summit '18)
https://youtu.be/-_kZC29sWAo

Androidテストは現状、実行環境(JVM, 実機, Firebaseなどのクラウド端末)を意識してコードを区別する必要がありました。
上記のEspressoとRobolectrics 4.0のサンプルコードが同じになったように、
「Nitrogen Project」にて今後は同じテストコードを複数環境で実行できるよう取り組んでいるそうです。

スクリーンショット 2018-12-11 5.10.12.png 画像: Google I/O '18スライドより

ロードマップも公開されており
Unified Testing Platformの公開予定は2019年となっているとのこと。

スクリーンショット 2018-12-12 7.25.59.png 画像: Android Dev Summit '18スライドより

ビルドシステムにGradle, Bazelを使うとのことなので覚えておいたほうが良さげです。
また、Firebase Test LabもNitrogen Projectに統合予定とのこと。

Bazel

https://docs.bazel.build/versions/master/bazel-overview.html
https://bazel.build/roadmaps/android.html

Firebase Test Lab

個人的に思うこと

来年のAndroidテスト関連、個人的にこんなことを思いました。

  • test/androidTestフォルダがどっちでもよくなりつつある
    • 今後はどちらか消えるのでは?
    • Robolectricsをエンジニアが意識することはなくなりそう?
  • MockitoのKotlin版サポートとかしないかな
  • Spek公式サポートまだかな
  • Robolectricsのアイコン、もこもこしてるとけど電気羊の夢..?

参考にしました

mixi developers - Android Testは"Write once, run everywhere."の夢を見るか
https://medium.com/mixi-developers/android-test-%E3%81%AF-write-once-run-everywhere-%E3%81%AE%E5%A4%A2%E3%82%92%E8%A6%8B%E3%82%8B%E3%81%8B-4b6e179928f6

AndroidX Test / yanzm
https://speakerdeck.com/yanzm/androidx-test

86
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
86
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?