Espresso-testing-quick-guide

提供:Dev Guides
移動先:案内検索

エスプレッソテストフレームワーク-はじめに

一般に、モバイル自動化テストは困難で困難な作業です。 さまざまなデバイスやプラットフォームでAndroidが利用できるため、モバイルオートメーションテストは面倒です。 より簡単にするために、Googleはこの課題に取り組み、Espressoフレームワークを開発しました。 Androidアプリケーションのユーザーインターフェイスを自動化およびテストするための非常にシンプルで一貫性のある柔軟なAPIを提供します。 Espressoテストは、Javaと、Androidアプリケーションを開発するための最新のプログラミング言語であるKotlinの両方で作成できます。

Espresso APIはシンプルで簡単に習得できます。 マルチスレッドテストの複雑さなしに、Android UIテストを簡単に実行できます。 Googleドライブ、マップ、および他のいくつかのアプリケーションは現在、エスプレッソを使用しています。

エスプレッソの特徴

エスプレッソでサポートされている主な機能は次のとおりです。

  • 非常にシンプルなAPIなので、簡単に習得できます。
  • 高度な拡張性と柔軟性。
  • Android WebViewコンポーネントをテストするための個別のモジュールを提供します。
  • Androidインテントを検証するだけでなく検証するための個別のモジュールを提供します。
  • アプリケーションとテスト間の自動同期を提供します。

エスプレッソの利点

エスプレッソの利点は何ですか?

  • 下位互換性
  • セットアップが簡単
  • 非常に安定したテストサイクル。
  • アプリケーション外でのテスト活動もサポートします。
  • JUnit4をサポート
  • ブラックボックステストの作成に適したUI自動化。

エスプレッソテストフレームワーク-セットアップ手順

この章では、espressoフレームワークをインストールし、espressoテストを記述してAndroidアプリケーションで実行するように設定する方法を理解しましょう。

前提条件

Espressoは、Android SDKを使用してJava/Kotlin言語で開発されたAndroidアプリケーションをテストするためのユーザーインターフェイステストフレームワークです。 したがって、エスプレッソの唯一の要件は、JavaまたはKotlinでAndroid SDKを使用してアプリケーションを開発することであり、最新のAndroid Studioを使用することをお勧めします。

エスプレッソフレームワークで作業を開始する前に適切に設定する項目のリストは次のとおりです-

  • 最新のJava JDKをインストールし、JAVA_HOME環境変数を構成します。
  • 最新のAndroid Studio(バージョン3.2。 以上)。
  • SDKマネージャーを使用して最新のAndroid SDKをインストールし、ANDROID_HOME環境変数を構成します。
  • 最新のGradle Build Toolをインストールし、GRADLE_HOME環境変数を設定します。

EspressoTestingフレームワークを構成する

当初、エスプレッソテストフレームワークは、Androidサポートライブラリの一部として提供されています。 その後、Androidチームは新しいAndroidライブラリAndroidXを提供し、最新のエスプレッソテストフレームワーク開発をライブラリに移行します。 エスプレッソテストフレームワークの最新の開発(Android 9.0、APIレベル28以上)は、AndroidXライブラリで行われます。

プロジェクトにエスプレッソテストフレームワークを含めることは、エスプレッソテストフレームワークをアプリケーションgradleファイルapp/build.gradleの依存関係として設定するのと同じくらい簡単です。 完全な構成は次のとおりです。

Androidサポートライブラリを使用して、

android {
   defaultConfig {
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

AndroidXライブラリを使用して、

android {
   defaultConfig {
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.androidx.test:runner:1.0.2'
   androidTestImplementation 'com.androidx.espresso:espresso-core:3.0.2'
}

_android/defaultConfig_の_testInstrumentationRunner_は、_AndroidJUnitRunner_クラスを設定して、インストルメンテーションテストを実行します。 _dependencies_の最初の行には_JUnit_テストフレームワークが含まれ、_dependencies_の2行目にはテストケースを実行するテストランナーライブラリが含まれ、最後に_dependencies_の3行目にはエスプレッソテストフレームワークが含まれます。

デフォルトでは、Androidスタジオは、espressoテストフレームワーク(Androidサポートライブラリ)を依存関係として設定し、Androidプロジェクトを作成すると、gradleは必要なライブラリをMavenリポジトリからダウンロードします。 簡単なHello world Androidアプリケーションを作成し、エスプレッソテストフレームワークが適切に構成されているかどうかを確認しましょう。

新しいAndroidアプリケーションを作成する手順は以下のとおりです-

  • Android Studioを起動します。
  • ファイル→新規→新規プロジェクトを選択します。
  • Application Name(HelloWorldApp)とCompany domain(espressosamples.finddevguides.com)を入力し、[_ Next_]をクリックします。

Androidアプリケーション

Androidプロジェクトを作成するには、

  • API 15:Android 4.0.3(IceCreamSandwich)として最小のAPIを選択し、[次へ]をクリックします。

ターゲットAndroidデバイス

Androidデバイスをターゲットにするには、

  • [空のアクティビティ]を選択し、[次へ]をクリックします。

空のアクティビティ

アクティビティをモバイルに追加するには、

  • メインアクティビティの名前を入力し、[完了]をクリックします。

メインアクティビティ

アクティビティを構成するには、

  • 新しいプロジェクトが作成されたら、_app/build.gradle_ファイルを開き、その内容を確認します。 ファイルの内容は次のとおりです。
apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.finddevguides.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'),    'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:28.0.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

最後の行は、エスプレッソテストフレームワークの依存関係を指定します。 デフォルトでは、Androidサポートライブラリが設定されています。 メニューで_Refactor_→Migrate to _AndroidX_をクリックして、_AndroidX_ライブラリを使用するようにアプリケーションを再構成できます。

Espresso Testing Framework

Androidxに移行するには、

  • これで、_app/build.gradle_が次のように変更されます。
apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.finddevguides.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
   implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

現在、最後の行にはAndroidXライブラリのエスプレッソテストフレームワークが含まれています。

デバイスの設定

テスト中は、テストに使用されるAndroidデバイスのアニメーションをオフにすることをお勧めします。 これにより、アイドリングリソースを確認する際の混乱が軽減されます。

Androidデバイスでアニメーションを無効にする方法を見てみましょう–(設定→開発者オプション)、

  • ウィンドウアニメーションスケール
  • 遷移アニメーションスケール
  • アニメーターの継続時間スケール

[設定]画面で[開発者オプション]メニューが使用できない場合は、[電話について]オプションで[番号を作成]を数回クリックします。 これにより、_Developer Option_メニューが有効になります。

Android Studioでテストを実行する

この章では、Androidスタジオを使用してテストを実行する方法を見てみましょう。

すべてのAndroidアプリケーションには2種類のテストがあります-

  • 機能/ユニットテスト
  • 計装テスト

機能テストでは、実際のAndroidアプリケーションをデバイスまたはエミュレーターにインストールして起動し、機能をテストする必要はありません。 実際のアプリケーションを呼び出さずにコンソール自体で起動できます。 ただし、インストルメンテーションテストでは、ユーザーインターフェイスやユーザーインタラクションなどの機能をテストするために、実際のアプリケーションを起動する必要があります。 デフォルトでは、単体テストは src/test/java/ フォルダーに書き込まれ、インストルメンテーションテストは src/androidTest/java/ フォルダーに書き込まれます。 Androidスタジオは、選択したテストクラスで記述されたテストを実行するために、テストクラスの_Run_コンテキストメニューを提供します。 デフォルトでは、Androidアプリケーションには_src/test_フォルダーに_ExampleUnitTest_と_src/androidTest_フォルダーに_ExampleInstrumentedTest_という2つのクラスがあります。

デフォルトの単体テストを実行するには、Androidスタジオで_ExampleUnitTest_を選択して右クリックし、次に示すように_Run 'ExampleUnitTest'_をクリックします。

Android Studio

ユニットテストの実行

これにより、単体テストが実行され、次のスクリーンショットのようにコンソールに結果が表示されます-

テストと表示

単体テストの成功

デフォルトのインストルメンテーションテストを実行するには、AndroidスタジオでExampleInstrumentationTestを選択し、右クリックして、次に示すように「ExampleInstrumentationTest」の実行をクリックします。

計測テスト

計装テストの実行

これは、デバイスまたはエミュレーターのいずれかでアプリケーションを起動することによって単体テストを実行し、次のスクリーンショットのようにコンソールに結果を表示します-

単体テスト

インストルメンテーションテストは成功しました。

Espresso Testing Framework-JUnitの概要

この章では、エスプレッソテストフレームワークが構築されるJavaコミュニティによって開発された人気のあるユニットテストフレームワークである_JUnit_の基本を理解しましょう。

_JUnit_は、Javaアプリケーションの単体テストの事実上の標準です。 単体テストで一般的ですが、計装テストも完全にサポートおよび提供されています。 Espressoテストライブラリは、必要なJUnitクラスを拡張して、Androidベースのインスツルメンテーションテストをサポートします。

簡単な単体テストを書く

Javaクラス_Computation_(Computation.java)を作成し、簡単な数学演算_Summation_および_Multiplication_を作成しましょう。 次に、_JUnit_を使用してテストケースを作成し、テストケースを実行して確認します。

  • Android Studioを起動します。
  • 前の章で作成した_HelloWorldApp_を開きます。 *_App/src/main/java/com/finddevguides/espressosamples/helloworldapp/_にファイル_Computation.java_を作成し、以下のように_Sum_および_Multiply_という2つの関数を記述します。
package com.finddevguides.espressosamples.helloworldapp;
public class Computation {
   public Computation() {}
   public int Sum(int a, int b) {
      return a + b;
   }
   public int Multiply(int a, int b) {
      return a* b;
   }
}
  • app/src/test/java/com/finddevguides/espressosamples/helloworldappにファイルComputationUnitTest.javaを作成し、ユニットテストケースを記述して、以下に示すようにSumおよびMultiply機能をテストします。
package com.finddevguides.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
   @Test
   public void sum_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

ここでは、2つの新しい用語_ @ Test_と_assertEquals_を使用しました。 一般に、JUnitはJavaアノテーションを使用して、クラス内のテストケースとテストケースの実行方法に関する情報を識別します。 _ @ Test_はそのようなJavaアノテーションの1つで、特定の関数がjunitテストケースであることを指定します。 _assertEquals_は、最初の引数(期待値)と2番目の引数(計算値)が等しく同じであることをアサートする関数です。 _JUnit_は、さまざまなテストシナリオ用の多数のアサーションメソッドを提供します。

  • 次に、前の章で説明したように、クラスを右クリックし、Run _'ComputationUnitTest'_オプションを呼び出して、Androidスタジオで_ComputationUnitTest_を実行します。 これにより、単体テストケースが実行され、成功が報告されます。

計算ユニットテストの結果は以下のとおりです-

計算ユニットテスト

アノテーション

JUnitフレームワークはアノテーションを広範囲に使用します。 重要な注釈のいくつかは次のとおりです-

  • @テスト
  • @前
  • @後
  • @BeforeClass
  • @放課後
  • @ルール

_ @ Test_注釈

_ @ Test_は、JUnit_フレームワークで非常に重要な注釈です。 _ @ Test_は、通常のメソッドとテストケースメソッドを区別するために使用されます。 メソッドが @ Test_アノテーションで装飾されると、その特定のメソッドは_Test case_と見なされ、_JUnit Runner_によって実行されます。 _JUnit Runner_は特別なクラスで、Javaクラス内で使用可能な_JUnitテストケースを見つけて実行するために使用されます。 現時点では、_Android Studioのビルドインオプションを使用してユニットテストを実行しています(これにより、_JUnit Runner_が実行されます)。 サンプルコードは次のとおりです。

package com.finddevguides.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

@前

_ @ Before_アノテーションは、特定のテストクラスで利用可能なテストメソッドを実行する前に呼び出す必要があるメソッドを参照するために使用されます。 たとえば、サンプルでは、​​Computation_オブジェクトを別のメソッドで作成し、 @ Before_アノテーションを付けて、_sum_isCorrect_と_multiply_isCorrect_の両方のテストケースの前に実行されるようにすることができます。 完全なコードは次のとおりです。

package com.finddevguides.espressosamples.helloworldapp;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@後

_ @ After_は_ @ Before_と似ていますが、_ @ After_アノテーションが付けられたメソッドは、各テストケースの実行後に呼び出されるか実行されます。 サンプルコードは次のとおりです。

package com.finddevguides.espressosamples.helloworldapp;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @After
   public void DestroyComputationObject() {
      this.computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@BeforeClass

_ @ BeforeClass_は_ @ Before_に似ていますが、_ @ BeforeClass_アノテーションが付けられたメソッドは、特定のクラスのすべてのテストケースを実行する前に1回だけ呼び出されるか実行されます。 データベース接続オブジェクトのようなリソースを集中的に使用するオブジェクトを作成すると便利です。 これにより、テストケースのコレクションを実行する時間が短縮されます。 このメソッドは、適切に機能するために静的である必要があります。 サンプルでは、​​以下に指定されているすべてのテストケースを実行する前に、計算オブジェクトを1回作成できます。

package com.finddevguides.espressosamples.helloworldapp;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@放課後

_ @ AfterClass_は_ @ BeforeClass_に似ていますが、_ @ AfterClass_アノテーションが付けられたメソッドは、特定のクラスのすべてのテストケースが実行された後に1回だけ呼び出されるか実行されます。 このメソッドは、正しく機能するために静的である必要もあります。 サンプルコードは次のとおりです-

package com.finddevguides.espressosamples.helloworldapp;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@ルール

_ @ Rule_アノテーションは、JUnit_のハイライトの1つです。 テストケースに動作を追加するために使用されます。 _TestRule_型のフィールドにのみ注釈を付けることができます。 _ @ Before_および @ After_アノテーションによって提供される機能セットを実際に提供しますが、効率的で再利用可能な方法です。 たとえば、テストケース中にデータを保存するために一時フォルダーが必要になる場合があります。 通常、テストケースを実行する前に(@Beforeまたは@BeforeClassアノテーションを使用して)一時フォルダーを作成し、テストケースの実行後に(@Afterまたは@AfterClassアノテーションを使用して)一時フォルダーを破棄する必要があります。 代わりに、JUnit_フレームワークによって提供される(_TestRule_型の)_TemporaryFolder_クラスを使用して、すべてのテストケース用の一時フォルダーを作成できます。一時フォルダーは、テストケースの実行時に削除されます。 タイプ_TemporaryFolder_の新しい変数を作成し、以下に指定するように @ Rule_で注釈を付ける必要があります。

package com.finddevguides.espressosamples.helloworldapp;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @Rule
   public TemporaryFolder folder = new TemporaryFolder();
   @Test
   public void file_isCreated() throws IOException {
      folder.newFolder("MyTestFolder");
      File testFile = folder.newFile("MyTestFile.txt");
      assertTrue(testFile.exists());
   }
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

実行の順序

_JUnit_では、以下に示すように、異なる注釈が付けられたメソッドが特定の順序で実行されます。

  • @BeforeClass
  • @ルール
  • @前
  • @テスト
  • @後
  • @放課後

アサーション

アサーションは、テストケースの期待値がテストケースの結果の実際の値と一致するかどうかを確認する方法です。 _JUnit_は、さまざまなシナリオのアサーションを提供します。いくつかの重要なアサーションを以下にリストします-

  • * fail()*-テストケースを明示的に失敗させる。
  • * assertTrue(boolean test_condition)*-test_conditionが真であることを確認します
  • * assertFalse(boolean test_condition)*-test_conditionがfalseであることを確認します
  • * assertEquals(expected、actual)*-両方の値が等しいことを確認します
  • * assertNull(object)*-オブジェクトがnullであることを確認します
  • * assertNotNull(object)*-オブジェクトがnullでないことを確認します
  • * assertSame(expected、actual)*-両方が同じオブジェクトを参照していることを確認します。
  • * assertNotSame(expected、actual)*-両方が異なるオブジェクトを参照していることを確認します。

エスプレッソテストフレームワーク-アーキテクチャ

この章では、エスプレッソテストフレームワークの用語、簡単なエスプレッソテストケースの作成方法、およびエスプレッソテストフレームワークの完全なワークフローまたはアーキテクチャについて学習します。

概要

Espressoは、Androidアプリケーションのユーザーインターフェイスとユーザーインタラクションをテストするための多数のクラスを提供します。 以下に指定されているように、それらは5つのカテゴリにグループ化することができます-

JUnitランナー

Androidテストフレームワークは、JUnit3およびJUnit4スタイルのテストケースで記述されたエスプレッソテストケースを実行するランナーAndroidJUnitRunnerを提供します。 これは、Androidアプリケーションに固有であり、実際のデバイスまたはエミュレーターの両方で、エスプレッソテストケースとテスト対象アプリケーションのロードを透過的に処理し、テストケースを実行し、テストケースの結果を報告します。 テストケースでAndroidJUnitRunnerを使用するには、@ RunWithアノテーションを使用してテストクラスに注釈を付け、次に指定されたとおりにAndroidJUnitRunner引数を渡す必要があります-

@RunWith(AndroidJUnit4.class)
   public class ExampleInstrumentedTest {
}

JUnitルール

Androidテストフレームワークは、テストケースを実行する前にAndroidアクティビティを起動するActivityTestRuleルールを提供します。 @ Test`および@Beforeアノテーションが付けられた各メソッドの前にアクティビティを起動します。 @Afterアノテーションが付けられたメソッドの後、アクティビティを終了します。 サンプルコードは次のとおりです。

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

ここで、_MainActivity_は、テストケースの実行前に起動され、特定のテストケースの実行後に破棄されるアクティビティです。

ViewMatchers

Espressoは、Androidアクティビティ画面のビュー階層内のUI要素/ビューを照合および検索するために、多数のビューマッチャークラス(androidx.test.espresso.matcher.ViewMatchers package)を提供します。 EspressoのメソッドonViewは、タイプ_Matcher_(ビューマッチャー)の単一の引数を取り、対応するUIビューを見つけて、対応する_ViewInteraction_オブジェクトを返します。 _onView_メソッドによって返される_ViewInteraction_オブジェクトを使用して、一致したビューのクリックなどのアクションを呼び出したり、一致したビューをアサートしたりできます。 「Hello World!」というテキストを含むビューを見つけるためのサンプルコードは次のとおりです。

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));

ここで、_withText_はマッチャーであり、「Hello World!」というテキストを持つUIビューと一致させるために使用できます。

ViewActions

Espressoは、選択された/一致したビューで異なるアクションを呼び出すために、(androidx.test.espresso.action.ViewActionsで)多数のビューアクションクラスを提供します。 _onView_が_ViewInteraction_オブジェクトと一致して返されると、_ViewInteraction_オブジェクトの「perform」メソッドを呼び出して適切なビューアクションでそれを渡すことで、任意のアクションを呼び出すことができます。 一致したビューをクリックするサンプルコードは次のとおりです。

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.perform(click());

ここで、一致したビューのクリックアクションが呼び出されます。

ViewAssertions

ビューマッチャーやビューアクションと同様に、Espressoは(_androidx.test.espresso.assertion.ViewAssertions_パッケージで)多数のビューアサーションを提供して、一致したビューが予想どおりであることをアサートします。 onViewが_ViewInteraction_オブジェクトに一致して返されると、_ViewInteraction_のcheckメソッドを使用して適切なビューアサーションで渡すことで、すべてのアサートをチェックできます。 一致したビューが次のとおりであることをアサートするサンプルコード、

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.check(matches(withId(R.id.text_view)));

ここで、_matches_はビューマッチャーを受け入れ、ビューアサーションを返します。これは、_ViewInteraction_のcheckメソッドで確認できます。

エスプレッソテストフレームワークのワークフロー

エスプレッソテストフレームワークがどのように機能し、どのようにシンプルで柔軟な方法であらゆる種類のユーザーインタラクションを行うオプションを提供するかを理解しましょう。 エスプレッソテストケースのワークフローは次のとおりです。

  • 前に学んだように、Android JUnitランナー_AndroidJUnit4_はAndroidテストケースを実行します。 エスプレッソテストケースは、_ @ RunWith(AndroidJUnut.class)_でマークする必要があります。 最初に、_AndroidJUnit4_はテストケースを実行する環境を準備します。 接続されているAndroidデバイスまたはエミュレーターを起動し、アプリケーションをインストールして、テスト対象のアプリケーションが準備完了状態であることを確認します。 テストケースを実行し、結果を報告します。
  • Espressoでは、アクティビティを指定するために、少なくとも_ActivityTestRule_タイプの_JUnit_ルールが1つ必要です。 Android JUnitランナーは、_ActivityTestRule_を使用して起動するアクティビティを開始します。
  • すべてのテストケースでは、目的のビューに一致して検索するために、少なくとも1つの_onView_または_onDate_(_AdapterView_などのデータベースビューの検索に使用)メソッド呼び出しが必要です。 onViewまたはonDataは_ViewInteraction_オブジェクトを返します。
  • _ViewInteraction_オブジェクトが返されたら、選択したビューのアクションを呼び出すか、アサーションを使用して予想されるビューのビューを確認します。
  • アクションは、_ViewInteraction_オブジェクトのperformメソッドを使用して、使用可能なビューアクションのいずれかを渡すことで呼び出すことができます。
  • アサーションは、使用可能なビューアサーションのいずれかを渡すことにより、_ViewInteraction_オブジェクトのcheckメソッドを使用して呼び出すことができます。

_Workflow_のダイアグラム表現は次のとおりです。

ワークフロー

例–アサーションの表示

「HelloWorldApp」アプリケーションで「Hello World!」というテキストを持つテキストビューを見つける簡単なテストケースを作成し、ビューアサーションを使用してアサートします。 完全なコードは次のとおりです。

package com.finddevguides.espressosamples.helloworldapp;

import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withText;;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static org.junit.Assert.*;
/**
 *Instrumented test, which will execute on an Android device.
  *
   * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void view_isCorrect() {
      onView(withText("Hello World!")).check(matches(isDisplayed()));
   }
   @Test
   public void useAppContext() {
     //Context of the app under test.
      Context appContext = InstrumentationRegistry.getTargetContext();
      assertEquals("com.finddevguides.espressosamples.helloworldapp", appContext.getPackageName());
   }
}

ここでは、_withText_ビューマッチャーを使用して、「Hello World!」テキストを含むテキストビューを検索し、ビューアサーションに一致して、テキストビューが適切に表示されることをアサートしました。 Android Studioでテストケースが呼び出されると、テストケースが実行され、以下のように成功メッセージが報告されます。

view_isCorrectテストケース

テストケース

エスプレッソテストフレームワーク-ビューマッチャー

Espressoフレームワークは、多くのビューマッチャーを提供します。 マッチャーの目的は、ID、テキスト、子ビューの可用性など、ビューのさまざまな属性を使用してビューを一致させることです。 各マッチャーは、ビューの特定の属性と一致し、特定のタイプのビューに適用されます。 たとえば、_withId_マッチャーはビューの_Id_プロパティと一致し、すべてのビューに適用されますが、withTextマッチャーはビューの_Text_プロパティと一致し、_TextView_のみに適用されます。

この章では、エスプレッソマッチャーが構築されている_Hamcrest_ライブラリと同様に、エスプレッソテストフレームワークによって提供されるさまざまなマッチャーを学びましょう。

ハムクレスト図書館

_Hamcrest_ライブラリは、エスプレッソテストフレームワークの範囲内の重要なライブラリです。 _Hamcrest_は、それ自体マッチャーオブジェクトを記述するためのフレームワークです。 Espressoフレームワークは_Hamcrest_ライブラリを広範に使用し、必要に応じて拡張してシンプルで拡張可能なマッチャーを提供します。

_Hamcrest_は、単純な関数_assertThat_と、オブジェクトをアサートするマッチャーのコレクションを提供します。 _assertThat_には3つの引数があり、以下に示すとおりです-

  • 文字列(テストの説明、オプション)
  • オブジェクト(実際)
  • マッチャー(予想)

リストオブジェクトに期待値があるかどうかをテストする簡単な例を書いてみましょう。

import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
@Test
public void list_hasValue() {
   ArrayList<String> list = new ArrayList<String>();
   list.add("John");
   assertThat("Is list has John?", list, hasItem("John"));
}

ここで、_hasItem_はマッチャーを返し、実際のリストがアイテムの1つとして値を指定しているかどうかをチェックします。

_Hamcrest_には多くの組み込みマッチャーがあり、新しいマッチャーを作成するオプションもあります。 エスプレッソテストフレームワークで役立つ重要な組み込みマッチャーのいくつかは次のとおりです-

何でも-常にマッチャー

論理ベースのマッチャー

  • allOf -任意の数のマッチャーを受け入れ、すべてのマッチャーが成功した場合にのみ一致します。
  • anyOf -任意の数のマッチャーを受け入れ、1つのマッチャーが成功した場合に一致します。
  • not -1つのマッチャーを受け入れ、マッチャーが失敗した場合にのみマッチし、その逆も同様です。

テキストベースのマッチャー

  • equalToIgnoringCase -実際の入力が、大文字と小文字を区別せずに予想される文字列と等しいかどうかをテストするために使用
  • equalToIgnoringWhiteSpace -大文字と小文字を無視して、実際の入力が指定された文字列に等しいかどうかをテストするために使用されます。
  • containsString -実際の入力に指定された文字列が含まれているかどうかをテストするために使用されます。
  • endsWith -実際の入力が指定された文字列で始まるかどうかをテストするために使用されます。
  • startsWith -入力が実際に指定された文字列で終了するかどうかをテストするために使用されます。

番号ベースのマッチャー

  • closeTo -実際の入力が予想数に近いかどうかをテストするために使用されます。
  • greaterThan -実際の入力が予想された数よりも大きいかどうかをテストするために使用されます。
  • greaterThanOrEqualTo -実際の入力が予想される数値以上かどうかをテストするために使用されます。
  • lessThan -実際の入力が予想される数より少ないかどうかをテストするために使用されます。
  • lessThanOrEqualTo -実際の入力が予想される数以下かどうかをテストするために使用されます。

オブジェクトベースのマッチャー

  • equalTo -実際の入力が期待されるオブジェクトと等しいかどうかをテストするために使用されます
  • hasToString -実際の入力にtoStringメソッドがあるかどうかをテストするために使用されます。
  • instanceOf -実際の入力が予想されるクラスのインスタンスであるかどうかをテストするために使用されます。
  • isCompatibleType -実際の入力が予想されるタイプと互換性があるかどうかをテストするために使用されます。
  • notNullValue -実際の入力がヌルではないかどうかをテストするために使用されます。
  • sameInstance -実際の入力と予想が同じインスタンスであるかどうかをテストするために使用されます。
  • hasProperty -実際の入力に期待されるプロパティがあるかどうかをテストするために使用

is −シュガーまたは_equalTo_のショートカット

マッチャー

Espressoには、ビューを照合して検索するためのonView()メソッドが用意されています。 ビューマッチャーを受け入れ、ViewInteractionオブジェクトを返し、一致したビューと対話します。 ビューマッチャーの頻繁に使用されるリストは以下に説明されています-

withId()

_withId()_はint型の引数を受け入れ、引数はビューのIDを参照します。 ビューのIDを使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withId(R.id.testView))

withText()

_withText()_は_string_型の引数を受け入れ、引数はビューのtextプロパティの値を参照します ビューのテキスト値を使用してビューに一致するマッチャーを返します。 _TextView_のみに適用されます。 サンプルコードは次のとおりです。

onView(withText("Hello World!"))

withContentDescription()

_withContentDescription()_は_string_型の引数を受け入れ、引数はビューのコンテンツ説明プロパティの値を参照します ビューの説明を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withContentDescription("blah"))

テキスト自体ではなく、テキスト値のリソースIDを渡すこともできます。

onView(withContentDescription(R.id.res_id_blah))

hasContentDescription()

_hasContentDescription()_には引数がありません。 これは、コンテンツの説明があるビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), hasContentDescription()))

withTagKey()

_withTagKey()_は_string_型の引数を受け入れ、引数はビューのタグキーを参照します。 タグキーを使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withTagKey("blah"))

タグ名自体の代わりに、タグ名のリソースIDを渡すこともできます。

onView(withTagKey(R.id.res_id_blah))

withTagValue()

_withTagValue()_は、Matcher <Object>型の引数を受け入れ、引数はビューのタグ値を参照します。 タグ値を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withTagValue(is((Object) "blah")))

ここで、_is_はHamcrestマッチャーです。

withClassName()

_withClassName()_は、Matcher <String>型の引数を受け入れ、引数はビューのクラス名の値を参照します。 クラス名を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withClassName(endsWith("EditText")))

ここで、_endsWith_はHamcrestマッチャーであり、Matcher <String>を返します

withHint()

_withHint()_は、Matcher <String>型の引数を受け入れ、引数はビューのヒント値を参照します。 ビューのヒントを使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withClassName(endsWith("Enter name")))

withInputType()

_withInputType()_は_int_型の引数を受け入れ、引数はビューの入力型を参照します。 入力タイプを使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withInputType(TYPE_CLASS_DATETIME))

ここで、_TYPE_CLASS_DATETIME_は、日付と時刻をサポートする編集ビューを指します。

withResourceName()

_withResourceName()_は、Matcher <String>型の引数を受け入れ、引数はビューのクラス名の値を参照します。 ビューのリソース名を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withResourceName(endsWith("res_name")))

文字列引数も受け入れます。 サンプルコードは次のとおりです。

onView(withResourceName("my_res_name"))

withAlpha()

_withAlpha()_は_float_型の引数を受け入れ、引数はビューのアルファ値を参照します。 ビューのアルファ値を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withAlpha(0.8))

withEffectiveVisibility()

_withEffectiveVisibility()_は_ViewMatchers.Visibility_型の引数を受け入れ、引数はビューの有効な可視性を参照します。 ビューの可視性を使用してビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withEffectiveVisibility(withEffectiveVisibility.INVISIBLE))

withSpinnerText()

_withSpinnerText()_は、Matcher <String>型の引数を受け入れ、引数はスピナーの現在選択されているビューの値を参照します。 選択したアイテムのtoString値に基づいてスピナーと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(withSpinnerText(endsWith("USA")))

文字列引数または文字列のリソースIDも受け入れます。 サンプルコードは次のとおりです。

onView(withResourceName("USA"))
onView(withResourceName(R.string.res_usa))

withSubstring()

_withSubString()_は、_withText()_と似ていますが、ビューのテキスト値のサブストリングをテストするのに役立つことを除きます。

onView(withSubString("Hello"))

hasLinks()

_hasLinks()_には引数がなく、リンクを持つビューに一致するマッチャーを返します。 TextViewのみに適用されます。 サンプルコードは次のとおりです。

onView(allOf(withSubString("Hello"), hasLinks()))

ここで、_allOf_はHamcrestマッチャーです。 _allOf_はマッチャーを返します。マッチャーは渡されたすべてのマッチャーに一致します。ここでは、ビューのテキスト値にリンクがあるかどうかを確認するのに使用されます。

hasTextColor()

_hasTextColor()_はint型の単一の引数を受け入れ、引数は色のリソースIDを参照します。 色に基づいて_TextView_と一致するマッチャーを返します。 _TextView_のみに適用されます。 サンプルコードは次のとおりです。

onView(allOf(withSubString("Hello"), hasTextColor(R.color.Red)))

hasEllipsizedText()

_hasEllipsizedText()_には引数がありません。 長いテキストと省略形(最初のいずれか)を持つTextViewに一致するマッチャーを返します。 十.. 最後)またはカットオフ(最初…)。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_text_view_id), hasEllipsizedText()))

hasMultilineText()

_hasMultilineText()_には引数がありません。 これは、複数行のテキストを持つTextViewに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_test_view_id), hasMultilineText()))

hasBackground()

_hasBackground()_は、int型の単一の引数を受け入れ、引数はバックグラウンドリソースのリソースIDを参照します。 バックグラウンドリソースに基づいてビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId("image"), hasBackground(R.drawable.your_drawable)))

hasErrorText()

_hasErrorText()_は、Matcher <String>型の引数を受け入れ、引数はビューの(EditText)エラー文字列値を参照します。 ビューのエラー文字列を使用してビューに一致するマッチャーを返します。 これは_EditText_のみに適用されます。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.editText_name), hasErrorText(is("name is required"))))

文字列引数も受け入れます。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.editText_name), hasErrorText("name is required")))

hasImeAction()

_hasImeAction()_は、Matcher <Integer>型の引数を受け入れ、引数はビューの(EditText)サポートされている入力メソッドを参照します。 ビューのサポートされている入力メソッドを使用してビューに一致するマッチャーを返します。 これは_EditText_のみに適用されます。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.editText_name),
hasImeAction(is(EditorInfo.IME_ACTION_GO))))

ここで、EditorInfo.IME_ACTION_GOはインプットメソッドオプションの1つです。 _hasImeAction()_は整数引数も受け入れます。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.editText_name),
hasImeAction(EditorInfo.IME_ACTION_GO)))

supportsInputMethods()

_supportsInputMethods()_には引数がありません。 入力メソッドをサポートしている場合、ビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.editText_name), supportsInputMethods()))

isRoot()

_isRoot()_には引数がありません。 ルートビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_root_id), isRoot()))

表示されています()

_isDisplayed()_には引数がありません。 現在表示されているビューと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isDisplayed()))

isDisplayingAtLeast()

_isDisplayingAtLeast()_は、int型の単一の引数を受け入れます。 現在表示されているビューと少なくとも指定された割合で一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isDisplayingAtLeast(75)))

isCompletelyDisplayed()

_isCompletelyDisplayed()_には引数がありません。 現在完全に画面に表示されているビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isCompletelyDisplayed()))

有効になっています()

_isEnabled()_には引数がありません。 有効なビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isEnabled()))

isFocusable()

_isFocusable()_には引数がありません。 フォーカスオプションを持つビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isFocusable()))

hasFocus()

_hasFocus()_には引数がありません。 現在フォーカスされているビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), hasFocus()))

isClickable()

_isClickable()_には引数がありません。 クリックオプションであるビューと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isClickable()))

isSelected()

_isSelected()_には引数がありません。 現在選択されているビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isSelected()))

isChecked()

_isChecked()_には引数がありません。 それは、CompoundButtonタイプ(またはそのサブタイプ)で、チェック状態のビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isChecked()))

isNotChecked()

_isNotChecked()_はisCheckedの反対です。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_view_id), isNotChecked()))

isJavascriptEnabled()

_isJavascriptEnabled()_には引数がありません。 JavaScriptを評価しているWebViewに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.my_webview_id), isJavascriptEnabled()))

withParent()

_withParent()_は、Matcher <View>型の引数を1つ受け入れます。 引数はビューを参照します。 指定されたビューが親ビューであるビューと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.childView), withParent(withId(R.id.parentView))))

hasSibling()

_hasSibling()_は、Matcher> View <型の引数を1つ受け入れます。 引数はビューを参照します。 渡されたビューがその兄弟ビューの1つであるビューと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(hasSibling(withId(R.id.siblingView)))

withChild()

_withChild()_は、Matcher <View>型の引数を1つ受け入れます。 引数はビューを参照します。 渡されたビューが子ビューであるビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.parentView), withChild(withId(R.id.childView))))

hasChildCount()

_hasChildCount()_は、int型の引数を1つ受け入れます。 引数は、ビューの子カウントを参照します。 引数で指定されたのとまったく同じ数の子ビューを持つビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(hasChildCount(4))

hasMinimumChildCount()

_hasMinimumChildCount()_は、int型の引数を1つ受け入れます。 引数は、ビューの子カウントを参照します。 引数で指定された数以上の子ビューを持つビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(hasMinimumChildCount(4))

hasDescendant()

_hasDescendant()_は、Matcher <View>型の引数を1つ受け入れます。 引数はビューを参照します。 渡されたビューがビュー階層の子孫ビューの1つであるビューに一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(hasDescendant(withId(R.id.descendantView)))

isDescendantOfA()

_isDescendantOfA()_は、Matcher <View>型の引数を1つ受け入れます。 引数はビューを参照します。 渡されたビューがビュー階層の祖先ビューの1つであるビューと一致するマッチャーを返します。 サンプルコードは次のとおりです。

onView(allOf(withId(R.id.myView), isDescendantOfA(withId(R.id.parentView))))

カスタムビューマッチャー

Espressoには、独自のカスタムビューマッチャーを作成するためのさまざまなオプションがあり、_Hamcrest_マッチャーに基づいています。 カスタムマッチャーは、フレームワークを拡張し、好みに合わせてフレームワークをカスタマイズするための非常に強力な概念です。 カスタムマッチャーを作成する利点のいくつかは次のとおりです。

  • 独自のカスタムビューのユニークな機能を活用するには
  • カスタムマッチャーは、_AdapterView_ベースのテストケースでさまざまな種類の基になるデータと照合するのに役立ちます。
  • 複数のマッチャーの機能を組み合わせて、現在のマッチャーを簡素化するには

需要が生じたときに新しいマッチャーを作成でき、非常に簡単です。 新しいカスタムマッチャーを作成して、_TextView_のidとテキストの両方をテストするマッチャーを返します。

エスプレッソは、新しいマッチャーを記述するために次の2つのクラスを提供します-

  • TypeSafeMatcher
  • BoundedMatcher

両方のクラスは性質が似ていますが、_BoundedMatcher_は、正しい型を手動で確認することなく、正しい型へのオブジェクトのキャストを透過的に処理します。 _BoundedMatcher_クラスを使用して、新しいマッチャー_withIdAndText_を作成します。 新しいマッチャーを作成する手順を確認しましょう。

  • _app/build.gradle_ファイルに以下の依存関係を追加して同期します。
dependencies {
   implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • マッチャー(メソッド)を含める新しいクラスを作成し、_final_としてマークします
public final class MyMatchers {
}
  • 必要な引数を使用して新しいクラス内で静的メソッドを宣言し、Matcher <View>を戻り値の型として設定します。
public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
   }
}
  • 静的メソッド内に以下のシグネチャを持つ新しいBoundedMatcherオブジェクト(戻り値も)を作成します。
public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
      };
   }
}
  • _BoundedMatcher_オブジェクトの_describeTo_および_matchesSafely_メソッドをオーバーライドします。 describeToには、戻り値のないタイプ_Description_の単一の引数があり、マッチャーに関するエラー情報に使用されます。 _matchesSafely_には戻り値の型_boolean_を持つTextView型の引数が1つあり、ビューの照合に使用されます。

コードの最終バージョンは次のとおりです。

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
         @Override
         public void describeTo(final Description description) {
            description.appendText("error text: ");
            stringMatcher.describeTo(description);
            integerMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(final TextView textView) {
            return stringMatcher.matches(textView.getText().toString()) &&
            integerMatcher.matches(textView.getId());
         }
      };
   }
}
  • 最後に、mewマッチャーを使用して、以下に示すテストケースを記述できます。
@Test
public void view_customMatcher_isCorrect() {
   onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
      .check(matches(withText("Hello World!")));
}

エスプレッソテストフレームワーク-アサーションの表示

前述のように、ビューアサーションは、実際のビュー(ビューマッチャーを使用して検出)と予想されるビューの両方が同じであることをアサートするために使用されます。 サンプルコードは次のとおりです。

onView(withId(R.id.my_view)) .check(matches(withText("Hello")))

ここに、

  • _onView()_は、一致したビューに対応する_ViewInteration_オブジェクトを返します。 _ViewInteraction_は、一致したビューと対話するために使用されます。
  • _withId(R.id.my_view)_は、_id_属性が_my_view_と等しいビュー(実際)と一致するビューマッチャーを返します。
  • _withText(“ Hello”)_は、_Hello_に等しいテキスト属性を持つビュー(予想される)と一致するビューマッチャーも返します。
  • _check_は、_ViewAssertion_型の引数を受け入れ、渡された_ViewAssertion_オブジェクトを使用してアサーションを行うメソッドです。
  • _matches(withText(“ Hello”))_は、ビューアサーションを返します。これは、実際のビュー(_withId_を使用して検出された)と期待されるビュー(_withText_を使用して検出された)が同一であることをアサートする*実際の仕事*を実行します

ビューオブジェクトをアサートするためのエスプレッソテストフレームワークによって提供されるメソッドのいくつかを学びましょう。

存在しない()

ビューアサーションを返します。これにより、ビューマッチャーが一致するビューを見つけられないことが保証されます。

onView(withText("Hello")) .check(doesNotExist());

ここで、テストケースは、テキストHelloのあるビューがないことを確認します。

matches()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーによって一致したビューと一致することが保証されます。

onView(withId(R.id.textView_hello)) .check(matches(withText("Hello World!")));

ここで、テストケースは、ID R.id.textView_helloを持つビューが存在し、テキストHello World!を持つターゲットビューと一致することを確認します。

isBottomAlignedWith()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーと下に揃えられます。

onView(withId(R.id.view)) .check(isBottomAlignedWith(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューと下揃えであることを確認します。

isCompletelyAbove()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの上に完全に配置されることが保証されます。

onView(withId(R.id.view)) .check(isCompletelyAbove(withId(R.id.target_view)))

ここで、テストケースは、IDを持つビューR.id.viewが存在し、IDを持つビュー_R.id.target_view_の上に完全に配置されることを確認します。

isCompletelyBelow()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの下に完全に配置されることが保証されます。

onView(withId(R.id.view)) .check(isCompletelyBelow(withId(R.id.target_view)))

ここでは、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの下に完全に配置されていることを確認します。

isCompletelyLeftOf()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの完全に左に配置されます。

onView(withId(R.id.view)) .check(isCompletelyLeftOf(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの完全に左に配置されていることを確認します

isCompletelyRightOf()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの完全に右に配置されます。

onView(withId(R.id.view)) .check(isCompletelyRightOf(withId(R.id.target_view)))

ここで、テストケースは、ID R.id.viewを持つビューが存在し、ID R.id.target_viewを持つビューの完全に右に配置されることを確認します。

isLeftAlignedWith()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーと左揃えになっていることが保証されます。

onView(withId(R.id.view)) .check(isLeftAlignedWith(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューと左揃えであることを確認します

isPartiallyAbove()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの上に部分的に配置されます。

onView(withId(R.id.view)) .check(isPartiallyAbove(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの上に部分的に配置されていることを確認します

isPartiallyBelow()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの下に部分的に配置されます。

onView(withId(R.id.view)) .check(isPartiallyBelow(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの下に部分的に配置されていることを確認します。

isPartiallyLeftOf()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの部分的に左に配置されます。

onView(withId(R.id.view)) .check(isPartiallyLeftOf(withId(R.id.target_view)))

ここで、テストケースでは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの部分的に左に配置されていることを確認します。

isPartiallyRightOf()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーの部分的に右側に配置されます。

onView(withId(R.id.view)) .check(isPartiallyRightOf(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの部分的に右側に配置されることを確認します。

isRightAlignedWith()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーと右揃えになります。

onView(withId(R.id.view)) .check(isRightAlignedWith(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューと右揃えであることを確認します。

isTopAlignedWith()

ターゲットビューマッチャーを受け入れ、ビューアサーションを返します。これにより、ビューマッチャー(実際)が存在し、ターゲットビューマッチャーと上揃えになっていることが保証されます。

onView(withId(R.id.view)) .check(isTopAlignedWith(withId(R.id.target_view)))

ここで、テストケースは、ID _R.id.view_を持つビューが存在し、ID _R.id.target_view_を持つビューの上部に位置合わせされていることを確認します。

noEllipsizedText()

ビューのアサーションを返します。これにより、ビュー階層に省略されたテキストビューや切り取られたテキストビューが含まれなくなります。

onView(withId(R.id.view)) .check(noEllipsizedText());

noMultilineButtons()

ビューのアサーションを返します。これにより、ビュー階層に複数行のボタンが含まれないことが保証されます。

onView(withId(R.id.view)) .check(noMultilineButtons());

noOverlaps()

ビューアサーションを返します。これにより、TextViewまたはImageViewに割り当て可能な子孫オブジェクトが互いにオーバーラップしないことが保証されます。 ターゲットビューマッチャーを受け入れ、ビューアサーションを返す別のオプションがあります。これにより、ターゲットビューに一致する子孫ビューが重複しないようになります。

エスプレッソテストフレームワーク-アクションの表示

前に学習したように、ビューアクションは、Androidアプリケーションのユーザーが実行可能なすべてのアクションを自動化します。 Espresso onView_および“ onData”は_perform_メソッドを提供します。このメソッドは、ビューアクションを受け入れ、テスト環境で対応するユーザーアクションを呼び出し/自動化します。 たとえば、「click()」はビューアクションであり、onView(_R.id.myButton _)。perform(click())_メソッドに渡されると、ボタンのクリックイベントが発生します(id: myButton」)テスト環境で。

この章では、エスプレッソテストフレームワークによって提供されるビューアクションについて学びましょう。

typeText()

_typeText()_は、_String_型の引数(テキスト)を1つ受け取り、ビューアクションを返します。 返されるビューアクションは、指定されたテキストをビューに入力します。 テキストを配置する前に、ビューを1回タップします。 既にテキストが含まれている場合、コンテンツは任意の位置に配置できます。

onView(withId(R.id.text_view)).perform(typeText("Hello World!"))

typeTextIntoFocusedView()

_typeTextIntoFocusedView()_は、ビュー内のカーソル位置のすぐ隣にテキストを配置することを除いて、_typeText()_と似ています。

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

replaceText()

_replaceText()_は、ビューのコンテンツを置き換えることを除いて、_typeText()_と似ています。

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

クリアテキスト()

_clearText()_には引数がなく、ビュー内のテキストをクリアするビューアクションを返します。

onView(withId(R.id.text_view)).perform(clearText())

pressKey()

_pressKey()_は、キーコード(KeyEvent.KEYCODE_ENTERなど)を受け入れ、ビューアクションを返します。このアクションは、キーコードに対応するキーを押します。

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER))

pressMenuKey()

_pressMenuKey()_には引数がなく、ハードウェアメニューキーを押すビューアクションを返します。

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER), pressMenuKey())

closeSoftKeyboard()

_closeSoftKeyboard()_には引数がなく、キーボードが開いている場合はキーボードを閉じるビューアクションを返します。

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", closeSoftKeyboard())

クリック()

_click()_には引数がなく、ビューのクリックアクションを呼び出すビューアクションを返します。

onView(withId(R.id.button)).perform(click())

ダブルクリック()

_doubleClick()_には引数がなく、ビューのダブルクリックアクションを呼び出すビューアクションを返します。

onView(withId(R.id.button)).perform(doubleClick())

longClick()

_longClick()_には引数がなく、ビューのロングクリックアクションを呼び出すビューアクションを返します。

onView(withId(R.id.button)).perform(longClick())

pressBack()

pressBack()には引数がなく、戻るボタンをクリックするビューアクションを返します。

onView(withId(R.id.button)).perform(pressBack())

pressBackUnconditionally()

_pressBackUnconditionally()_には引数がなく、戻るボタンをクリックし、戻るボタンアクションがアプリケーション自体を終了する場合に例外をスローしないビューアクションを返します。

onView(withId(R.id.button)).perform(pressBack())

openLink()

_openLink()_には2つの引数があります。 最初の引数(リンクテキスト)は_Matcher_タイプで、HTMLアンカータグのテキストを参照します。 2番目の引数(url)は_Matcher_型で、HTMLアンカータグのURLを参照します。 _TextView_のみに適用されます。 ビューアクションを返します。このアクションは、テキストビューのコンテンツで使用可能なすべてのHTMLアンカータグを収集し、最初の引数(リンクテキスト)と2番目の引数(url)に一致するアンカータグを見つけ、最後に対応するurlを開きます。 次のような内容を持つテキストビューを考えてみましょう-

<a href="http://www.google.com/">copyright</a>

次に、以下のテストケースを使用してリンクを開いてテストできます。

onView(withId(R.id.text_view)).perform(openLink(is("copyright"),
   is(Uri.parse("http://www.google.com/"))))

ここで、openLinkはテキストビューのコンテンツを取得し、著作権がテキストであるリンク、https://www.google.com/[www.google.com]をURLとして見つけ、ブラウザーでURLを開きます。

openLinkWithText()

openLinkWithText()_には1つの引数があり、* String 型またはMatcher型のいずれかです。 これは、単に_openLink *メソッドへのショートカットです。

onView(withId(R.id.text_view)).perform(openLinkWithText("copyright"))

openLinkWithUri()

openLinkWithUri()_には1つの引数があり、_String_型またはMatcher型のいずれかです。 これは、_openLink *メソッドの_simply_ short cutです。

onView(withId(R.id.text_view)).perform(openLinkWithUri("http://www.google.com/"))

pressImeActionButton()

_pressImeActionButton()_には引数がなく、_android:imeOptions_設定で設定されたアクションを実行するビューアクションを返します。 たとえば、_android:imeOptions_がactionNextに等しい場合、これはカーソルを画面内の次の可能な_EditText_ビューに移動します。

onView(withId(R.id.text_view)).perform(pressImeActionButton())

scrollTo()

_scrollTo()_には引数がなく、画面上の一致したscrollViewをスクロールするビューアクションを返します。

onView(withId(R.id.scrollView)).perform(scrollTo())

スワイプダウン()

_swipeDown()_には引数がなく、画面上のスワイプダウンアクションを起動するビューアクションを返します。

onView(withId(R.id.root)).perform(swipeDown())

上にスワイプする()

_swipeUp()_には引数がなく、画面上で上にスワイプアクションを起動するビューアクションを返します。

onView(withId(R.id.root)).perform(swipeUp())

swipeRight()

_swipeRight()_には引数がなく、画面上で右にスワイプアクションを実行するビューアクションを返します。

onView(withId(R.id.root)).perform(swipeRight())

左スワイプ()

_swipeLeft()_には引数がなく、画面上で左にスワイプアクションを実行するビューアクションを返します。

onView(withId(R.id.root)).perform(swipeLeft())

エスプレッソテストフレームワーク-AdapterView

AdapterView_は、_Adapter_を使用して、基になるデータソースから取得した製品リストやユーザーの連絡先などの類似情報のコレクションをレンダリングするために特別に設計された特別な種類のビューです。 データソースは、複雑なデータベースエントリの単純なリストです。 _AdapterView_から派生したビューには、_ListView _、 GridView_、および_Spinner_があります。

_AdapterView_は、基になるデータソースで利用可能なデータの量に応じて、ユーザーインターフェイスを動的にレンダリングします。 さらに、_AdapterView_は、必要な最小限のデータのみをレンダリングします。これは、画面の使用可能な可視領域にレンダリングできます。 _AdapterView_は、基になるデータが大きい場合でもメモリを節約し、ユーザーインターフェイスを滑らかに見せるためにこれを行います。

分析すると、AdapterView_アーキテクチャの性質により、_onView_オプションとそのビューマッチャーが無関係になります。これは、テストする特定のビューが最初はまったくレンダリングされない可能性があるためです。 幸いなことに、espressoは_onData()メソッドを提供します。このメソッドは、基になるデータに一致するハムクレストマッチャー(基になるデータのデータ型に関連)を受け入れ、一致したデータのビューに対応する_DataInteraction_型のオブジェクトを返します。 サンプルコードは次のとおりです。

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

ここで、_onData()_は、基になるデータ(配列リスト)で使用可能な場合、エントリ "Apple"に一致し、_DataInteraction_オブジェクトを返し、一致したビュー( "Apple"エントリに対応するTextView)と対話します。

方法

_DataInteraction_は、ビューと対話する以下のメソッドを提供します。

perform()

これは、ビューアクションを受け入れ、渡されたビューアクションを起動します。

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

小切手()

これはビューアサーションを受け入れ、渡されたビューアサーションをチェックします。

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .check(matches(withText("Apple")))

inAdapterView()

これはビューマッチャーを受け入れます。 ビューマッチャーで渡された特定の_AdapterView_を選択し、_DataInteraction_オブジェクトを返し、一致した_AdapterView_と対話します。

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

atPosition()

これは整数型の引数を受け入れ、基になるデータ内のアイテムの位置を参照します。 渡されたデータの位置値に対応するビューを選択し、_DataInteraction_オブジェクトを返し、一致したビューと対話します。 基になるデータの正しい順序がわかっている場合に役立ちます。

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

onChildView()

これはビューマッチャーを受け入れ、特定の子ビュー内のビューと一致します。 たとえば、_AdapterView_に基づく製品リストの_Buy_ボタンなどの特定のアイテムを操作できます。

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .onChildView(withId(R.id.buy_button))
   .perform(click())

サンプルアプリケーションを作成する

以下に示す手順に従って、_AdapterView_に基づく単純なアプリケーションを作成し、_onData()_メソッドを使用してテストケースを作成します。

  • Androidスタジオを起動します。
  • 前述のように新しいプロジェクトを作成し、_MyFruitApp_という名前を付けます。
  • RefactorMigrate to _AndroidX_オプションメニューを使用して、アプリケーションをAndroidXフレームワークに移行します。
  • メインアクティビティのデフォルトデザインを削除し、_ListView_を追加します。 _activity_main.xml_の内容は次のとおりです。
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"/>
</RelativeLayout>
  • 新しいレイアウトリソース_item.xml_を追加して、リストビューのアイテムテンプレートを指定します。 _item.xml_の内容は次のとおりです。
<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • 次に、基本データとしてフルーツ配列を持つアダプターを作成し、リストビューに設定します。 これは、以下で指定するように、_MainActivity_の_onCreate()_で行う必要があります。
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

  //Find fruit list view
   final ListView listView = (ListView) findViewById(R.id.listView);

  //Initialize fruit data
   String[] fruits = new String[]{
      "Apple",
      "Banana",
      "Cherry",
      "Dates",
      "Elderberry",
      "Fig",
      "Grapes",
      "Grapefruit",
      "Guava",
      "Jack fruit",
      "Lemon",
      "Mango",
      "Orange",
      "Papaya",
      "Pears",
      "Peaches",
      "Pineapple",
      "Plums",
      "Raspberry",
      "Strawberry",
      "Watermelon"
   };

  //Create array list of fruits
   final ArrayList<String> fruitList = new ArrayList<String>();
   for (int i = 0; i < fruits.length; ++i) {
      fruitList.add(fruits[i]);
   }

  //Create Array adapter
   final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, fruitList);

  //Set adapter in list view
   listView.setAdapter(adapter);
}
  • 次に、コードをコンパイルしてアプリケーションを実行します。 _My Fruit App_のスクリーンショットは次のとおりです。

コードをコンパイル

  • 次に、_ExampleInstrumentedTest.java_ファイルを開き、以下に指定されているように_ActivityTestRule_を追加します。
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class);

また、テスト構成が_app/build.gradle_で行われていることを確認してください-

dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • 新しいテストケースを追加して、以下のようにリストビューをテストします。
@Test
public void listView_isCorrect() {
  //check list view is visible
   onView(withId(R.id.listView)).check(matches(isDisplayed()));
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click());
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
      .check(matches(withText("Apple")));
  //click a child item
   onData(allOf())
      .inAdapterView(withId(R.id.listView))
      .atPosition(10)
      .perform(click());
}
  • 最後に、Android Studioのコンテキストメニューを使用してテストケースを実行し、すべてのテストケースが成功するかどうかを確認します。

エスプレッソテストフレームワーク-WebView

_WebView_は、アプリケーション内にWebページを表示するためにAndroidが提供する特別なビューです。 _WebView_は、chromeやfirefoxなどの本格的なブラウザアプリケーションのすべての機能を提供するわけではありません。 ただし、表示されるコンテンツを完全に制御し、Webページ内で呼び出されるすべてのAndroid機能を公開します。 _WebView_を有効にし、HTMLテクノロジーとカメラや連絡先のダイヤルなどのネイティブ機能を使用してUIを簡単に設計できる特別な環境を提供します。 この機能セットにより、_WebView_は_Hybrid application_と呼ばれる新しい種類のアプリケーションを提供できます。UIはHTMLで実行され、ビジネスロジックは_JavaScript_または外部APIエンドポイントを介して実行されます。

通常、_WebView_のテストは、ネイティブユーザーインターフェース/ビューではなくユーザーインターフェース要素にHTMLテクノロジーを使用するため、挑戦する必要があります。 Espressoは、WebマッチャーとWebアサーションに新しいセットを提供することで、この分野で優れています。これは、ネイティブビューマッチャーとビューアサーションに意図的に似ています。 同時に、Webテクノロジーベースのテスト環境も含めることにより、バランスの取れたアプローチを提供します。

Espresso Webは、Web要素の検索と操作に使用される_WebDriver_ Atomフレームワークに基づいて構築されています。 Atom_はビューアクションに似ています。 AtomはWebページ内ですべての対話を行います。 _WebDriver_は、_findElement()、_ getElement()_などの事前定義されたメソッドのセットを公開して、Web要素を検索し、対応するアトムを返します(Webページでアクションを実行します)。

標準のウェブテストステートメントは次のコードのようになります。

onWebView()
   .withElement(Atom)
   .perform(Atom)
   .check(WebAssertion)

ここに、

  • onWebView()-onView()と同様に、WebViewをテストするためのAPIセットを公開します。
  • withElement()-Atomを使用してWebページ内のWeb要素を特定し、ViewInteractionに似たWebInterationオブジェクトを返すために使用されるいくつかのメソッドの1つ。
  • perform()-Atomを使用してWebページ内でアクションを実行し、WebInteractionを返します。
  • check()-これはWebAssertionを使用して必要なアサーションを行います。

サンプルのWebテストコードは次のとおりです。

onWebView()
   .withElement(findElement(Locator.ID, "apple"))
   .check(webMatches(getText(), containsString("Apple")))

ここに、

  • _findElement()_要素を特定し、Atomを返します
  • _webMatches_はmatchesメソッドに似ています

サンプルアプリケーションを作成する

WebViewに基づいた簡単なアプリケーションを作成し、_onWebView()_メソッドを使用してテストケースを作成しましょう。 次の手順に従って、サンプルアプリケーションを作成します-

  • Androidスタジオを起動します。
  • 前述のように新しいプロジェクトを作成し、_MyWebViewApp_という名前を付けます。
  • RefactorMigrate to _AndroidX_オプションメニューを使用して、アプリケーションをAndroidXフレームワークに移行します。
  • _AndroidManifest.xml_ファイルに以下の構成オプションを追加して、インターネットへのアクセス許可を付与します。
<uses-permission android:name = "android.permission.INTERNET"/>
  • Espresso Webは、個別のプラグインとして提供されます。 そのため、app/build.gradleに依存関係を追加して同期します。
dependencies {
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.1'
}
  • メインアクティビティのデフォルトデザインを削除し、WebViewを追加します。 activity_main.xmlの内容は次のとおりです。
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <WebView
      android:id = "@+id/web_view_test"
      android:layout_width = "fill_parent"
      android:layout_height = "fill_parent"/>
</RelativeLayout>
  • _WebViewClient_を拡張する新しいクラス_ExtendedWebViewClient_を作成し、_shouldOverrideUrlLoading_メソッドをオーバーライドして、同じ_WebView_にリンクアクションをロードします。そうでない場合は、アプリケーションの外部で新しいブラウザウィンドウが開きます。 _MainActivity.java_に配置します。
private class ExtendedWebViewClient extends WebViewClient {
   @Override
   public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
   }
}
  • 次に、_MainActivity_のonCreateメソッドに以下のコードを追加します。 コードの目的は、_WebView_を見つけて適切に構成し、最後にターゲットURLをロードすることです。
//Find web view
WebView webView = (WebView) findViewById(R.id.web_view_test);

//set web view client
webView.setWebViewClient(new ExtendedWebViewClient());

//Clear cache
webView.clearCache(true);

//load Url
webView.loadUrl("http://<your domain or IP>/indexl");

ここに、

  • _indexl_の内容は次のとおりです-
<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   <body>
      <h1>Fruits</h1>
      <ol>
         <li><a href = "applel" id = "apple">Apple</a></li>
         <li><a href = "bananal" id = "banana">Banana</a></li>
         </ol>
   </body>
</html>
  • _indexl_で参照される_applel_ファイルの内容は次のとおりです-
<html>
   <head>
      <title>Android Web View Sample</title>
   </head>

   <body>
      <h1>Apple</h1>
   </body>
</html>
  • _bananal_で参照される_bananal_ファイルの内容は次のとおりです。
<html>
   <head>
      <title>Android Web View Sample</title>
   </head>

   <body>
      <h1>Banana</h1>
   </body>
</html>
  • indexl、applel、およびbananalをWebサーバーに配置する
  • loadUrlメソッドのURLを構成済みのURLに置き換えます。
  • 次に、アプリケーションを実行し、すべてが正常かどうかを手動で確認します。 以下は、_WebViewサンプルアプリケーションのスクリーンショットです-

WebViewサンプル

  • 今、_ExampleInstrumentedTest.java_ファイルを開き、以下のルールを追加します-
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class, false, true) {
   @Override
   protected void afterActivityLaunched() {
      onWebView(withId(R.id.web_view_test)).forceJavascriptEnabled();
   }
};

ここでは、_WebView_を見つけ、_WebView_のJavaScriptを有効にしました。これは、エスプレッソWebテストフレームワークがJavaScriptエンジンを介して排他的に動作し、Web要素を識別および操作するためです。

  • 次に、テストケースを追加して、_WebView_とその動作をテストします。
@Test
public void webViewTest(){
   onWebView()
      .withElement(findElement(Locator.ID, "apple"))
      .check(webMatches(getText(), containsString("Apple")))
      .perform(webClick())
      .withElement(findElement(Locator.TAG_NAME, "h1"))
      .check(webMatches(getText(), containsString("Apple")));
}

ここでは、テストは次の順序で行われました。

  • _findElement()_メソッドと_Locator.ID_列挙を介してid属性を使用して、リンクを見つけました。
  • _webMatches()_メソッドを使用してリンクのテキストをチェックします
  • リンクに対してクリックアクションを実行します。 _applel_ページを開きます。
  • findElement()メソッドと_Locator.TAG_NAME_列挙を使用して、再びh1要素を見つけました。
  • 最後に、_webMatches()_メソッドを使用して_h1_タグのテキストを再度チェックします。
  • 最後に、Android Studioのコンテキストメニューを使用してテストケースを実行します。

非同期操作

この章では、Espresso Idling Resourcesを使用して非同期操作をテストする方法を学習します。

最新のアプリケーションの課題の1つは、スムーズなユーザーエクスペリエンスを提供することです。 スムーズなユーザーエクスペリエンスを提供するには、バックグラウンドで多くの作業を行い、アプリケーションプロセスが数ミリ秒を超えないようにします。 バックグラウンドタスクの範囲は、単純なものから、リモートAPI/データベースからデータを取得する高価で複雑なタスクまであります。 過去の課題に直面するために、開発者は、バックグラウンドスレッドでコストのかかる実行時間の長いタスクを記述し、バックグラウンドスレッドが完了するとメインの_UIThread_と同期していました。

マルチスレッドアプリケーションの開発が複雑な場合、テストケースの作成はさらに複雑になります。 たとえば、データベースから必要なデータをロードする前に、_AdapterView_をテストしないでください。 データのフェッチが別のスレッドで実行される場合、テストはスレッドが完了するまで待機する必要があります。 そのため、テスト環境はバックグラウンドスレッドとUIスレッド間で同期する必要があります。 Espressoは、マルチスレッドアプリケーションのテストに優れたサポートを提供します。 アプリケーションは次の方法でスレッドを使用し、エスプレッソはすべてのシナリオをサポートします。

ユーザーインターフェイスのスレッド化

Android SDKによって内部的に使用され、複雑なUI要素でスムーズなユーザーエクスペリエンスを提供します。 Espressoはこのシナリオを透過的にサポートし、構成や特別なコーディングは必要ありません。

非同期タスク

最新のプログラミング言語は、非同期プログラミングをサポートしており、スレッドプログラミングの複雑さを伴わずに軽量のスレッドを実行します。 非同期タスクは、espressoフレームワークによって透過的にサポートされます。

ユーザースレッド

開発者は、データベースから複雑なデータや大きなデータを取得するために新しいスレッドを開始する場合があります。 このシナリオをサポートするために、espressoはアイドリングリソースの概念を提供します。

この章では、アイドリングリソースの概念とその使用方法を学びましょう。

概要

アイドリングリソースの概念は非常にシンプルで直感的です。 基本的な考え方は、長時間実行されるプロセスが別のスレッドで開始されるたびに変数(ブール値)を作成して、プロセスが実行されているかどうかを識別し、テスト環境に登録することです。 テスト中に、テストランナーは登録された変数があればそれをチェックし、実行ステータスを見つけます。 実行ステータスがtrueの場合、テストランナーはステータスがfalseになるまで待機します。

Espressoは、実行ステータスを維持するためにIdlingResourcesインターフェイスを提供します。 実装する主なメソッドはisIdleNow()です。 isIdleNow()がtrueを返す場合、espressoはテストプロセスを再開するか、isIdleNow()がfalseを返すまで待機します。 IdlingResourcesを実装し、派生クラスを使用する必要があります。 Espressoは、組み込みのIdlingResources実装の一部も提供して、作業負荷を軽減します。 それらは次のとおりです。

CountingIdlingResource

これにより、実行中のタスクの内部カウンターが維持されます。 _increment()_および_decrement()_メソッドを公開します。 _increment()_はカウンターに1を追加し、_decrement()_はカウンターから1を削除します。 _isIdleNow()_は、アクティブなタスクがない場合にのみtrueを返します。

UriIdlingResource

これは_CounintIdlingResource_に似ていますが、ネットワークの待ち時間も取得するために、カウンターを長期間ゼロにする必要があります。

IdlingThreadPoolExecutor

これは、現在のスレッドプールでアクティブな実行中のタスクの数を維持するための_ThreadPoolExecutor_のカスタム実装です。

IdlingScheduledThreadPoolExecutor

これは_IdlingThreadPoolExecutor_に似ていますが、タスクとScheduledThreadPoolExecutorのカスタム実装もスケジュールします。

_IdlingResources_の上記の実装のいずれかまたはカスタム実装がアプリケーションで使用されている場合、以下のように_IdlingRegistry_クラスを使用してアプリケーションをテストする前に、テスト環境に登録する必要があります。

IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());

また、以下のようにテストが完了したら削除することができます-

IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());

Espressoはこの機能を個別のパッケージで提供します。パッケージはapp.gradleで以下のように構成する必要があります。

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}

サンプルアプリケーション

別のスレッドでWebサービスから取得して果物を一覧表示する簡単なアプリケーションを作成し、アイドルリソースの概念を使用してテストします。

  • Androidスタジオを起動します。
  • 前述のように新しいプロジェクトを作成し、MyIdlingFruitAppという名前を付けます
  • Refactor→_AndroidX_への移行オプションメニューを使用して、アプリケーションをAndroidXフレームワークに_移行_します。
  • 以下に指定されているように、_app/build.gradle_にエスプレッソアイドリングリソースライブラリを追加(および同期)します。
dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • メインアクティビティのデフォルトデザインを削除し、ListViewを追加します。 _activity_main.xml_の内容は次のとおりです。
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"/>
</RelativeLayout>
  • 新しいレイアウトリソース_item.xml_を追加して、リストビューのアイテムテンプレートを指定します。 _item.xml_の内容は次のとおりです。
<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • 新しいクラスを作成します– MyIdlingResource。 _MyIdlingResource_は、IdlingResourceを1か所に保持し、必要なときにいつでも取得するために使用されます。 この例では、_CountingIdlingResource_を使用します。
package com.finddevguides.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;

public class MyIdlingResource {
   private static CountingIdlingResource mCountingIdlingResource =
      new CountingIdlingResource("my_idling_resource");
   public static void increment() {
      mCountingIdlingResource.increment();
   }
   public static void decrement() {
      mCountingIdlingResource.decrement();
   }
   public static IdlingResource getIdlingResource() {
      return mCountingIdlingResource;
   }
}
  • 以下のように、_MainActivity_クラスで_CountingIdlingResource_型のグローバル変数_mIdlingResource_を宣言します。
@Nullable
private CountingIdlingResource mIdlingResource = null;
  • 以下のように、Webからフルーツリストを取得するプライベートメソッドを記述します。
private ArrayList<String> getFruitList(String data) {
   ArrayList<String> fruits = new ArrayList<String>();
   try {
     //Get url from async task and set it into a local variable
      URL url = new URL(data);
      Log.e("URL", url.toString());

     //Create new HTTP connection
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();

     //Set HTTP connection method as "Get"
      conn.setRequestMethod("GET");

     //Do a http request and get the response code
      int responseCode = conn.getResponseCode();

     //check the response code and if success, get response content
      if (responseCode == HttpURLConnection.HTTP_OK) {
         BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
         String line;
         StringBuffer response = new StringBuffer();
         while ((line = in.readLine()) != null) {
            response.append(line);
         }
         in.close();
         JSONArray jsonArray = new JSONArray(response.toString());
         Log.e("HTTPResponse", response.toString());
         for(int i = 0; i < jsonArray.length(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String name = String.valueOf(jsonObject.getString("name"));
            fruits.add(name);
         }
      } else {
         throw new IOException("Unable to fetch data from url");
      }
      conn.disconnect();
   } catch (IOException | JSONException e) {
      e.printStackTrace();
   }
   return fruits;
}
  • _getFruitList_メソッドを使用して_onCreate()_メソッドで新しいタスクを作成し、Webからデータを取得してから、新しいアダプターを作成してリストビューに設定します。 また、スレッド内で作業が完了したら、アイドルリソースを減らします。 コードは次のとおりです。
//Get data
class FruitTask implements Runnable {
   ListView listView;
   CountingIdlingResource idlingResource;
   FruitTask(CountingIdlingResource idlingRes, ListView listView) {
      this.listView = listView;
      this.idlingResource = idlingRes;
   }
   public void run() {
     //code to do the HTTP request
      final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
      try {
         synchronized (this){
            runOnUiThread(new Runnable() {
               @Override
               public void run() {
                 //Create adapter and set it to list view
                  final ArrayAdapter adapter = new
                     ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
                  ListView listView = (ListView)findViewById(R.id.listView);
                  listView.setAdapter(adapter);
               }
            });
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
         MyIdlingResource.decrement();//Set app as idle.
      }
   }
}

ここでは、フルーツのURLは_http://<ドメインまたはIP/fruits.json_と見なされ、JSONとしてフォーマットされます。 内容は以下の通りです、

[
   {
      "name":"Apple"
   },
   {
      "name":"Banana"
   },
   {
      "name":"Cherry"
   },
   {
      "name":"Dates"
   },
   {
      "name":"Elderberry"
   },
   {
      "name":"Fig"
   },
   {
      "name":"Grapes"
   },
   {
      "name":"Grapefruit"
   },
   {
      "name":"Guava"
   },
   {
      "name":"Jack fruit"
   },
   {
      "name":"Lemon"
   },
   {
      "name":"Mango"
   },
   {
      "name":"Orange"
   },
   {
      "name":"Papaya"
   },
   {
      "name":"Pears"
   },
   {
      "name":"Peaches"
   },
   {
      "name":"Pineapple"
   },
   {
      "name":"Plums"
   },
   {
      "name":"Raspberry"
   },
   {
      "name":"Strawberry"
   },
   {
      "name":"Watermelon"
   }
]

-ファイルをローカルWebサーバーに配置して使用します。

  • 次に、ビューを見つけ、_FruitTask_を渡して新しいスレッドを作成し、アイドルリソースをインクリメントして、最後にタスクを開始します。
//Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
  • _MainActivity_の完全なコードは次のとおりです。
package com.finddevguides.espressosamples.myidlingfruitapp;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.test.espresso.idling.CountingIdlingResource;

import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
   @Nullable
   private CountingIdlingResource mIdlingResource = null;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

     //Get data
      class FruitTask implements Runnable {
         ListView listView;
         CountingIdlingResource idlingResource;
         FruitTask(CountingIdlingResource idlingRes, ListView listView) {
            this.listView = listView;
            this.idlingResource = idlingRes;
         }
         public void run() {
           //code to do the HTTP request
            final ArrayList<String> fruitList = getFruitList(
               "http://<yourdomain or IP>/fruits.json");
            try {
               synchronized (this){
                  runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                       //Create adapter and set it to list view
                        final ArrayAdapter adapter = new ArrayAdapter(
                           MainActivity.this, R.layout.item, fruitList);
                        ListView listView = (ListView) findViewById(R.id.listView);
                        listView.setAdapter(adapter);
                     }
                  });
               }
            } catch (Exception e) {
               e.printStackTrace();
            }
            if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
               MyIdlingResource.decrement();//Set app as idle.
            }
         }
      }
     //Find list view
      ListView listView = (ListView) findViewById(R.id.listView);
      Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
      MyIdlingResource.increment();
      fruitTask.start();
   }
   private ArrayList<String> getFruitList(String data) {
      ArrayList<String> fruits = new ArrayList<String>();
      try {
        //Get url from async task and set it into a local variable
         URL url = new URL(data);
         Log.e("URL", url.toString());

        //Create new HTTP connection
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        //Set HTTP connection method as "Get"
         conn.setRequestMethod("GET");

        //Do a http request and get the response code
         int responseCode = conn.getResponseCode();

        //check the response code and if success, get response content
         if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuffer response = new StringBuffer();
            while ((line = in.readLine()) != null) {
               response.append(line);
            }
            in.close();
            JSONArray jsonArray = new JSONArray(response.toString());
            Log.e("HTTPResponse", response.toString());

            for(int i = 0; i < jsonArray.length(); i++) {
               JSONObject jsonObject = jsonArray.getJSONObject(i);
               String name = String.valueOf(jsonObject.getString("name"));
               fruits.add(name);
            }
         } else {
            throw new IOException("Unable to fetch data from url");
         }
         conn.disconnect();
      } catch (IOException | JSONException e) {
         e.printStackTrace();
      }
      return fruits;
   }
}
  • 次に、アプリケーションマニフェストファイル_AndroidManifest.xml_に以下の構成を追加します
<uses-permission android:name = "android.permission.INTERNET"/>
  • 次に、上記のコードをコンパイルして、アプリケーションを実行します。 _My Idling Fruit App_のスクリーンショットは次のとおりです。

アイドリングフルーツアプリ

  • 次に、_ExampleInstrumentedTest.java_ファイルを開き、以下に指定されているようにActivityTestRuleを追加します。
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class);
Also, make sure the test configuration is done in app/build.gradle
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • 新しいテストケースを追加して、以下のようにリストビューをテストします。
@Before
public void registerIdlingResource() {
   IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
}
@Test
public void contentTest() {
  //click a child item
   onData(allOf())
   .inAdapterView(withId(R.id.listView))
   .atPosition(10)
   .perform(click());
}
@After
public void unregisterIdlingResource() {
   IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
}
  • 最後に、Android Studioのコンテキストメニューを使用してテストケースを実行し、すべてのテストケースが成功するかどうかを確認します。

エスプレッソテストフレームワーク-インテント

Android Intentは、内部(製品リスト画面から製品詳細画面を開く)または外部(ダイヤラーを開いて電話をかけるなど)の新しいアクティビティを開くために使用されます。 内部インテントアクティビティは、エスプレッソテストフレームワークによって透過的に処理され、ユーザー側からの特定の作業は必要ありません。 ただし、外部アクティビティを呼び出すことは、テスト対象のアプリケーションであるスコープから外れるため、本当に困難です。 ユーザーが外部アプリケーションを呼び出してテスト対象のアプリケーションを終了すると、ユーザーが定義済みのアクションシーケンスでアプリケーションに戻る可能性はかなり低くなります。 したがって、アプリケーションをテストする前にユーザーアクションを想定する必要があります。 Espressoには、この状況を処理するための2つのオプションがあります。 それらは次のとおりです。

意図されました

これにより、ユーザーはテスト対象のアプリケーションから正しいインテントが開かれていることを確認できます。

意図する

これにより、ユーザーは、カメラから写真を撮る、連絡先リストから番号をダイヤルするなどの外部アクティビティをモックし、定義済みの値セット(実際の画像の代わりにカメラから定義済みの画像など)でアプリケーションに戻ることができます。

セットアップ

Espressoはプラグインライブラリを介してインテントオプションをサポートしており、ライブラリはアプリケーションのgradleファイルで設定する必要があります。 設定オプションは次のとおりです。

dependencies {
  //...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}

意図されました()

Espressoインテントプラグインは、呼び出されたインテントが予期されたインテントであるかどうかを確認する特別なマッチャーを提供します。 提供されるマッチャーとマッチャーの目的は次のとおりです。

hasAction

これはインテントアクションを受け入れ、指定されたインテントに一致するマッチャーを返します。

hasData

これはデータを受け入れ、マッチャーを返します。マッチャーは呼び出し中にインテントに提供されたデータと一致します。

toPackage

これは、インテントパッケージ名を受け入れ、呼び出されたインテントのパッケージ名と一致するマッチャーを返します。

ここで、新しいアプリケーションを作成し、_intended()_を使用して外部アクティビティのアプリケーションをテストして、概念を理解しましょう。

  • Androidスタジオを起動します。
  • 前述のように新しいプロジェクトを作成し、IntentSampleAppという名前を付けます。
  • Refactor→Migrate to _AndroidX_オプションメニューを使用して、アプリケーションをAndroidXフレームワークに移行します。
  • 以下に示すように、_activity_main.xml_を変更して、テキストボックス、連絡先リストを開くボタン、およびコールをダイヤルするボタンを作成します。
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <EditText
      android:id = "@+id/edit_text_phone_number"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:text = ""
      android:autofillHints = "@string/phone_number"/>
   <Button
      android:id = "@+id/call_contact_button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/edit_text_phone_number"
      android:text = "@string/call_contact"/>
   <Button
      android:id = "@+id/button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/call_contact_button"
      android:text = "@string/call"/>
</RelativeLayout>
  • また、_strings.xml_リソースファイルに以下の項目を追加し、
<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • 次に、onCreate_メソッドの下のメインアクティビティ(_MainActivity.java)に以下のコードを追加します。
public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     //... code
     //Find call from contact button
      Button contactButton = (Button) findViewById(R.id.call_contact_button);
      contactButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
           //Uri uri = Uri.parse("content://contacts");
            Intent contactIntent = new Intent(Intent.ACTION_PICK,
               ContactsContract.Contacts.CONTENT_URI);
            contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
            startActivityForResult(contactIntent, REQUEST_CODE);
         }
      });
     //Find edit view
      final EditText phoneNumberEditView = (EditText)
         findViewById(R.id.edit_text_phone_number);
     //Find call button
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            if(phoneNumberEditView.getText() != null) {
               Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
               Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
               startActivity(callIntent);
            }
         }
      });
   }
  //... code
}

ここでは、連絡先リストを開くためのID _call_contact_button_のボタンと、通話をダイヤルするためのID _button_のボタンをプログラムしました。

  • 以下に示すように、_MainActivity_クラスに静的変数_REQUEST_CODE_を追加します。
public class MainActivity extends AppCompatActivity {
  //...
   private static final int REQUEST_CODE = 1;
  //...
}
  • 次に、以下のように_MainActivity_クラスに_onActivityResult_メソッドを追加します。
public class MainActivity extends AppCompatActivity {
  //...
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
           //Bundle extras = data.getExtras();
           //String phoneNumber = extras.get("data").toString();
            Uri uri = data.getData();
            Log.e("ACT_RES", uri.toString());
            String[] projection = {
               ContactsContract.CommonDataKinds.Phone.NUMBER,
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            cursor.moveToFirst();

            int numberColumnIndex =
               cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberColumnIndex);

            int nameColumnIndex = cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            String name = cursor.getString(nameColumnIndex);
            Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);

           //Find edit view
            final EditText phoneNumberEditView = (EditText)
               findViewById(R.id.edit_text_phone_number);
            phoneNumberEditView.setText(number);
         }
      }
   };
  //...
}

ここで、_call_contact_button_ボタンを使用して連絡先リストを開き、連絡先を選択した後、ユーザーがアプリケーションに戻ると、_onActivityResult_が呼び出されます。 _onActivityResult_メソッドが呼び出されると、ユーザーが選択した連絡先を取得し、連絡先番号を見つけてテキストボックスに設定します。

  • アプリケーションを実行し、すべてが正常であることを確認します。 _Intentサンプルアプリケーション_の最終的な外観は以下のとおりです。

サンプルアプリケーション

  • 次に、アプリケーションのgradleファイルで以下に示すようにエスプレッソインテントを構成します。
dependencies {
  //...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Android Studioが提供する[今すぐ同期]メニューオプションをクリックします。 これにより、インテントテストライブラリがダウンロードされ、適切に構成されます。
  • _ExampleInstrumentedTest.java_ファイルを開き、通常使用される_AndroidTestRule_の代わりに_IntentsTestRule_を追加します。 _IntentTestRule_は、インテントテストを処理する特別なルールです。
public class ExampleInstrumentedTest {
  //... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
  //... code
}
  • 2つのローカル変数を追加して、テスト電話番号とダイヤラーパッケージ名を次のように設定します。
public class ExampleInstrumentedTest {
  //... code
   private static final String PHONE_NUMBER = "1 234-567-890";
   private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
  //... code
}
  • android studioが提供するAlt + Enterオプションを使用してインポートの問題を修正するか、以下のインポートステートメントを含めます。
import android.content.Context;
import android.content.Intent;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • 以下のテストケースを追加して、ダイヤラーが適切に呼び出されるかどうかをテストします。
public class ExampleInstrumentedTest {
  //... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
  //... code
}

ここでは、hasAction _、 hasData_、および_toPackage_マッチャーが_allOf_マッチャーとともに使用され、すべてのマッチャーが渡された場合にのみ成功します。

  • 次に、Androidスタジオのコンテンツメニューから_ExampleInstrumentedTest_を実行します。

意図する()

Espressoは、外部インテントアクションを模擬する特別なメソッド_intending()_を提供します。 _intending()_は、モックされるインテントのパッケージ名を受け入れ、以下に指定されるように、モックされたインテントへの応答方法を設定するメソッド_respondWith_を提供します。

intending(toPackage("com.android.contacts")).respondWith(result);

ここで、_respondWith()_は、_Instrumentation.ActivityResult_型のインテント結果を受け入れます。 新しいスタブインテントを作成し、以下に示すように結果を手動で設定できます。

//Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
   new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);

連絡先アプリケーションが適切に開かれているかどうかをテストする完全なコードは次のとおりです。

@Test
public void stubIntentTest() {
  //Stub intent
   Intent intent = new Intent();
   intent.setData(Uri.parse("content://com.android.contacts/data/1"));
   Instrumentation.ActivityResult result =
      new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
   intending(toPackage("com.android.contacts")).respondWith(result);

  //find the button and perform click action
   onView(withId(R.id.call_contact_button)).perform(click());

  //get context
   Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();

  //get phone number
   String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
      ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
   Cursor cursor =
      targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
      acts/data/1"), projection, null, null, null);

   cursor.moveToFirst();
   int numberColumnIndex =
      cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
   String number = cursor.getString(numberColumnIndex);

  //now, check the data
   onView(withId(R.id.edit_text_phone_number))
   .check(matches(withText(number)));
}

ここでは、新しいインテントを作成し、戻り値(インテントを呼び出すとき)を連絡先リストの最初のエントリ_content://com.android.contacts/data/1_として設定しました。 次に、_intending_メソッドを設定して、連絡先リストの代わりに新しく作成されたインテントをモックします。 パッケージ_com.android.contacts_が呼び出され、リストのデフォルトの最初のエントリが返されたときに、新しく作成されたインテントを設定して呼び出します。 次に、_click()_アクションを起動してモックインテントを開始し、モックインテントを呼び出した電話番号と連絡先リストの最初のエントリの番号が同じかどうかを最終的に確認します。

欠落しているインポートの問題がある場合は、Android Studioが提供するAlt + Enterオプションを使用してインポートの問題を修正するか、以下のインポートステートメントを含めます。

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;

テストクラスに以下のルールを追加して、連絡先リストの読み取り許可を提供します-

@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);

アプリケーションマニフェストファイル、_AndroidManifest.xml_に以下のオプションを追加します-

<uses-permission android:name = "android.permission.READ_CONTACTS"/>

次に、連絡先リストに少なくとも1つのエントリがあることを確認してから、Android Studioのコンテキストメニューを使用してテストを実行します。

複数のアプリケーションのUI

Androidは、複数のアプリケーションを含むユーザーインターフェイステストをサポートしています。 アプリケーションにメッセージを送信するためにアプリケーションからメッセージングアプリケーションに移動するオプションがあるとします。その後、アプリケーションに戻ります。 このシナリオでは、_UIオートマトラテストフレームワーク_は、アプリケーションのテストに役立ちます。 _UI automator_は、エスプレッソテストフレームワークの優れたコンパニオンと見なすことができます。 _UI automator_を選択する前に、espressoテストフレームワークの_intending()_オプションを利用できます。

セットアップ手順

Androidは、UIオートマトーを個別のプラグインとして提供します。 以下に指定されているように、_app/build.gradle_で設定する必要があります。

dependencies {
   ...
   androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

テストケース作成のワークフロー

_UI Automator_ベースのテストケースの記述方法を理解しましょう。

  • _getInstance()_メソッドを呼び出して_Instrumentation_オブジェクトを渡すことにより、_UiDevice_オブジェクトを取得します。
myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
  • _findObject()_メソッドを使用して_UiObject_オブジェクトを取得します。 このメソッドを使用する前に、_uiautomatorviewer_アプリケーションを開いて、ターゲットアプリケーションのUIコンポーネントを検査できます。ターゲットアプリケーションを理解すると、より良いテストケースを作成できるためです。
UiObject button = myDevice.findObject(new UiSelector()
   .text("Run")
   .className("android.widget.Button"));
  • _UiObject’s_メソッドを呼び出して、ユーザーインタラクションをシミュレートします。 たとえば、_setText()_はテキストフィールドを編集し、_click()_はボタンのクリックイベントを発生させます。
if(button.exists() && button.isEnabled()) {
   button.click();
}
  • 最後に、UIが期待される状態を反映しているかどうかを確認します。

エスプレッソテストフレームワーク-テストレコーダー

テストケースを書くのは退屈な仕事です。 エスプレッソは非常に簡単で柔軟なAPIを提供しますが、テストケースの作成は怠zyで時間のかかるタスクです。 これを克服するために、Androidスタジオはエスプレッソテストケースを記録および生成する機能を提供します。 _Record Espresso Test_は、_Run_メニューで使用できます。

以下の手順に従って、_HelloWorldApp_に簡単なテストケースを記録しましょう。

  • Androidスタジオを開き、_HelloWorldApp_アプリケーションを開きます。
  • RunRecord Espresso testをクリックして、_MainActivity_を選択します。
  • _Recorder_スクリーンショットは次のとおりです。

レコーダーのスクリーンショット

  • [アサーションの追加]をクリックします。 以下に示すように、アプリケーション画面が開きます。

表示画面

  • [Hello World!]をクリックします。 _Recorder_画面から_Select text view_へは、次のとおりです。

レコーダー画面

  • もう一度[アサーションの保存]をクリックします。これにより、アサーションが保存され、次のように表示されます。

アサーション

  • [OK]をクリックします。 新しいウィンドウが開き、テストケースの名前が尋ねられます。 デフォルト名は_MainActivityTest_です
  • 必要に応じて、テストケース名を変更します。
  • もう一度、[OK]をクリックします。 これにより、記録されたテストケースを含む_MainActivityTest_ファイルが生成されます。 完全なコーディングは次のとおりです。
package com.finddevguides.espressosamples.helloworldapp;

import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.espresso.ViewInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void mainActivityTest() {
      ViewInteraction textView = onView(
         allOf(withId(R.id.textView_hello), withText("Hello World!"),
         childAtPosition(childAtPosition(withId(android.R.id.content),
         0),0),isDisplayed()));
      textView.check(matches(withText("Hello World!")));
   }
   private static Matcher<View> childAtPosition(
      final Matcher<View> parentMatcher, final int position) {
      return new TypeSafeMatcher<View>() {
         @Override
         public void describeTo(Description description) {
            description.appendText("Child at position " + position + " in parent ");
            parentMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(View view) {
            ViewParent parent = view.getParent();
            return parent instanceof ViewGroup &&
               parentMatcher.matches(parent)&& view.equals(((ViewGroup)
               parent).getChildAt(position));
         }
      };
   }
}
  • 最後に、コンテキストメニューを使用してテストを実行し、テストケースが実行されるかどうかを確認します。

エスプレッソテストフレームワーク-UIパフォーマンス

ポジティブなユーザーエクスペリエンスは、アプリケーションの成功において非常に重要な役割を果たします。 ユーザーエクスペリエンスには、美しいユーザーインターフェイスだけでなく、それらの美しいユーザーインターフェイスのレンダリング速度や、1秒あたりのフレームレートも含まれます。 優れたユーザーエクスペリエンスを提供するには、ユーザーインターフェイスを毎秒60フレームで一貫して実行する必要があります。

この章では、Androidで利用可能なUIパフォーマンスを分析するためのオプションのいくつかを学びましょう。

dumpsys

_dumpsys_は、Androidデバイスで使用可能な組み込みツールです。 システムサービスに関する現在の情報を出力します。 _dumpsys_には、特定のカテゴリに関する情報をダンプするオプションがあります。 _gfxinfo_を渡すと、提供されたパッケージのアニメーション情報が提供されます。 コマンドは次のとおりです。

> adb shell dumpsys gfxinfo <PACKAGE_NAME>

framestats

_framestats_は、dumpsysコマンドのオプションです。 _dumpsys_が_framestats_で呼び出されると、最近のフレームの詳細なフレームタイミング情報をダンプします。 コマンドは次のとおりです。

> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

情報をCSV(カンマ区切り値)として出力します。 CSV形式の出力は、データを簡単にExcelにプッシュし、その後、Excelの数式とチャートから有用な情報を抽出するのに役立ちます。

systrace

_systrace_は、Androidデバイスで利用可能なビルド内ツールでもあります。 アプリケーションプロセスの実行時間をキャプチャして表示します。 _systrace_は、Android Studioのターミナルで以下のコマンドを使用して実行できます。

python %ANDROID_HOME%/platform-tools/systrace/systrace.py --time=10 -o
my_trace_outputl gfx view res

エスプレッソテストフレームワーク-アクセシビリティ

アクセシビリティ機能は、あらゆるアプリケーションの主要な機能の1つです。 ベンダーが開発したアプリケーションは、Android SDKによって設定された最小限のアクセシビリティガイドラインをサポートし、成功した有用なアプリケーションである必要があります。 アクセシビリティ標準に従うことは非常に重要であり、簡単な作業ではありません。 Android SDKは、適切に設計されたビューを提供してアクセスしやすいユーザーインターフェイスを作成することにより、優れたサポートを提供します。

同様に、Espressoテストフレームワークは、コアテストエンジンへのアクセシビリティテスト機能を透過的にサポートすることにより、開発者とエンドユーザーの両方に大きなメリットをもたらします。

Espressoでは、開発者は_AccessibilityChecks_クラスを介してアクセシビリティテストを有効にして構成できます。 サンプルコードは次のとおりです。

AccessibilityChecks.enable();

デフォルトでは、表示アクションを実行するとアクセシビリティチェックが実行されます。 チェックには、アクションが実行されるビューとすべての子孫ビューが含まれます。 次のコードを使用して、画面のビュー階層全体を確認できます-

AccessibilityChecks.enable().setRunChecksFromRootView(true);

結論

Espressoは、テストフレームワークで通常必要とされる余分な労力をかけることなく、非常に簡単な方法でアプリケーションを完全にテストするためのAndroid開発者向けの優れたツールです。 手動でコードを記述せずにテストケースを作成するレコーダーも備えています。 さらに、すべてのタイプのユーザーインターフェイステストをサポートします。 エスプレッソテストフレームワークを使用することにより、Android開発者は、短時間で問題なく、見栄えの良いアプリケーションと成功したアプリケーションを自信を持って開発できます。