Espresso 테스트 프레임 워크-빠른 가이드

일반적으로 모바일 자동화 테스트는 어렵고 어려운 작업입니다. 다양한 기기 및 플랫폼에서 Android를 사용할 수 있으므로 모바일 자동화 테스트가 지루합니다. 이를 더 쉽게하기 위해 Google은 과제를 해결하고 Espresso 프레임 워크를 개발했습니다. Android 애플리케이션에서 사용자 인터페이스를 자동화하고 테스트 할 수있는 매우 간단하고 일관 적이며 유연한 API를 제공합니다. Espresso 테스트는 자바와 Android 애플리케이션 개발을위한 최신 프로그래밍 언어 인 Kotlin으로 작성 될 수 있습니다.

Espresso API는 간단하고 배우기 쉽습니다. 다중 스레드 테스트의 복잡성없이 Android UI 테스트를 쉽게 수행 할 수 있습니다. Google 드라이브,지도 및 기타 일부 애플리케이션은 현재 Espresso를 사용하고 있습니다.

에스프레소의 특징

Espresso에서 지원하는 몇 가지 두드러진 기능은 다음과 같습니다.

  • 매우 간단한 API이므로 배우기 쉽습니다.

  • 확장 성과 유연성이 뛰어납니다.

  • Android WebView 구성 요소를 테스트하기위한 별도의 모듈을 제공합니다.

  • Android 인 텐트를 모의하고 검증하기위한 별도의 모듈을 제공합니다.

  • 애플리케이션과 테스트 간의 자동 동기화를 제공합니다.

에스프레소의 장점

이제 Espresso의 이점이 무엇인지 살펴 보겠습니다.

  • 하위 호환성

  • 설치가 쉽습니다.

  • 매우 안정적인 테스트주기.

  • 응용 프로그램 외부에서도 테스트 활동을 지원합니다.

  • JUnit4 지원

  • 블랙 박스 테스트 작성에 적합한 UI 자동화.

이 장에서는 에스프레소 프레임 워크를 설치하고 에스프레소 테스트를 작성하도록 구성하고 Android 애플리케이션에서 실행하는 방법을 이해하겠습니다.

전제 조건

Espresso는 Android SDK를 사용하여 Java / Kotlin 언어로 개발 된 Android 애플리케이션을 테스트하기위한 사용자 인터페이스 테스트 프레임 워크입니다. 따라서 에스프레소의 유일한 요구 사항은 Java 또는 Kotlin에서 Android SDK를 사용하여 애플리케이션을 개발하는 것이며 최신 Android Studio를 사용하는 것이 좋습니다.

에스프레소 프레임 워크에서 작업을 시작하기 전에 적절하게 구성 할 항목 목록은 다음과 같습니다.

  • 최신 Java JDK를 설치하고 JAVA_HOME 환경 변수를 구성합니다.

  • 최신 Android Studio (버전 3.2 이상)를 설치합니다.

  • SDK Manager를 사용하여 최신 Android SDK를 설치하고 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'
}

testInstrumentationRunner 에서 안드로이드 / defaultConfig는 설정 AndroidJUnitRunner의 계측 테스트를 실행하는 클래스. 필드에 첫 번째 라인 종속성 포함 JUnit을 테스트 워크를 상기 두 번째 라인 의존성 테스트 케이스들을 실행하기 위해 시험 러너 라이브러리를 포함하며, 마지막에서 세 번째 라인 의존성 에스프레소 테스트 구조를 포함한다.

기본적으로 Android 스튜디오는 에스프레소 테스트 프레임 워크 (Android 지원 라이브러리)를 종속성으로 설정하고 Android 프로젝트를 생성하면 gradle이 Maven 저장소에서 필요한 라이브러리를 다운로드합니다. 간단한 Hello world Android 애플리케이션을 만들고 에스프레소 테스트 프레임 워크가 올바르게 구성되었는지 확인하겠습니다.

새 Android 애플리케이션을 만드는 단계는 아래에 설명되어 있습니다.

  • Android Studio를 시작하십시오.

  • 파일 → 새로 만들기 → 새 프로젝트를 선택합니다.

  • 입력 응용 프로그램 이름 (helloworldapp을) 및 회사 도메인 (espressosamples.tutorialspoint.com)를 누른 다음 다음을 .

Android 프로젝트를 생성하려면

  • 최소 API를 API 15 : Android 4.0.3 (IceCreamSandwich)으로 선택한 후 다음을 클릭하십시오.

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 지원 라이브러리가 구성됩니다. 메뉴에서 RefactorMigrate to 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 기기에서 애니메이션을 비활성화하는 방법을 살펴 보겠습니다 – (설정 → 개발자 옵션),

  • 창 애니메이션 배율

  • 전환 애니메이션 배율

  • 애니메이터 기간 척도

설정 화면 에서 개발자 옵션 메뉴를 사용할 수없는 경우 전화 정보 옵션 에서 사용할 있는 빌드 번호를 여러 번 클릭합니다. 개발자 옵션 메뉴를 활성화 합니다.

이 장에서는 Android 스튜디오를 사용하여 테스트를 실행하는 방법을 살펴 보겠습니다.

모든 안드로이드 애플리케이션에는 두 가지 유형의 테스트가 있습니다.

  • 기능 / 단위 테스트

  • 계측 테스트

기능 테스트에서는 실제 Android 애플리케이션을 설치하고 기기 또는 에뮬레이터에 실행하고 기능을 테스트 할 필요가 없습니다. 실제 응용 프로그램을 호출하지 않고 콘솔 자체에서 시작할 수 있습니다. 그러나 계측 테스트에서는 사용자 인터페이스 및 사용자 상호 작용과 같은 기능을 테스트하기 위해 실제 애플리케이션을 실행해야합니다. 기본적으로 단위 테스트는src/test/java/ 폴더 및 계측 테스트가 작성됩니다. src/androidTest/java/폴더. Android 스튜디오는 테스트 클래스가 선택한 테스트 클래스로 작성된 테스트를 실행할 수있는 Run 컨텍스트 메뉴를 제공합니다 . - 기본적으로 안드로이드 응용 프로그램은 두 개의 클래스가 ExampleUnitTest을SRC / 테스트 폴더 ExampleInstrumentedTestSRC / androidTest 폴더에 있습니다.

기본 단위 테스트를 실행하려면 Android 스튜디오 에서 ExampleUnitTest 를 선택 하고 마우스 오른쪽 버튼으로 클릭 한 다음 아래와 같이 Run 'ExampleUnitTest' 를 클릭합니다 .

단위 테스트 실행

그러면 단위 테스트가 실행되고 다음 스크린 샷과 같이 콘솔에 결과가 표시됩니다.

단위 테스트 성공

기본 계측 테스트를 실행하려면 Android 스튜디오에서 ExampleInstrumentationTest를 선택하고 마우스 오른쪽 단추로 클릭 한 다음 아래와 같이 Run 'ExampleInstrumentationTest'를 클릭합니다.

계측 테스트 실행

이것은 장치 또는 에뮬레이터에서 응용 프로그램을 실행하여 단위 테스트를 실행하고 다음 스크린 샷과 같이 콘솔에 결과를 표시합니다.

계측 테스트가 성공적으로 실행되었습니다.

이 장에서는 에스프레소 테스트 프레임 워크가 구축되는 Java 커뮤니티에서 개발 한 인기있는 단위 테스트 프레임 워크 인 JUnit 의 기본 사항을 이해하겠습니다 .

JUnit 은 Java 애플리케이션의 단위 테스트를위한 사실상의 표준입니다. 단위 테스트에 널리 사용되지만 계측 테스트를 완벽하게 지원하고 제공합니다. Espresso 테스트 라이브러리는 Android 기반 계측 테스트를 지원하기 위해 필요한 JUnit 클래스를 확장합니다.

간단한 단위 테스트 작성

Java 클래스 인 Computation (Computation.java)을 만들고 간단한 수학적 연산, SummationMultiplication을 작성해 보겠습니다 . 그런 다음 JUnit을 사용하여 테스트 케이스를 작성하고 테스트 케이스를 실행하여 확인합니다.

  • Android Studio를 시작하십시오.

  • 이전 장에서 만든 HelloWorldApp을 엽니 다 .

  • , 파일을 생성 Computation.java을 에서 응용 프로그램 / SRC / 메인 / 자바 / 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));
   }
}

여기에서는 두 가지 새로운 용어 인 @TestassertEquals를 사용했습니다 . 일반적으로 JUnit은 Java 어노테이션을 사용하여 클래스의 테스트 케이스를 식별하고 테스트 케이스를 실행하는 방법에 대한 정보를 제공합니다. @Test 는 특정 함수가 junit 테스트 케이스임을 지정하는 이러한 Java 주석 중 하나입니다. assertEquals 는 첫 번째 인수 (예상 값)와 두 번째 인수 (계산 된 값)가 동일하고 동일하다는 것을 주장하는 함수입니다. JUnit 은 다양한 테스트 시나리오를위한 다양한 어설 션 메소드를 제공합니다.

  • 이제 이전 장에서 설명한대로 클래스를 마우스 오른쪽 버튼으로 클릭하고 'ComputationUnitTest' 실행 옵션을 호출하여 Android 스튜디오에서 ComputationUnitTest 를 실행합니다 . 이것은 단위 테스트 케이스를 실행하고 성공을보고합니다.

계산 단위 테스트 결과는 다음과 같습니다.

주석

JUnit 프레임 워크는 주석을 광범위하게 사용합니다 . 중요한 주석 중 일부는 다음과 같습니다.

  • @Test

  • @Before

  • @After

  • @BeforeClass

  • @AfterClass

  • @Rule

@Test 주석

@TestJUnit 프레임 워크 에서 매우 중요한 주석입니다 . @Test 는 일반 방법과 테스트 케이스 방법을 구별하는 데 사용됩니다. 메소드가 @Test 어노테이션 으로 장식되면 해당 특정 메소드는 테스트 케이스 로 간주 되고 JUnit Runner에 의해 실행됩니다 . JUnit Runner 는 Java 클래스 내에서 사용 가능한 JUnit 테스트 케이스 를 찾아 실행하는 데 사용되는 특수 클래스입니다. 지금은 Android Studio의 빌드 인 옵션을 사용하여 단위 테스트를 실행합니다 (이는 차례로 JUnit Runner 실행 ). 샘플 코드는 다음과 같습니다.

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은 유사하다 @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@Before 와 유사 하지만 @BeforeClass로 주석이 달린 메서드 는 특정 클래스의 모든 테스트 케이스를 실행하기 전에 한 번만 호출되거나 실행됩니다. 데이터베이스 연결 개체와 같이 리소스 집약적 인 개체를 만드는 데 유용합니다. 이렇게하면 테스트 케이스 모음을 실행하는 시간이 줄어 듭니다. 이 메서드가 제대로 작동하려면 정적이어야합니다. 샘플에서는 아래 지정된대로 모든 테스트 케이스를 실행하기 전에 계산 객체를 한 번 만들 수 있습니다.

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 의 하이라이트 중 하나입니다 . 테스트 케이스에 동작을 추가하는 데 사용됩니다. 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

역설

Assertion은 테스트 케이스의 예상 값이 테스트 케이스 결과의 실제 값과 일치하는지 확인하는 방법입니다. JUnit 은 다양한 시나리오에 대한 어설 션을 제공합니다. 몇 가지 중요한 주장이 아래에 나열되어 있습니다.

  • fail() − 명시 적으로 테스트 케이스를 실패하게 만드는 것.

  • assertTrue(boolean test_condition) − test_condition이 참인지 확인

  • assertFalse(boolean test_condition) − test_condition이 거짓인지 확인

  • assertEquals(expected, actual) − 두 값이 동일한 지 확인

  • assertNull(object) − 객체가 null인지 확인

  • assertNotNull(object) − 객체가 null이 아닌지 확인

  • assertSame(expected, actual) − 둘 다 동일한 객체를 참조하는지 확인합니다.

  • assertNotSame(expected, actual) − 둘 다 다른 개체를 참조하는지 확인합니다.

이 장에서는 에스프레소 테스트 프레임 워크의 용어, 간단한 에스프레소 테스트 케이스를 작성하는 방법 및 에스프레소 테스트 프레임 워크의 전체 워크 플로 또는 아키텍처에 대해 알아 보겠습니다.

개요

Espresso는 사용자 인터페이스와 Android 애플리케이션의 사용자 상호 작용을 테스트하기 위해 많은 클래스를 제공합니다. 아래에 명시된대로 다섯 가지 범주로 분류 할 수 있습니다.

JUnit 러너

Android 테스트 프레임 워크는 JUnit3 및 JUnit4 스타일 테스트 케이스로 작성된 에스프레소 테스트 케이스를 실행하기위한 실행기 AndroidJUnitRunner를 제공합니다. 이는 안드로이드 애플리케이션에 특화되어 있으며 실제 기기 또는 에뮬레이터에서 에스프레소 테스트 케이스 및 테스트중인 애플리케이션로드를 투명하게 처리하고 테스트 케이스를 실행하며 테스트 케이스의 결과를보고합니다. 테스트 케이스에서 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는 androidx.test.espresso.matcher.ViewMatchers 패키지에 있는 많은 뷰 매처 클래스를 제공 하여 Android 활동 화면의 뷰 계층 구조에서 UI 요소 / 뷰를 일치시키고 찾습니다. Espresso의 onView 메서드는 Matcher (View matchers) 유형의 단일 인수를 가져와 해당 UI보기를 찾은 다음 해당 ViewInteraction 개체를 반환 합니다. onView 메서드에 의해 반환 된 ViewInteraction 개체 는 일치 된보기를 클릭하는 것과 같은 작업을 호출하는 데 추가로 사용되거나 일치 된보기를 어설 션하는 데 사용할 수 있습니다. "Hello World!"라는 텍스트가있는보기를 찾는 샘플 코드 다음과 같다,

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

여기에서 withText 는 "Hello World!"라는 텍스트가있는 UI 뷰를 일치시키는 데 사용할 수있는 matcher입니다.

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을 적절한 뷰 주장으로 전달하여. 일치하는 뷰가 다음과 같다고 주장하는 샘플 코드,

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

여기서 매치 는 뷰 매처를 받아들이고 뷰 어설 션을 반환하며, 이는 ViewInteraction의 check 메소드로 확인할 수 있습니다 .

Espresso 테스트 프레임 워크의 워크 플로

에스프레소 테스트 프레임 워크가 어떻게 작동하는지 그리고 어떤 종류의 사용자 상호 작용을 간단하고 유연한 방식으로 수행 할 수있는 옵션을 제공하는지 이해하겠습니다. 에스프레소 테스트 케이스의 워크 플로는 다음과 같습니다.

  • 앞서 배운 것처럼 Android JUnit 실행기 인 AndroidJUnit4 는 Android 테스트 케이스를 실행합니다. 에스프레소 테스트 케이스는 @RunWith (AndroidJUnut.class) 로 표시되어야합니다 . 먼저 AndroidJUnit4 는 테스트 케이스를 실행할 환경을 준비합니다. 연결된 Android 기기 또는 에뮬레이터를 시작하고 애플리케이션을 설치하며 테스트 할 애플리케이션이 준비 상태인지 확인합니다. 테스트 케이스를 실행하고 결과를보고합니다.

  • Espresso는 활동 을 지정하기 위해 ActivityTestRule 유형의 JUnit 규칙 하나 이상이 필요 합니다. Android JUnit 실행기는 ActivityTestRule을 사용하여 시작할 활동을 시작합니다 .

  • 모든 테스트 케이스 는 원하는보기와 일치하고 찾기 위해 최소한의 단일 onView 또는 onDate ( AdapterView 와 같은 데이터 기반보기를 찾는 데 사용됨 ) 메소드 호출이 필요합니다. onView 또는 onData는 ViewInteraction 객체를 반환 합니다.

  • ViewInteraction 객체가 반환 되면 선택한 뷰의 작업을 호출하거나 어설 션을 사용하여 예상 뷰에 대한 뷰를 확인할 수 있습니다.

  • 사용 가능한보기 작업 중 하나를 전달 하여 ViewInteraction 개체 의 수행 메서드를 사용하여 작업을 호출 할 수 있습니다 .

  • 사용 가능한 뷰 어설 션 중 하나를 전달 하여 ViewInteraction 개체 의 check 메서드를 사용하여 어설 션을 호출 할 수 있습니다 .

워크 플로 의 다이어그램 표현은 다음과 같습니다.

예-어설 션보기

"Hello World!"가있는 텍스트보기를 찾는 간단한 테스트 케이스를 작성해 보겠습니다. "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 뷰 매처를 사용 하여 "Hello World!"가있는 텍스트 뷰를 찾았습니다. 텍스트와 일치보기 어설 션을 일치시켜 텍스트보기가 올바르게 표시되도록합니다. Android Studio에서 테스트 케이스가 호출되면 테스트 케이스를 실행하고 아래와 같이 성공 메시지를보고합니다.

view_isCorrect 테스트 케이스

Espresso 프레임 워크는 많은 뷰 매처를 제공합니다. matcher의 목적은 ID, 텍스트 및 하위보기의 가용성과 같은보기의 다른 속성을 사용하여보기를 일치시키는 것입니다. 각 매처는보기의 특정 속성과 일치하며 특정 유형의보기에 적용됩니다. 예를 들어 withId 매처 는 뷰 의 Id 속성 과 일치하고 모든 뷰에 적용되는 반면 withText 매처 는 뷰 의 Text 속성 과 일치하고 TextView 에만 적용 됩니다.

이 장에서는 에스프레소 테스트 프레임 워크에서 제공하는 다양한 매처를 배우고 에스프레소 매 처가 구축 된 Hamcrest 라이브러리에 대해 알아 보겠습니다 .

햄 크레스트 도서관

Hamcrest 라이브러리는 에스프레소 테스트 프레임 워크 범위에서 중요한 라이브러리입니다. Hamcrest 는 그 자체로 matcher 객체를 작성하기위한 프레임 워크입니다. Espresso 프레임 워크는 Hamcrest 라이브러리를 광범위하게 사용 하며 간단하고 확장 가능한 매처를 제공하기 위해 필요할 때마다 확장합니다.

Hamcrest 는 간단한 assertThat 함수 와 모든 객체를 주장하는 매처 모음을 제공합니다. assertThat 에는 세 가지 인수가 있으며 다음과 같습니다.

  • 문자열 (테스트에 대한 설명, 선택 사항)

  • 개체 (실제)

  • Matcher (예상)

목록 객체에 예상 값이 있는지 테스트하는 간단한 예제를 작성해 보겠습니다.

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 은 실제 목록에 항목 중 하나로 지정된 값이 있는지 여부를 확인하는 matcher를 반환합니다.

Hamcrest 에는 많은 내장 매처와 새로운 매처를 생성하는 옵션이 있습니다. 에스프레소 테스트 프레임 워크에서 유용한 몇 가지 중요한 내장 매처는 다음과 같습니다.

무엇이든-항상 매처

논리적 기반 매처

  • allOf − 모든 일치자가 성공한 경우에만 원하는 수의 일치 자와 일치를 허용합니다.

  • anyOf -한 명의 일치자가 성공하면 원하는 수의 일치 자와 일치를 허용합니다.

  • not -한 명의 매처를 수락하고 매 처가 실패한 경우에만 일치하며 그 반대의 경우도 마찬가지입니다.

텍스트 기반 매처

  • equalToIgnoringCase − 실제 입력이 대소 문자를 무시하고 예상되는 문자열과 같은지 테스트하는 데 사용됩니다.

  • equalToIgnoringWhiteSpace − 실제 입력이 대소 문자와 공백을 무시하고 지정된 문자열과 같은지 테스트하는 데 사용됩니다.

  • containsString − 실제 입력에 지정된 문자열이 포함되어 있는지 테스트하는 데 사용됩니다.

  • endsWith − 실제 입력이 지정된 문자열로 시작하는지 테스트하는 데 사용됩니다.

  • startsWith − 실제 입력이 지정된 문자열로 끝나는 지 여부를 테스트하는 데 사용됩니다.

숫자 기반 매처

  • closeTo − 실제 입력이 예상 숫자에 가까운 지 테스트하는 데 사용됩니다.

  • greaterThan − 실제 입력이 예상 숫자보다 큰지 테스트하는 데 사용됩니다.

  • greaterThanOrEqualTo − 실제 입력이 예상 숫자보다 크거나 같은지 테스트하는 데 사용됩니다.

  • lessThan − 실제 입력이 예상 숫자보다 작은 지 테스트하는 데 사용됩니다.

  • lessThanOrEqualTo − 실제 입력이 예상 숫자보다 작거나 같은지 테스트하는 데 사용됩니다.

개체 기반 매처

  • equalTo − 실제 입력이 예상되는 객체와 같은지 테스트하는 데 사용

  • hasToString − 실제 입력에 toString 메서드가 있는지 테스트하는 데 사용됩니다.

  • instanceOf − 실제 입력이 예상 클래스의 인스턴스인지 테스트하는 데 사용됩니다.

  • isCompatibleType − 실제 입력이 예상 유형과 호환되는지 테스트하는 데 사용됩니다.

  • notNullValue − 실제 입력이 null이 아닌지 테스트하는 데 사용됩니다.

  • sameInstance − 실제 입력과 예상이 동일한 인스턴스인지 테스트하는 데 사용됩니다.

  • hasProperty − 실제 입력에 예상 속성이 있는지 테스트하는 데 사용

is − Sugar 또는 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이고 Matcher <String>을 반환합니다.

withHint ()

withHint () 는 Matcher <String> 유형의 인수를 허용하며 인수는 뷰의 힌트 값을 참조합니다. 뷰의 힌트를 사용하여 뷰와 일치하는 matcher를 반환합니다. 샘플 코드는 다음과 같습니다.

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> 유형의 인수를 허용하며 인수는 Spinner의 현재 선택된 뷰의 값을 참조합니다. 선택한 항목의 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 matcher입니다. allOf 는 전달 된 모든 matcher와 일치하는 matcher를 반환합니다. 여기서는보기를 일치시키고보기의 텍스트 값에 링크가 있는지 확인하는 데 사용됩니다.

hasTextColor ()

hasTextColor () 는 int 유형의 단일 인수를 허용하며 인수는 색상의 리소스 ID를 참조합니다. 색상에 따라 TextView 와 일치하는 matcher를 반환합니다 . TextView 에만 적용 됩니다. 샘플 코드는 다음과 같습니다.

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

hasEllipsizedText ()

hasEllipsizedText () 에는 인수가 없습니다. 긴 텍스트가 있고 생략 된 (first .. ten .. last) 또는 잘린 (first…) TextView와 일치하는 matcher를 반환합니다. 샘플 코드는 다음과 같습니다.

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는 입력 방법 옵션에 있습니다. 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 ()

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 () 에는 인수가 없습니다. 현재 화면에 완전히 표시된 뷰와 일치하는 matcher를 반환합니다. 샘플 코드는 다음과 같습니다.

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

사용 가능()

isEnabled () 에는 인수가 없습니다. 활성화 된 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

isFocusable ()

isFocusable () 에는 인수가 없습니다. 포커스 옵션이있는 뷰와 일치하는 matcher를 반환합니다. 샘플 코드는 다음과 같습니다.

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와 일치하는 matcher를 반환합니다. 샘플 코드는 다음과 같습니다.

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

withParent ()

withParent () 는 Matcher <View> 유형의 인수 하나를 허용합니다. 인수는 견해를 나타냅니다. 지정된 뷰가 상위 뷰인 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

hasSibling ()

hasSibling () 은 Matcher> View <유형의 인수 하나를 허용합니다. 인수는 견해를 나타냅니다. 전달 된 뷰가 형제 뷰 중 하나 인 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

withChild ()

withChild () 는 Matcher <View> 유형의 인수 하나를 허용합니다. 인수는 견해를 나타냅니다. 전달 된 뷰가 자식 뷰인 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

hasChildCount ()

hasChildCount () 는 int 유형의 인수 하나를 허용합니다. 인수는 뷰의 하위 개수를 나타냅니다. 인수에 지정된 것과 정확히 동일한 수의 자식보기를 가진보기와 일치하는 일치자를 반환합니다. 샘플 코드는 다음과 같습니다.

onView(hasChildCount(4))

hasMinimumChildCount ()

hasMinimumChildCount () 는 int 유형의 인수 하나를 허용합니다. 인수는 뷰의 하위 개수를 나타냅니다. 인수에 지정된 하위보기의 수 이상이있는보기와 일치하는 일치자를 리턴합니다. 샘플 코드는 다음과 같습니다.

onView(hasMinimumChildCount(4))

hasDescendant ()

hasDescendant () 는 Matcher <View> 유형의 인수 하나를 허용합니다. 인수는 견해를 나타냅니다. 전달 된 뷰가 뷰 계층 구조의 하위 뷰 중 하나 인 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

isDescendantOfA ()

isDescendantOfA () 는 Matcher <View> 유형의 인수 하나를 허용합니다. 인수는 견해를 나타냅니다. 전달 된 뷰가 뷰 계층 구조의 상위 뷰 중 하나 인 뷰와 일치하는 매처를 반환합니다. 샘플 코드는 다음과 같습니다.

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

Espresso는 자체 커스텀 뷰 매처를 생성 할 수있는 다양한 옵션을 제공하며 Hamcrest 매처를 기반으로 합니다. Custom matcher는 프레임 워크를 확장하고 프레임 워크를 우리 취향에 맞게 사용자 정의하는 매우 강력한 개념입니다. 커스텀 매처를 작성할 때의 장점은 다음과 같습니다.

  • 자체 사용자 정의보기의 고유 한 기능을 활용하려면

  • Custom matcher는 AdapterView 기반 테스트 케이스에서 다른 유형의 기본 데이터와 일치 하도록 도와줍니다 .

  • 여러 매처의 기능을 결합하여 현재 매처를 단순화하려면

우리는 수요가 발생할 때 새로운 matcher를 만들 수 있으며 매우 쉽습니다. TextView 의 id와 text를 테스트하기 위해 matcher를 반환하는 새로운 사용자 지정 matcher를 만들어 보겠습니다 .

Espresso는 새 매처를 작성하기 위해 다음 두 가지 클래스를 제공합니다.

  • TypeSafeMatcher

  • BoundedMatcher

BoundedMatcher 가 올바른 유형을 수동으로 확인하지 않고 올바른 유형으로 오브젝트 캐스팅을 투명하게 처리 한다는 점을 제외하면 두 클래스는 본질적으로 유사 합니다. BoundedMatcher 클래스를 사용하여 새로운 matcher withIdAndText생성 합니다. 새 매처를 작성하는 단계를 확인해 보겠습니다.

  • app / build.gradle 파일 에 아래 종속성을 추가 하고 동기화하십시오.

dependencies {
   implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • 매처 (메소드)를 포함 할 새 클래스를 만들고 최종으로 표시합니다.

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 유형의 단일 인수가 있으며 매처 관련 오류 정보에 사용됩니다. matchesSafely 는 반환 유형이 부울 인 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 matcher를 사용하여 아래와 같이 테스트 케이스를 작성할 수 있습니다.

@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 와 같은 텍스트 속성을 가진보기 (예상)와 일치하는보기 매처를 반환합니다 .

  • checkViewAssertion 유형의 인수를 받아들이고 ViewAssertion 개체에 전달 된 것을 사용하여 assertion을 수행 하는 메서드입니다 .

  • matches (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를 가진 view가 존재하고 id, R.id.target_view를 가진 view 위에 완전히 위치 하는지 확인합니다.

isCompletelyBelow ()

대상 뷰 매처를 받아들이고 뷰 어설 션을 반환하여 뷰 매처 (실제)가 존재하고 대상 뷰 매처 아래에 완전히 위치하도록합니다.

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

여기서, 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view를 가진 view 아래 완전히 위치 하는지 확인 합니다.

isCompletelyLeftOf ()

대상 뷰 매처를 받아들이고 뷰 어설 션을 반환하여 뷰 매처 (실제)가 존재하고 대상 뷰 매처의 완전히 왼쪽에 위치하도록합니다.

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

여기서 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view를 가진 view에서 완전히 왼쪽에 위치 하는지 확인합니다.

isCompletelyRightOf ()

대상 뷰 매처를 수락하고 뷰 어설 션을 반환하여 뷰 매처 (실제)가 존재하고 대상 뷰 매처의 완전히 오른쪽에 위치하도록합니다.

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

여기서 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view를 가진 view의 완전히 오른쪽에 위치하는지 확인합니다.

isLeftAlignedWith ()

대상 뷰 매처를 수락하고 뷰 어설 션을 반환하여 뷰 매처 (실제)가 존재하고 대상 뷰 매처와 정렬되도록합니다.

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

여기에서 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view 를 가진 view와 정렬되어 있는지 확인합니다.

isPartiallyAbove ()

대상보기 일치자를 허용하고보기 어설 션을 반환하여보기 일치 자 (실제)가 존재하고 대상보기 일치 자 위에 부분적으로 위치하도록합니다.

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

여기서, 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view를 가진 view 위에 부분적으로 위치 하는지 확인합니다.

isPartiallyBelow ()

대상보기 일치자를 허용하고보기 어설 션을 리턴하여보기 일치 자 (실제)가 존재하고 대상보기 일치 자 아래 부분적으로 위치하도록합니다.

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

여기서 테스트 케이스는 id, R.id.view를 가진 view가 존재하고 id, R.id.target_view를 가진 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를 가진 view가 존재하고 id, R.id.target_view 를 가진 view와 최상단 정렬 되는지 확인합니다.

noEllipsizedText ()

뷰 계층 구조에 생략되거나 잘린 텍스트 뷰가 포함되지 않도록하는 뷰 어설 션을 반환합니다.

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

noMultilineButtons ()

뷰 계층 구조에 여러 줄 단추가 포함되지 않도록하는 뷰 어설 션을 반환합니다.

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

noOverlaps ()

TextView 또는 ImageView에 할당 할 수있는 하위 개체가 서로 겹치지 않도록하는 뷰 어설 션을 반환합니다. 대상 뷰 매처를 받아들이고 뷰 어설 션을 반환하는 또 다른 옵션이있어 대상 뷰와 일치하는 하위 뷰가 겹치지 않도록합니다.

앞에서 배운 것처럼보기 작업은 Android 애플리케이션에서 사용자가 수행 할 수있는 모든 가능한 작업을 자동화합니다. Espresso onView 및 "onData" 는보기 작업을 수락하고 테스트 환경에서 해당 사용자 작업을 호출 / 자동화하는 수행 메서드를 제공 합니다. 예를 들어,“click ()”은 onView ( R.id.myButton ) .perform (click ()) 메서드에 전달 될 때 버튼의 클릭 이벤트를 발생 시키는 뷰 액션입니다 (id :“myButton”). ) 테스트 환경에서.

이 장에서는 에스프레소 테스트 프레임 워크에서 제공하는보기 작업에 대해 알아 보겠습니다.

typeText ()

typeText ()문자열 유형의 하나의 인수 (텍스트)를 허용 하고보기 조치를 리턴합니다. 반환 된보기 작업은 제공된 텍스트를보기에 입력합니다. 텍스트를 배치하기 전에보기를 한 번 탭합니다. 내용이 이미 텍스트를 포함하고있는 경우 임의의 위치에 배치 될 수 있습니다.

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 () 는 view의 내용을 대체한다는 점을 제외하면 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 () 에는 인수가 없으며보기 작업을 반환합니다.이 작업은 뒤로 버튼을 클릭하고 뒤로 버튼 작업이 애플리케이션 자체를 종료하는 경우 예외를 throw하지 않습니다.

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

openLink ()

openLink () 에는 두 개의 인수가 있습니다. 첫 번째 인수 (링크 텍스트)는 Matcher 유형 이며 HTML 앵커 태그의 텍스트를 참조합니다. 두 번째 인수 (url)는 Matcher 유형 이며 HTML 앵커 태그의 URL을 참조합니다. TextView 에만 적용 됩니다. 텍스트보기의 콘텐츠에서 사용 가능한 모든 HTML 앵커 태그를 수집하는보기 작업을 반환하고 첫 번째 인수 (링크 텍스트) 및 두 번째 인수 (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 () 에는 ** String * 또는 Matcher 유형일 수있는 하나의 인수가 있습니다. 이는 단순히 openLink * 메소드 의 지름길 입니다.

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

openLinkWithUri ()

openLinkWithUri () 에는 문자열 또는 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 ()

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

AdapterViewAdapter를 사용하여 기본 데이터 소스에서 가져온 제품 목록 및 사용자 연락처와 같은 유사한 정보 모음을 렌더링하도록 특별히 설계된 특별한 종류의보기 입니다. 데이터 소스는 복잡한 데이터베이스 항목에 대한 간단한 목록 일 수 있습니다. AdapterView 에서 파생 된보기 중 일부 는 ListView , GridViewSpinner 입니다.

AdapterView 는 기본 데이터 소스에서 사용 가능한 데이터의 양에 따라 사용자 인터페이스를 동적으로 렌더링합니다. 또한 AdapterView 는 화면의 사용 가능한 가시 영역에서 렌더링 할 수있는 최소한의 필수 데이터 만 렌더링합니다. AdapterView 는 메모리를 절약하고 기본 데이터가 큰 경우에도 사용자 인터페이스를 매끄럽게 보이게하기 위해이 작업을 수행합니다.

분석시 AdapterView 아키텍처 의 특성으로 인해 테스트 할 특정 뷰가 처음에 전혀 렌더링되지 않을 수 있기 때문에 onView 옵션과 해당 뷰 매 처가 관련이 없습니다. 운 좋게도 espresso 는 hamcrest matcher (기본 데이터의 데이터 유형과 관련이있는)를 받아 기본 데이터와 일치시키고 일치 된 데이터의 뷰에 해당하는 DataInteraction 유형의 개체를 반환하는 onData ( ) 메서드를 제공합니다 . 샘플 코드는 다음과 같습니다.

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

여기서 onData () 는 기본 데이터 (배열 목록)에서 사용할 수있는 경우 "Apple"항목과 일치 하고 일치 된 뷰 ( "Apple"항목에 해당하는 TextView )와 상호 작용하기 위해 DataInteraction 개체를 반환합니다 .

행동 양식

DataInteraction 은 뷰와 상호 작용할 수있는 아래 메서드를 제공합니다.

행하다()

이것은 뷰 액션을 받아들이고 전달 된 뷰 액션을 실행합니다.

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 () 메서드를 사용하여 테스트 케이스를 작성합니다 .

  • 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"
/>
  • 이제 과일 배열을 기본 데이터로 사용하는 어댑터를 만들고 목록보기로 설정합니다. 이 요구 사항은에서 수행 할 에서 onCreate ()MainActivity 아래에 지정된대로

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

또한, 확인 시험 구성에서 수행 할 응용 프로그램 / 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 는 응용 프로그램 내부에 웹 페이지를 표시하기 위해 Android에서 제공하는 특수보기입니다. WebView 는 chrome 및 firefox와 같은 본격적인 브라우저 애플리케이션의 모든 기능을 제공하지 않습니다. 그러나 표시 할 콘텐츠를 완전히 제어하고 웹 페이지 내에서 호출 할 모든 Android 기능을 노출합니다. WebView를 활성화 하고 HTML 기술과 카메라 및 연락처와 같은 기본 기능을 사용하여 UI를 쉽게 설계 할 수있는 특수 환경을 제공합니다. 이 기능 세트를 통해 WebView하이브리드 애플리케이션이라는 새로운 종류의 애플리케이션을 제공 할 수 있습니다. 여기서 UI는 HTML로 수행되고 비즈니스 로직은 JavaScript 또는 외부 API 엔드 포인트를 통해 수행 됩니다.

일반적으로 WebView 테스트는 기본 사용자 인터페이스 /보기가 아닌 사용자 인터페이스 요소에 HTML 기술을 사용하기 때문에 어려운 작업이 필요합니다. Espresso는 네이티브 뷰 매처 및 뷰 어설 션과 의도적으로 유사한 웹 매처 및 웹 어설 션에 새로운 세트를 제공함으로써이 분야에서 탁월합니다. 동시에 웹 기술 기반 테스트 환경을 포함하여 균형 잡힌 접근 방식을 제공합니다.

Espresso 웹은 웹 요소를 찾고 조작하는 데 사용되는 WebDriver Atom 프레임 워크를 기반으로 합니다. Atom 은보기 작업과 유사합니다. Atom은 웹 페이지 내에서 모든 상호 작용을 수행합니다. WebDriverfindElement () , getElement () 와 같은 미리 정의 된 메소드 세트를 노출하여 웹 요소를 찾고 해당 아톰을 반환합니다 (웹 페이지에서 작업 수행).

표준 웹 테스트 문은 아래 코드와 같습니다.

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

여기,

  • onWebView ()-onView () 와 유사하게 WebView를 테스트하기위한 API 세트를 노출합니다.

  • withElement () − Atom을 사용하여 웹 페이지 내에서 웹 요소를 찾는 데 사용되는 여러 메소드 중 하나이며 ViewInteraction과 유사한 WebInteration 객체를 반환합니다.

  • perform () -Atom을 사용하여 웹 페이지 내에서 작업을 실행하고 WebInteraction을 반환합니다.

  • check () -이것은 WebAssertion을 사용하여 필요한 주장을 수행합니다.

샘플 웹 테스트 코드는 다음과 같습니다.

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

여기,

  • findElement () 요소를 찾고 Atom을 반환

  • webMatches 는 match 메소드와 유사합니다.

샘플 애플리케이션 작성

WebView를 기반으로 간단한 애플리케이션을 작성하고 onWebView () 메서드를 사용하여 테스트 케이스를 작성해 보겠습니다 . 샘플 응용 프로그램을 작성하려면 다음 단계를 따르십시오.

  • Android 스튜디오를 시작하십시오.

  • 앞에서 설명한대로 새 프로젝트를 만들고 이름을 MyWebViewApp으로 지정 합니다.

  • RefactorMigrate to AndroidX 옵션 메뉴 를 사용하여 애플리케이션을 AndroidX 프레임 워크로 마이그레이션 합니다.

  • AndroidManifest.xml 파일 에 아래 구성 옵션을 추가하여 인터넷 액세스 권한을 부여하십시오.

<uses-permission android:name = "android.permission.INTERNET" />
  • Espresso 웹은 별도의 플러그인으로 제공됩니다. 따라서 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의 같은에서 부하 링크 행동 방법 웹보기를 ; 그렇지 않으면 응용 프로그램 외부에 새 브라우저 창이 열립니다. 에 배치 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 배치

  • loadUrl 메서드의 URL을 구성된 URL로 바꿉니다.

  • 이제 응용 프로그램을 실행하고 모든 것이 정상인지 수동으로 확인하십시오. 아래의 스크린 샷입니다 웹보기 샘플 응용 프로그램 -

  • 이제 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 와 그 동작 을 테스트합니다 .

@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")));
}

여기서 테스트는 다음과 같은 순서로 진행되었습니다.

  • 링크 발견 사과 통해 id 속성 사용 findElement () 메소드와 Locator.ID 열거.

  • webMatches () 메서드를 사용하여 링크의 텍스트를 확인합니다.

  • 링크에서 클릭 동작을 수행합니다. apple.html 페이지 가 열립니다 .

  • findElement () 메서드와 Locator.TAG_NAME 열거를 사용하여 h1 요소를 다시 찾았습니다 .

  • 마지막으로 webMatches () 메서드를 사용하여 h1 태그 의 텍스트를 다시 확인합니다 .

  • 마지막으로 Android 스튜디오 컨텍스트 메뉴를 사용하여 테스트 케이스를 실행합니다.

이 장에서는 Espresso Idling 리소스를 사용하여 비동기 작업을 테스트하는 방법을 알아 봅니다.

최신 애플리케이션의 과제 중 하나는 원활한 사용자 경험을 제공하는 것입니다. 원활한 사용자 경험을 제공하려면 애플리케이션 프로세스가 몇 밀리 초 이상 걸리지 않도록 백그라운드에서 많은 작업이 필요합니다. 백그라운드 작업은 간단한 작업부터 원격 API / 데이터베이스에서 데이터를 가져 오는 복잡한 작업까지 다양합니다. 과거의 문제를 해결하기 위해 개발자는 백그라운드 스레드에서 비용이 많이 들고 오래 실행되는 작업을 작성하고 백그라운드 스레드가 완료되면 기본 UIThread 와 동기화했습니다 .

다중 스레드 응용 프로그램을 개발하는 것이 복잡하다면 테스트 사례를 작성하는 것이 훨씬 더 복잡합니다. 예를 들어, 필요한 데이터가 데이터베이스에서로드되기 전에 AdapterView 를 테스트해서는 안됩니다 . 데이터 가져 오기가 별도의 스레드에서 수행되는 경우 테스트는 스레드가 완료 될 때까지 기다려야합니다. 따라서 테스트 환경은 백그라운드 스레드와 UI 스레드간에 동기화되어야합니다. Espresso는 멀티 스레드 애플리케이션 테스트를위한 탁월한 지원을 제공합니다. 애플리케이션은 다음과 같은 방식으로 스레드를 사용하며 에스프레소는 모든 시나리오를 지원합니다.

사용자 인터페이스 스레딩

복잡한 UI 요소로 원활한 사용자 경험을 제공하기 위해 Android SDK에서 내부적으로 사용됩니다. Espresso는이 시나리오를 투명하게 지원하며 구성 및 특수 코딩이 필요하지 않습니다.

비동기 작업

최신 프로그래밍 언어는 스레드 프로그래밍의 복잡성없이 경량 스레딩을 수행하기 위해 비동기 프로그래밍을 지원합니다. 비동기 작업은 espresso 프레임 워크에서도 투명하게 지원됩니다.

사용자 스레드

개발자는 데이터베이스에서 복잡하거나 큰 데이터를 가져 오기 위해 새 스레드를 시작할 수 있습니다. 이 시나리오를 지원하기 위해 espresso는 유휴 리소스 개념을 제공합니다.

이 장에서 유휴 자원의 개념과 방법을 배우십시오.

개요

유휴 리소스의 개념은 매우 간단하고 직관적입니다. 기본 아이디어는 장기 실행 프로세스가 별도의 스레드에서 시작될 때마다 변수 (부울 값)를 만들어 프로세스가 실행 중인지 여부를 식별하고 테스트 환경에 등록하는 것입니다. 테스트 중에 테스트 실행기는 등록 된 변수가있는 경우이를 확인한 다음 실행 상태를 찾습니다. 실행 상태가 참이면 테스트 실행기는 상태가 거짓이 될 때까지 기다립니다.

Espresso는 실행 상태를 유지하기 위해 IdlingResources 인터페이스를 제공합니다. 구현할 주요 메서드는 isIdleNow ()입니다. isIdleNow ()가 true를 반환하면 espresso는 테스트 프로세스를 재개하거나 isIdleNow ()가 false를 반환 할 때까지 기다립니다. IdlingResources를 구현하고 파생 클래스를 사용해야합니다. Espresso는 또한 워크로드를 쉽게하기 위해 내장 된 IdlingResources 구현 중 일부를 제공합니다. 다음과 같습니다.

CountingIdlingResource

이것은 실행중인 작업의 내부 카운터를 유지합니다. increment ()decrement () 메소드를 노출 합니다. increment () 는 카운터에 하나를 추가하고 decrement () 는 카운터에서 하나를 제거합니다. isIdleNow () 는 작업이 활성화되지 않은 경우에만 true를 반환합니다.

UriIdlingResource

이것은 네트워크 대기 시간을 감당하기 위해 카운터가 오랜 기간 동안 0 이어야 한다는 점을 제외 하면 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"
}

샘플 애플리케이션

별도의 스레드에있는 웹 서비스에서 과일을 가져 와서 과일을 나열하는 간단한 애플리케이션을 만든 다음 유휴 리소스 개념을 사용하여 테스트 해 보겠습니다.

  • Android 스튜디오를 시작하십시오.

  • 앞에서 설명한대로 새 프로젝트를 만들고 이름을 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를 한 곳에 보관하고 필요할 때마다 가져 오는 데 사용됩니다. 이 예제 에서는 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;
   }
}
  • MainActivity 클래스 에서 CountingIdlingResource 유형 의 전역 변수 mIdlingResource 를 아래와 같이 선언 합니다.

@Nullable
private CountingIdlingResource mIdlingResource = null;
  • 아래와 같이 웹에서 과일 목록을 가져 오는 개인 메서드를 작성합니다.

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 메서드를 사용하여 웹에서 데이터를 가져온 다음 새 어댑터를 만들고 목록보기로 설정합니다. 또한 스레드에서 작업이 완료되면 유휴 리소스를 줄입니다. 코드는 다음과 같습니다.

// 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 : // <your domain 또는 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 − 파일을 로컬 웹 서버에 저장하고 사용하십시오.

  • 이제보기를 찾고 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 Idling Fruit 앱 의 스크린 샷은 다음과 같습니다.

  • 이제 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는 이러한 상황을 처리 할 수있는 두 가지 옵션을 제공합니다. 다음과 같습니다.

예정된

이를 통해 사용자는 테스트중인 애플리케이션에서 올바른 인 텐트가 열렸는지 확인할 수 있습니다.

의도

이를 통해 사용자는 카메라에서 사진 찍기, 연락처 목록에서 전화 걸기 등과 같은 외부 활동을 조롱하고 미리 정의 된 값 집합 (예 : 실제 이미지 대신 카메라에서 미리 정의 된 이미지)을 사용하여 애플리케이션으로 돌아갈 수 있습니다. .

설정

Espresso는 플러그인 라이브러리를 통해 인 텐트 옵션을 지원하며 라이브러리는 애플리케이션의 gradle 파일에서 구성되어야합니다. 구성 옵션은 다음과 같습니다.

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

예정된()

Espresso 인 텐트 플러그인은 호출 된 인 텐트가 예상되는 인 텐트인지 확인하기위한 특수 매처를 제공합니다. 제공되는 매처 및 매처의 목적은 다음과 같습니다.

hasAction

이는 인 텐트 작업을 수락하고 지정된 인 텐트와 일치하는 매처를 반환합니다.

hasData

이것은 데이터를 받아들이고 그것을 호출하는 동안 인 텐트에 제공된 데이터와 일치하는 매처를 반환합니다.

toPackage

이는 인 텐트 패키지 이름을 수락하고 호출 된 인 텐트의 패키지 이름과 일치하는 매처를 반환합니다.

이제 새로운 애플리케이션을 생성 하고이 개념을 이해하기 위해 의도 () 를 사용하여 외부 활동에 대해 애플리케이션을 테스트 해 보겠습니다 .

  • 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가있는 버튼 , 전화를 걸 수있는 버튼 을 프로그래밍했습니다 .

  • 아래와 같이 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);
         }
      }
   };
   // ...
}

여기서 onActivityResult 는 사용자가 call_contact_button 버튼을 사용하여 연락처 목록을 열고 연락처를 선택한 후 애플리케이션으로 돌아올 때 호출됩니다 . 한때 하여 onActivityResult 메소드가 호출, 사용자 선택한 연락처의 연락처를 찾아 텍스트 상자에 설정을 가져옵니다.

  • 응용 프로그램을 실행하고 모든 것이 정상인지 확인하십시오. Intent 샘플 애플리케이션 의 최종 모습은 아래와 같습니다.

  • 이제 아래와 같이 애플리케이션의 gradle 파일에서 에스프레소 인 텐트를 구성합니다.

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Android Studio에서 제공 하는 Sync Now 메뉴 옵션을 클릭합니다 . 그러면 인 텐트 테스트 라이브러리가 다운로드되고 올바르게 구성됩니다.

  • 열기 ExampleInstrumentedTest.java 파일과 추가 IntentsTestRule 대신에 일반적으로 사용의 AndroidTestRule을 . IntentTestRule 은 인 텐트 테스트를 처리하기위한 특수 규칙입니다.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • 두 개의 로컬 변수를 추가하여 테스트 전화 번호와 다이얼러 패키지 이름을 아래와 같이 설정합니다.

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 스튜디오에서 제공하는 Alt + Enter 옵션을 사용하여 가져 오기 문제를 수정하거나 아래 import 문을 포함합니다.

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 , hasDatatoPackage 매처는 모든 매 처가 전달 된 경우에만 성공하기 위해 allOf 매처 와 함께 사용됩니다 .

  • 이제 Android 스튜디오의 콘텐츠 메뉴를 통해 ExampleInstrumentedTest 를 실행합니다 .

의도 ()

Espresso는 외부 인 텐트 작업을 모의하기위한 purposeing () 이라는 특수 메서드를 제공합니다 . 의도 () 목적의 패키지 이름 조롱 할을 받아들이고 방법의 제공 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 () 액션을 실행하여 모의 인 텐트를 시작하고 마지막으로 모의 인 텐트를 호출 한 전화 번호와 연락처 목록의 첫 번째 항목 번호가 동일한 지 확인합니다.

누락 된 가져 오기 문제가있는 경우 Android 스튜디오에서 제공하는 Alt + Enter 옵션을 사용하여 가져 오기 문제를 수정하거나 아래 import 문을 포함합니다.

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" />

이제 연락처 목록에 항목이 하나 이상 있는지 확인한 다음 Android Studio의 컨텍스트 메뉴를 사용하여 테스트를 실행합니다.

Android는 둘 이상의 애플리케이션을 포함하는 사용자 인터페이스 테스트를 지원합니다. 우리의 응용 프로그램이 메시지를 보내기 위해 응용 프로그램에서 메시징 응용 프로그램으로 이동 한 다음 응용 프로그램으로 돌아 오는 옵션이 있다고 생각해 보겠습니다. 이 시나리오에서 UI 자동화 테스트 프레임 워크 는 애플리케이션을 테스트하는 데 도움이됩니다. UI 자동화 는 에스프레소 테스트 프레임 워크의 좋은 동반자로 간주 될 수 있습니다. UI automator 를 선택하기 전에 에스프레소 테스트 프레임 워크에서 intenting () 옵션을 이용할 수 있습니다 .

설정 지침

Android는 별도의 플러그인으로 UI 자동화를 제공합니다. 아래 지정된대로 app / build.gradle 에서 구성해야 합니다.

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

테스트 케이스 작성을위한 워크 플로

UI Automator 기반 테스트 케이스 를 작성하는 방법을 이해하겠습니다 .

  • 가져 UiDevice의 호출에 의해 객체 의 getInstance () 메소드를하고 통과 계측 개체를.

myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
  • 가져 오기 에서 UIObject의 사용하여 객체 findObject () 메소드를. 이 방법을 사용하기 전에 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를 제공하지만 테스트 케이스 작성은 게으르고 시간이 많이 걸리는 작업 일 수 있습니다. 이를 극복하기 위해 Android 스튜디오는 에스프레소 테스트 케이스를 기록하고 생성하는 기능을 제공합니다. Record Espresso Test실행 메뉴 에서 사용할 수 있습니다.

아래 설명 된 단계에 따라 HelloWorldApp 에 간단한 테스트 케이스를 기록해 보겠습니다 .

  • Android 스튜디오를 열고 HelloWorldApp 애플리케이션을 엽니 다 .

  • 실행Espresso 테스트 기록을 클릭 하고 MainActivity를 선택 합니다.

  • 레코더 스크린 샷은 다음과됩니다

  • 어설 션 추가를 클릭합니다 . 아래와 같이 응용 프로그램 화면이 열립니다.

  • Hello World를 클릭하십시오 ! . 텍스트보기선택 하는 레코더 화면 은 다음과 같습니다.

  • Save Assertion을 다시 클릭하면 어설 션이 저장되고 다음과 같이 표시됩니다.

  • 클릭 OK . 새 창이 열리고 테스트 케이스의 이름을 묻습니다. 기본 이름은 MainActivityTest입니다.

  • 필요한 경우 테스트 케이스 이름을 변경하십시오.

  • 다시 확인을 클릭 합니다. 그러면 기록 된 테스트 케이스와 함께 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));
         }
      };
   }
}
  • 마지막으로 컨텍스트 메뉴를 사용하여 테스트를 실행하고 테스트 케이스가 실행되는지 확인합니다.

긍정적 인 사용자 경험은 애플리케이션의 성공에 매우 중요한 역할을합니다. 사용자 경험에는 아름다운 사용자 인터페이스뿐만 아니라 이러한 아름다운 사용자 인터페이스가 렌더링되는 속도와 초당 프레임 속도도 포함됩니다. 사용자 인터페이스는 좋은 사용자 경험을 제공하기 위해 초당 60 프레임으로 일관되게 실행되어야합니다.

이 장에서 UI 성능을 분석하기 위해 Android에서 사용할 수있는 몇 가지 옵션에 대해 알아 보겠습니다.

dumpsys

dumpsys 는 Android 기기에서 사용할 수있는 내장 도구입니다. 시스템 서비스에 대한 현재 정보를 출력합니다. dumpsys 에는 특정 범주에 대한 정보를 덤프하는 옵션이 있습니다. gfxinfo 를 전달 하면 제공된 패키지의 애니메이션 정보가 제공됩니다. 명령은 다음과 같습니다.

> adb shell dumpsys gfxinfo <PACKAGE_NAME>

프레임 통계

framestats 는 dumpsys 명령의 옵션입니다. dumpsysframestats 로 호출 되면 최근 프레임의 자세한 프레임 타이밍 정보를 덤프합니다. 명령은 다음과 같습니다.

> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

정보를 CSV (쉼표로 구분 된 값)로 출력합니다. CSV 형식의 출력은 데이터를 쉽게 Excel로 푸시 한 다음 Excel 공식 및 차트를 통해 유용한 정보를 추출하는 데 도움이됩니다.

systrace

systrace 는 Android 기기에서 사용할 수있는 빌드 내 도구이기도합니다. 애플리케이션 프로세스의 실행 시간을 캡처하고 표시합니다. systrace 는 Android 스튜디오의 터미널에서 아래 명령을 사용하여 실행할 수 있습니다.

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

접근성 기능은 모든 응용 프로그램의 주요 기능 중 하나입니다. 공급 업체에서 개발 한 애플리케이션은 성공적이고 유용한 애플리케이션이되기 위해 Android SDK에서 설정 한 최소 접근성 지침을 지원해야합니다. 접근성 표준을 따르는 것은 매우 중요하며 쉬운 작업이 아닙니다. Android SDK는 액세스 가능한 사용자 인터페이스를 생성하기 위해 적절하게 설계된보기를 제공하여 큰 지원을 제공합니다.

마찬가지로 Espresso 테스트 프레임 워크는 접근성 테스트 기능을 핵심 테스트 엔진에 투명하게 지원하여 개발자와 최종 사용자 모두에게 큰 도움이됩니다.

Espresso에서 개발자는 AccessibilityChecks 클래스를 통해 접근성 테스트를 활성화하고 구성 할 수 있습니다 . 샘플 코드는 다음과 같습니다.

AccessibilityChecks.enable();

기본적으로 접근성 검사는보기 작업을 수행 할 때 실행됩니다. 검사에는 작업이 수행 된보기와 모든 하위보기가 포함됩니다. 다음 코드를 사용하여 화면의 전체 뷰 계층 구조를 확인할 수 있습니다.

AccessibilityChecks.enable().setRunChecksFromRootView(true);

결론

Espresso는 Android 개발자가 일반적으로 테스트 프레임 워크에서 요구하는 추가 노력 없이도 매우 쉬운 방법으로 애플리케이션을 완전히 테스트 할 수있는 훌륭한 도구입니다. 코드를 수동으로 작성하지 않고도 테스트 케이스를 생성 할 수있는 레코더도 있습니다. 또한 모든 유형의 사용자 인터페이스 테스트를 지원합니다. 에스프레소 테스트 프레임 워크를 사용하면 Android 개발자는 단기간에 문제없이 멋진 애플리케이션과 성공적인 애플리케이션을 자신있게 개발할 수 있습니다.