Espresso TestingFramework-クイックガイド

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

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

エスプレッソの特徴

Espressoでサポートされている主な機能は次のとおりです。

  • 非常にシンプルなAPIなので、簡単に習得できます。

  • 高度にスケーラブルで柔軟性があります。

  • AndroidWebViewコンポーネントをテストするための個別のモジュールを提供します。

  • Androidインテントを検証およびモックするための個別のモジュールを提供します。

  • アプリケーションとテスト間の自動同期を提供します。

エスプレッソの利点

エスプレッソのメリットは何ですか。

  • 下位互換性

  • セットアップが簡単。

  • 非常に安定したテストサイクル。

  • アプリケーション外のテストアクティビティもサポートします。

  • JUnit4をサポート

  • ブラックボックステストの作成に適したUIオートメーション。

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

前提条件

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

エスプレッソフレームワークでの作業を開始する前に適切に構成する必要のあるアイテムのリストは次のとおりです。

  • 最新のJavaJDKをインストールし、JAVA_HOME環境変数を構成します。

  • 最新のAndroidStudio(バージョン3.2以降)をインストールします。

  • SDKManagerを使用して最新のAndroidSDKをインストールし、ANDROID_HOME環境変数を構成します。

  • 最新のGradleビルドツールをインストールし、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クラスを設定します。最初の行依存関係が含まJUnitのテストフレームワークを、第二のライン依存性がテストケースを実行するためのテストランナーライブラリを含み、最終的に第三のライン依存性がエスプレッソテストフレームワークを含みます。

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

新しいAndroidアプリケーションを作成する手順を以下に説明します-

  • AndroidStudioを起動します。

  • 「ファイル」→「新規」→「新規プロジェクト」を選択します。

  • 入力したアプリケーション名(がHelloWorldApp)と会社のドメイン(espressosamples.tutorialspoint.com)をしてからクリックし、次へを

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

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

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

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

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

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

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

  • 新しいプロジェクトが作成されたら、app /build.gradleファイルを開いてその内容を確認します。ファイルの内容は以下のとおりです。

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.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サポートライブラリが構成されています。メニューの[リファクタリング] → [ AndroidXへの移行]をクリックして、AndroidXライブラリを使用するようにアプリケーションを再構成できます。

Androidxに移行するには、

  • これで、app /build.gradleが以下のように変更されます。

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.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デバイスでアニメーションを無効にする方法を見てみましょう–([設定]→[開発者向けオプション)]、

  • ウィンドウアニメーションスケール

  • トランジションアニメーションスケール

  • アニメーターの継続時間スケール

場合は開発者向けオプションのメニューがで使用できない設定画面、クリックビルド番号は内部で利用可能な電話についてオプションの数倍。これにより、開発者向けオプションメニューが有効になります。

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

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

  • 機能/ユニットテスト

  • 計装テスト

機能テストでは、実際のAndroidアプリケーションをデバイスまたはエミュレーターにインストールして起動し、機能をテストする必要はありません。実際のアプリケーションを呼び出さなくても、コンソール自体で起動できます。ただし、インストルメンテーションテストでは、ユーザーインターフェイスやユーザー操作などの機能をテストするために、実際のアプリケーションを起動する必要があります。デフォルトでは、ユニットテストはsrc/test/java/ フォルダとインストルメンテーションテストはに書かれています src/androidTest/java/フォルダ。Android Studioには、選択したテストクラスで記述されたテストを実行するためのテストクラスの実行コンテキストメニューが用意されています。-デフォルトでは、Androidアプリケーションの2つのクラスがあるExampleUnitTestのsrc /テスト用フォルダとExampleInstrumentedTestをのsrc / androidTestフォルダ。

デフォルトの単体テストを実行するには、Android StudioExampleUnitTestを選択し、それを右クリックして、次に示すように[実行] 'ExampleUnitTest'をクリックします

ユニットテストを実行する

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

ユニットテストの成功

デフォルトのインストルメンテーションテストを実行するには、Android StudioでExampleInstrumentationTestを選択し、それを右クリックして、次に示すように[実行] 'ExampleInstrumentationTest'をクリックします。

計装テストを実行する

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

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

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

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

簡単な単体テストを書く

私たちは、Javaクラス、作成してみましょう計算(Computation.java)を、簡単な数学的な操作、書き込み総和乗算を。次に、JUnitを使用してテストケースを作成し、テストケースを実行してチェックします。

  • AndroidStudioを起動します。

  • 前の章で作成したHelloWorldAppを開きます。

  • ファイル、作成Computation.javaをしてアプリ/ srcに/メイン/ javaの/ COM / tutorialspoint / espressosamples /がHelloWorldApp / -および書き込み二つの関数乗算下記指定されているように、

package com.tutorialspoint.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 / tutorialspoint / espressosamples / helloworldappにComputationUnitTest.javaというファイルを作成し、以下に指定するように、SumおよびMultiply機能をテストするための単体テストケースを記述します。

package com.tutorialspoint.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));
   }
}

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

  • さて、実行ComputationUnitTestをクラスを右クリックし、[ファイル名を指定して実行呼び出すことにより、Androidのスタジオで「ComputationUnitTest」前の章で説明したようにオプションを選択します。これにより、単体テストケースが実行され、成功が報告されます。

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

注釈

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

  • @Test

  • @Before

  • @After

  • @BeforeClass

  • @AfterClass

  • @Rule

@Testアノテーション

@Testは、JUnitフレームワークで非常に重要なアノテーションです。@Testは、通常のメソッドとテストケースメソッドを区別するために使用されます。メソッドが@Testアノテーションで装飾されると、その特定のメソッドはテストケースと見なされ、JUnitRunnerによって実行されますJUnit Runnerは特別なクラスであり、Javaクラス内で使用可能なJUnitテストケースを見つけて実行するために使用されます。今のところ、Android Studioの組み込みオプションを使用して単体テストを実行しています(これにより、JUnitランナーが実行されます)。サンプルコードは次のとおりです。

package com.tutorialspoint.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アノテーションは、特定のテストクラスで使用可能なテストメソッドを実行する前に呼び出す必要があるメソッドを参照するために使用されます。このサンプルでは例えば、計算のオブジェクトは、別の方法で作成することができるとの注釈が付け@Beforeそれは両方の前に実行されるようにsum_isCorrectmultiply_isCorrectテストケース。完全なコードは次のとおりです。

package com.tutorialspoint.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

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

package com.tutorialspoint.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回作成できます。

package com.tutorialspoint.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アノテーションが付けられたメソッドは、特定のクラスのすべてのテストケースが実行された後に一度だけ呼び出されるか実行されます。このメソッドも、正しく機能するために静的である必要があります。サンプルコードは次のとおりです-

package com.tutorialspoint.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フレームワークによって提供されるTemporaryFolder(タイプTestRule)クラスを使用して、すべてのテストケース用の一時フォルダーを作成できます。一時フォルダーは、テストケースの実行時に削除されます。TemporaryFolder型の新しい変数を作成し、以下に指定するように@Ruleでアノテーションを付ける必要があります。

package com.tutorialspoint.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

  • @Rule

  • @Before

  • @Test

  • @After

  • @AfterClass

アサーション

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

  • fail() −テストケースを明示的に失敗させる。

  • assertTrue(boolean test_condition) −test_conditionがtrueであることを確認します

  • 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パッケージ内)を提供します。EspressoのメソッドonViewは、Matcherタイプ(ビューマッチャー)の単一の引数を取り、対応するUIビューを検索し、対応するViewInteractionオブジェクトを返します。ViewInteractionによって返されるオブジェクトonViewの方法は、さらにマッチしたビューをクリックしのようなアクションを呼び出すために使用することができたりマッチしたビューをアサートするために使用することができます。「HelloWorld!」というテキストでビューを見つけるためのサンプルコード。以下のとおりであります、

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

ここで、withTextはマッチャーであり、「HelloWorld!」というテキストを持つUIビューを照合するために使用できます。

ViewActions

Espressoは、選択された/一致したビューでさまざまなアクションを呼び出すために、多数のビューアクションクラス(androidx.test.espresso.action.ViewActions内)を提供します。いったんonViewマッチを返すViewInteractionのオブジェクト、任意のアクションを「実行」する方法で呼び出すことによって呼び出すことができるViewInteractionのオブジェクトと適切なビューのアクションでそれを渡します。一致したビューをクリックするためのサンプルコードは次のとおりです。

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)));

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

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

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

  • 以前に学習したように、AndroidJUnitランナーであるAndroidJUnit4はAndroidテストケースを実行します。エスプレッソテストケースは、@ RunWith(AndroidJUnut.class)でマークする必要があります。まず、AndroidJUnit4はテストケースを実行するための環境を準備します。接続されたAndroidデバイスまたはエミュレーターのいずれかを起動し、アプリケーションをインストールして、テストするアプリケーションが準備完了状態にあることを確認します。テストケースを実行し、結果を報告します。

  • Espressoは、アクティビティを指定するために、ActivityTestRuleタイプのJUnitルールを少なくとも1つ必要とします。Android JUnitランナーは、ActivityTestRuleを使用して起動されるアクティビティを開始します。

  • すべてのテストケースは、単一の最小必要onView又はonDate(データを等ベースのビューを見つけるために使用AdapterView一致し、所望のビューを見つけるためのメソッド呼出しを)。onViewまたはonDataは、ViewInteractionオブジェクトを返します。

  • いったんViewInteractionのオブジェクトが返され、我々は、いずれかの選択したビューのアクションを呼び出すか、アサーションを使用して、当社の予想ビューのビューを確認することができます。

  • アクションは、使用可能なビューアクションのいずれかを渡すことにより、ViewInteractionオブジェクトのperformメソッドを使用して呼び出すことができます。

  • アサーションは、使用可能なビューアサーションのいずれかを渡すことにより、ViewInteractionオブジェクトのcheckメソッドを使用して呼び出すことができます。

ワークフローの図表現は次のとおりです。

例-ビューアサーション

「HelloWorld!」を含むテキストビューを見つけるための簡単なテストケースを書いてみましょう。「HelloWorldApp」アプリケーションでテキストを入力し、ビューアサーションを使用してそれをアサートします。完全なコードは次のとおりです。

package com.tutorialspoint.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.tutorialspoint.espressosamples.helloworldapp", appContext.getPackageName());
   }
}

ここでは、withTextビューマッチャーを使用して、「HelloWorld!」を含むテキストビューを見つけました。テキストおよび一致ビューアサーションは、テキストビューが正しく表示されていることを表明します。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 −任意の数のマッチャーを受け入れ、いずれかのマッチャーが成功した場合は一致します。

  • not − 1つのマッチャーを受け入れ、マッチャーが失敗した場合にのみ一致し、その逆も同様です。

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

  • equalToIgnoringCase −実際の入力が大文字と小文字を無視して予想される文字列と等しいかどうかをテストするために使用されます。

  • equalToIgnoringWhiteSpace −大文字と小文字および空白を無視して、実際の入力が指定された文字列と等しいかどうかをテストするために使用されます。

  • containsString −実際の入力に指定された文字列が含まれているかどうかをテストするために使用されます。

  • endsWith −実際の入力が指定された文字列で始まるかどうかをテストするために使用されます。

  • startsWith −実際の入力が指定された文字列で終了するかどうかをテストするために使用されます。

数ベースのマッチャー

  • closeTo −実際の入力が期待値に近いかどうかをテストするために使用されます。

  • greaterThan −実際の入力が期待値より大きいかどうかをテストするために使用されます。

  • greaterThanOrEqualTo −実際の入力が期待値以上であるかどうかをテストするために使用されます。

  • lessThan −実際の入力が期待数より少ないかどうかをテストするために使用されます。

  • lessThanOrEqualTo −実際の入力が期待値以下であるかどうかをテストするために使用されます。

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

  • equalTo −実際の入力が期待されるオブジェクトと等しいかどうかをテストするために使用されます

  • hasToString −実際の入力にtoStringメソッドがあるかどうかをテストするために使用されます。

  • instanceOf −実際の入力が期待されるクラスのインスタンスであるかどうかをテストするために使用されます。

  • isCompatibleType −実際の入力が期待されるタイプと互換性があるかどうかをテストするために使用されます。

  • notNullValue −実際の入力がnullでないかどうかをテストするために使用されます。

  • sameInstance −実際の入力と期待値が同じインスタンスであるかどうかをテストするために使用されます。

  • hasProperty −実際の入力に期待されるプロパティがあるかどうかをテストするために使用されます

is −砂糖またはequalToのショートカット

マッチャー

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

withId()

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

onView(withId(R.id.testView))

withText()

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

onView(withText("Hello World!"))

withContentDescription()

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

onView(withContentDescription("blah"))

テキスト自体の代わりに、テキスト値のリソースIDを渡すこともできます。

onView(withContentDescription(R.id.res_id_blah))

hasContentDescription()

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

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

withTagKey()

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

onView(withTagKey("blah"))

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

onView(withTagKey(R.id.res_id_blah))

withTagValue()

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

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

ここで、ある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()には引数がありません。長いテキストがあり、楕円形(最初の..10 ..最後)または切り取られた(最初の…)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)))

supportInputMethods()

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

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の正反対です。サンプルコードは* follows、

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とテキストの両方をテストするためのマッチャーを返す新しいカスタムマッチャーを作成しましょう。

Espressoは、新しいマッチャーを作成するために次の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) {
      };
   }
}
  • オーバーライドdescribeTomatchesSafelyのメソッドBoundedMatcherのオブジェクトを。describeToには、戻り値の型のないDescription型の単一の引数があり、マッチャーに関するエラー情報に使用されます。matchSafelyには、戻り値の型がブール値のTextView型の単一の引数があり、ビューを照合するために使用されます。

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

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)は、my_viewと等しいid属性を持つビュー(実際)と一致するビューマッチャーを返します。

  • withText(“ Hello”)は、Helloと等しいテキスト属性を持つビュー(予期される)と一致するビューマッチャーも返します。

  • checkは、ViewAssertion型の引数を受け入れ、渡されたViewAssertionオブジェクトを使用してアサーションを実行するメソッドです。

  • matchs(withText(“ Hello”))は、ビューアサーションを返します。real job実際のビュー(withIdを使用して検出)と期待されるビュー(withTextを使用して検出)の両方が同一であると主張すること。

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

存在しません()

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

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

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

マッチス()

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

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はテキストビューのコンテンツを取得し、著作権をテキストとして、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型のいずれかです。それは単にショートにカットオープンリンク*方法。

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は、Adapterを使用して基になるデータソースからフェッチされた製品リストやユーザー連絡先などの同様の情報のコレクションをレンダリングするために特別に設計された特別な種類のビューです。データソースは、単純なリストから複雑なデータベースエントリまでです。由来ビューの一部AdapterViewはあるのListViewGridViewコントロールスピナー

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

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

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

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

メソッド

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を選択し、一致したAdapterViewと対話するためにDataInteractionオブジェクトを返します。

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ベースの製品リストの[購入]ボタンなどの特定のアイテムを操作できます。

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

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

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

  • AndroidStudioを起動します。

  • 前に説明したように新しいプロジェクトを作成し、MyFruitAppという名前を付けます。

  • リファクタリング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);
}
  • 次に、コードをコンパイルしてアプリケーションを実行します。MyFruitアプリのスクリーンショットは次のとおりです。

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

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

Espresso Webは、Web要素の検索と操作に使用されるWebDriverAtomフレームワークに基づいて構築されています。Atomはビューアクションに似ています。Atomは、Webページ内のすべての対話を行います。WebDriverは、findElement()getElement()などの事前定義されたメソッドのセットを公開して、Web要素を検索し、対応するアトムを返します(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()は要素を見つけて、アトムを返します

  • webMatchesはmatchesメソッドに似ています

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

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

  • AndroidStudioを起動します。

  • 前に説明したように新しいプロジェクトを作成し、MyWebViewAppという名前を付けます。

  • リファクタリングAndroidXへの移行オプションメニューを使用して、アプリケーションをAndroidXフレームワークに移行します。

  • 以下の構成オプションをAndroidManifest.xmlファイルに追加して、インターネットへのアクセスを許可します。

<uses-permission android:name = "android.permission.INTERNET" />
  • EspressoWebは別のプラグインとして提供されます。そのため、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>
  • 新しいクラスを作成し、ExtendedWebViewClientが拡張WebViewClientとオーバーライド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>/index.html");

ここに、

  • index.htmlの内容は次のとおりです-

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

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Apple</h1>
   </body>
</html>
  • コンテンツbanana.htmlのファイルはに言及banana.html次のように、あります

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Banana</h1>
   </body>
</html>
  • index.html、apple.html、banana.htmlをWebサーバーに配置します

  • loadUrlメソッドのURLを構成済みのURLに置き換えます。

  • 次に、アプリケーションを実行し、すべてが正常かどうかを手動で確認します。以下は、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のをとのJavaScriptを有効にWebViewのエスプレッソのウェブテストフレームワークを特定し、ウェブ要素を操作するために、JavaScriptエンジンが独占的に動作しますので。

  • 次に、テストケースを追加して、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属性を使用して、リンク、appleを見つけました。

  • webMatches()メソッドを使用してリンクのテキストをチェックします

  • リンクに対してクリックアクションを実行します。apple.htmlページを開きます。

  • findElement()メソッドとLocator.TAG_NAME列挙を使用して、h1要素を再度見つけました。

  • 最後に、webMatches()メソッドを使用してh1タグのテキストを再度チェックします。

  • 最後に、AndroidStudioのコンテキストメニューを使用してテストケースを実行します。

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

最新のアプリケーションの課題の1つは、スムーズなユーザーエクスペリエンスを提供することです。スムーズなユーザーエクスペリエンスを提供するには、アプリケーションプロセスに数ミリ秒以上かかることがないようにするために、バックグラウンドで多くの作業が必要になります。バックグラウンドタスクは、リモートAPI /データベースからデータをフェッチする単純なタスクからコストのかかる複雑なタスクまで多岐にわたります。過去の課題に対処するために、開発者はバックグラウンドスレッドでコストのかかる長時間実行タスクを記述し、バックグラウンドスレッドが完了するとメインのUIThreadと同期していました。

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

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

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

非同期タスク

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

ユーザースレッド

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

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

概要概要

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

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

CountingIdlingResource

これにより、実行中のタスクの内部カウンターが維持されます。これは、increment(メソッドとdecrement()メソッドを公開しますインクリメント()はカウンターに1を追加し、デクリメント()はカウンターから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サービスから取得して果物を一覧表示する簡単なアプリケーションを作成し、アイドリングリソースの概念を使用してテストしてみましょう。

  • AndroidStudioを起動します。

  • 前に説明したように新しいプロジェクトを作成し、MyIdlingFruitAppという名前を付けます

  • 移行使っ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.tutorialspoint.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;
   }
}
  • グローバル変数を宣言し、mIdlingResource型のCountingIdlingResourceMainActivityの以下のようなクラス、

@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;
}
  • onCreate()メソッドで新しいタスクを作成し、getFruitListメソッドを使用して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:// <yourdomainまたは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"
   }
]

Note −ファイルをローカル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.tutorialspoint.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 IdlingFruitアプリのスクリーンショットは次のとおりです。

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

意図されました

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

意図する

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

セットアップ

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

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

意図されました()

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

hasAction

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

hasData

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

toPackage

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

それでは、新しいアプリケーションを作成し、意図された()を使用てアプリケーションの外部アクティビティをテストして、概念を理解しましょう。

  • AndroidStudioを起動します。

  • 前に説明したように新しいプロジェクトを作成し、IntentSampleAppという名前を付けます。

  • リファクタリング→ 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のボタンをプログラムしました。

  • 静的変数の追加REQUEST_CODEMainActivityの以下に示すように、クラスを

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);
         }
      }
   };
   // ...
}

ここで、onActivityResultは、ユーザーがcall_contact_buttonボタンを使用して連絡先リストを開き、連絡先を選択した後、アプリケーションに戻ったときに呼び出されます。いったんonActivityResultメソッドが呼び出され、それはユーザーが選択した連絡先、連絡先の電話番号を検索し、テキストボックスに設定を取得します。

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

  • 次に、以下に示すように、アプリケーションのgradleファイルでエスプレッソインテントを構成します。

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • AndroidStudioが提供する[今すぐ同期]メニューオプションをクリックします。これにより、インテントテストライブラリがダウンロードされ、適切に構成されます。

  • 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
}
  • androidstudioが提供する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
}

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

  • 次に、AndroidStudioのコンテンツメニューからExampleInstrumentedTestを実行します。

意図する()

Espressoは、外部インテントアクションをモックするintentioning()という特別なメソッドを提供します。意図()は、モックされるインテントのパッケージ名を受け入れ、以下に指定されているように、モックされたインテントがどのように応答される必要があるかを設定するメソッド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として設定しました。次に、連絡先リストの代わりに新しく作成されたインテントをモックする意図メソッドを設定しました。パッケージcom.android.contactsが呼び出され、リストのデフォルトの最初のエントリが返されると、新しく作成されたインテントが設定されて呼び出されます。次に、click()アクションを実行してモックインテントを開始し、最後にモックインテントを呼び出した電話番号と連絡先リストの最初のエントリの番号が同じであるかどうかを確認します。

不足しているインポートの問題がある場合は、androidstudioが提供する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つのエントリがあることを確認してから、AndroidStudioのコンテキストメニューを使用してテストを実行します。

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

セットアップ手順

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

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

テストケースを作成するためのワークフロー

UIAutomatorベースのテストケースの書き方を理解しましょう。

  • 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のメソッドを呼び出して、ユーザーの操作をシミュレートします。たとえば、setText()を使用してテキストフィールドを編集し、click()を使用してボタンのクリックイベントを発生させます。

if(button.exists() && button.isEnabled()) {
   button.click();
}
  • 最後に、UIが期待される状態を反映しているかどうかを確認します。

テストケースを書くのは退屈な仕事です。espressoは非常に簡単で柔軟なAPIを提供しますが、テストケースの作成は怠惰で時間のかかる作業になる可能性があります。これを克服するために、AndroidStudioはエスプレッソテストケースを記録および生成する機能を提供します。Record Espresso Testは、Runメニューから利用できます。

以下に説明する手順に従って、HelloWorldAppに簡単なテストケースを記録しましょう。

  • Android Studioを開き、続いてHelloWorldAppアプリケーションを開きます。

  • [実行] → [エスプレッソテストの記録]をクリックして、[ MainActivity]を選択します。

  • レコーダー、次のようにスクリーンショットは、あります

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

  • Hello Worldをクリックしてくださいレコーダーには、画面のテキストビューを選択するには、次のように、あります

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

  • [ OK]をクリックします。新しいウィンドウが開き、テストケースの名前が尋ねられます。デフォルト名はMainActivityTestです

  • 必要に応じて、テストケース名を変更します。

  • もう一度、[ OK ]をクリックします。これにより、記録されたテストケースを含むファイルMainActivityTestが生成されます。完全なコーディングは次のとおりです。

package com.tutorialspoint.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));
         }
      };
   }
}
  • 最後に、コンテキストメニューを使用してテストを実行し、テストケースが実行されるかどうかを確認します。

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

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

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は、AndroidStudioのターミナルで以下のコマンドを使用して実行できます。

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

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

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

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

AccessibilityChecks.enable();

デフォルトでは、ユーザー補助機能のチェックは、ビューアクションを実行すると実行されます。このチェックには、アクションが実行されるビューとすべての子孫ビューが含まれます。次のコードを使用して、画面のビュー階層全体を確認できます。

AccessibilityChecks.enable().setRunChecksFromRootView(true);

結論

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