กรอบการทดสอบ Espresso - คู่มือฉบับย่อ

โดยทั่วไปแล้วการทดสอบระบบอัตโนมัติบนมือถือเป็นงานที่ยากและท้าทาย ความพร้อมใช้งานของ Android สำหรับอุปกรณ์และแพลตฟอร์มต่างๆทำให้การทดสอบระบบอัตโนมัติบนมือถือเป็นเรื่องที่น่าเบื่อ เพื่อให้ง่ายขึ้น Google จึงรับมือกับความท้าทายนี้และพัฒนาเฟรมเวิร์ก Espresso มี API ที่เรียบง่ายสอดคล้องและยืดหยุ่นในการทำงานอัตโนมัติและทดสอบอินเทอร์เฟซผู้ใช้ในแอปพลิเคชัน Android การทดสอบ Espresso สามารถเขียนได้ทั้งใน Java และ Kotlin ซึ่งเป็นภาษาโปรแกรมสมัยใหม่เพื่อพัฒนาแอปพลิเคชัน Android

Espresso API นั้นง่ายและง่ายต่อการเรียนรู้ คุณสามารถทำการทดสอบ Android UI ได้อย่างง่ายดายโดยไม่ต้องใช้การทดสอบแบบมัลติเธรดที่ซับซ้อน Google ไดรฟ์แผนที่และแอปพลิเคชันอื่น ๆ กำลังใช้ Espresso

คุณสมบัติของ Espresso

คุณสมบัติเด่นบางประการที่เอสเปรสโซรองรับมีดังต่อไปนี้

  • API ที่เรียบง่ายและง่ายต่อการเรียนรู้

  • ปรับขนาดได้สูงและยืดหยุ่น

  • จัดเตรียมโมดูลแยกต่างหากเพื่อทดสอบคอมโพเนนต์ Android WebView

  • จัดเตรียมโมดูลแยกต่างหากเพื่อตรวจสอบความถูกต้องและจำลอง Android Intents

  • ให้การซิงโครไนซ์อัตโนมัติระหว่างแอปพลิเคชันและการทดสอบของคุณ

ข้อดีของ Espresso

มาดูประโยชน์ของ Espresso กันดีกว่า

  • ความเข้ากันได้ย้อนหลัง

  • ติดตั้งง่าย

  • รอบการทดสอบที่มีเสถียรภาพสูง

  • รองรับกิจกรรมการทดสอบภายนอกแอปพลิเคชันด้วย

  • รองรับ JUnit4

  • UI อัตโนมัติเหมาะสำหรับการเขียนการทดสอบกล่องดำ

ในบทนี้ให้เราเข้าใจวิธีการติดตั้งเฟรมเวิร์กเอสเปรสโซกำหนดค่าเพื่อเขียนการทดสอบเอสเพรสโซและดำเนินการในแอปพลิเคชัน Android

ข้อกำหนดเบื้องต้น

Espresso เป็นกรอบการทดสอบส่วนต่อประสานผู้ใช้สำหรับการทดสอบแอปพลิเคชัน Android ที่พัฒนาในภาษา Java / Kotlin โดยใช้ Android SDK ดังนั้นข้อกำหนดเดียวของ espresso คือการพัฒนาแอปพลิเคชันโดยใช้ Android SDK ใน Java หรือ Kotlin และขอแนะนำให้มี Android Studio รุ่นล่าสุด

รายการที่ต้องกำหนดค่าอย่างถูกต้องก่อนที่เราจะเริ่มทำงานในกรอบเอสเปรสโซมีดังนี้ -

  • ติดตั้ง Java JDK ล่าสุดและกำหนดค่าตัวแปรสภาพแวดล้อม JAVA_HOME

  • ติดตั้ง Android Studio ล่าสุด (เวอร์ชัน 3.2 หรือสูงกว่า)

  • ติดตั้ง Android SDK ล่าสุดโดยใช้ SDK Manager และกำหนดค่าตัวแปรสภาพแวดล้อม ANDROID_HOME

  • ติดตั้ง Gradle Build Tool ล่าสุดและกำหนดค่าตัวแปรสภาพแวดล้อม GRADLE_HOME

กำหนดค่า EspressoTesting Framework

ในขั้นต้นกรอบการทดสอบเอสเปรสโซมีให้เป็นส่วนหนึ่งของไลบรารี Android Support ต่อมาทีม 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ในandroid / 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 จะถูกกำหนดค่า เราสามารถกำหนดค่าแอปพลิเคชันใหม่เพื่อใช้ไลบรารีAndroidXโดยคลิกที่RefactorMigrate to 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 ซึ่งใช้สำหรับการทดสอบ วิธีนี้จะช่วยลดความสับสนในขณะที่ตรวจสอบทรัพยากร ideling

ให้เราดูวิธีปิดการใช้งานแอนิเมชั่นบนอุปกรณ์ Android - (การตั้งค่า→ตัวเลือกสำหรับนักพัฒนา)

  • ขนาดภาพเคลื่อนไหวของหน้าต่าง

  • ขนาดภาพเคลื่อนไหวการเปลี่ยน

  • มาตราส่วนระยะเวลาของแอนิเมเตอร์

หากเมนูตัวเลือกสำหรับนักพัฒนาไม่มีอยู่ในหน้าจอการตั้งค่าให้คลิกหมายเลขรุ่นที่มีอยู่ในตัวเลือกเกี่ยวกับโทรศัพท์หลาย ๆ ครั้ง ซึ่งจะเปิดใช้งานเมนูตัวเลือกสำหรับนักพัฒนา

ในบทนี้ให้เราดูวิธีเรียกใช้การทดสอบโดยใช้ Android Studio

แอปพลิเคชัน Android ทุกตัวมีการทดสอบสองประเภท -

  • การทดสอบการทำงาน / หน่วย

  • การทดสอบเครื่องมือวัด

การทดสอบการทำงานไม่จำเป็นต้องติดตั้งและเปิดใช้แอปพลิเคชัน Android จริงในอุปกรณ์หรือโปรแกรมจำลองและทดสอบการทำงาน สามารถเปิดใช้งานในคอนโซลได้โดยไม่ต้องเรียกใช้แอปพลิเคชันจริง อย่างไรก็ตามการทดสอบเครื่องมือวัดจำเป็นต้องเปิดแอปพลิเคชันจริงเพื่อทดสอบฟังก์ชันการทำงานเช่นอินเทอร์เฟซผู้ใช้และการโต้ตอบกับผู้ใช้ โดยค่าเริ่มต้นการทดสอบหน่วยจะเขียนเป็นsrc/test/java/ การทดสอบโฟลเดอร์และเครื่องมือวัดจะเขียนในรูปแบบ src/androidTest/java/โฟลเดอร์ Android studio มีเมนูบริบทเรียกใช้สำหรับคลาสทดสอบเพื่อเรียกใช้การทดสอบที่เขียนในคลาสทดสอบที่เลือก โดยค่าเริ่มต้นแอปพลิเคชัน Android จะมีสองคลาส - ExampleUnitTestในโฟลเดอร์src / testและExampleInstrumentedTestในโฟลเดอร์src / androidTest

ในการเรียกใช้การทดสอบหน่วยเริ่มต้นให้เลือกExampleUnitTestในสตูดิโอ Android คลิกขวาที่ไฟล์แล้วคลิกเรียกใช้ 'ExampleUnitTest'ดังที่แสดงด้านล่าง

เรียกใช้การทดสอบหน่วย

การดำเนินการนี้จะเรียกใช้การทดสอบหน่วยและแสดงผลลัพธ์ในคอนโซลดังภาพหน้าจอต่อไปนี้ -

ความสำเร็จในการทดสอบหน่วย

ในการเรียกใช้การทดสอบเครื่องมือเริ่มต้นให้เลือก ExampleInstrumentationTest ใน android studio คลิกขวาจากนั้นคลิก Run 'ExampleInstrumentationTest' ดังที่แสดงด้านล่าง

เรียกใช้การทดสอบเครื่องมือวัด

การดำเนินการนี้จะเรียกใช้การทดสอบหน่วยโดยเปิดแอปพลิเคชันในอุปกรณ์หรือโปรแกรมจำลองและแสดงผลลัพธ์ในคอนโซลดังภาพหน้าจอต่อไปนี้ -

การทดสอบเครื่องมือวัดทำได้สำเร็จ

ในบทนี้ให้เราเข้าใจพื้นฐานของJUnitซึ่งเป็นกรอบการทดสอบหน่วยยอดนิยมที่พัฒนาโดยชุมชน Java ซึ่งสร้างกรอบการทดสอบเอสเปรสโซ

JUnitเป็นมาตรฐานโดยพฤตินัยสำหรับหน่วยทดสอบแอปพลิเคชัน Java แม้ว่าจะเป็นที่นิยมสำหรับการทดสอบหน่วย แต่ก็มีการสนับสนุนและข้อกำหนดสำหรับการทดสอบเครื่องมืออย่างครบถ้วนเช่นกัน ไลบรารีการทดสอบ Espresso ขยายคลาส JUnit ที่จำเป็นเพื่อรองรับการทดสอบเครื่องมือที่ใช้ Android

เขียนแบบทดสอบหน่วยง่ายๆ

ขอให้เราสร้างชั้น Java, การคำนวณ (Computation.java) และเขียนการคำนวณทางคณิตศาสตร์ที่เรียบง่ายสรุปและคูณ จากนั้นเราจะเขียนกรณีทดสอบโดยใช้JUnitและตรวจสอบโดยเรียกใช้กรณีทดสอบ

  • เริ่ม Android Studio

  • เปิดHelloWorldApp ที่สร้างในบทก่อนหน้า

  • สร้างไฟล์Computation.javaในapp / src / main / java / com / tutorialspoint / espressosamples / helloworldapp /และเขียนสองฟังก์ชัน - SumและMultiplyตามที่ระบุด้านล่าง

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;
   }
}
  • สร้างไฟล์ ComputationUnitTest.java ใน app / src / test / java / com / tutorialspoint / espressosamples / helloworldapp และเขียน unit test case เพื่อทดสอบฟังก์ชัน 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 ซึ่งระบุว่าฟังก์ชันเฉพาะนั้นเป็นกรณีทดสอบจูนิท assertEqualsเป็นฟังก์ชันเพื่อยืนยันว่าอาร์กิวเมนต์แรก (ค่าที่คาดหวัง) และอาร์กิวเมนต์ที่สอง (ค่าที่คำนวณ) มีค่าเท่ากันและเหมือนกัน JUnitมีวิธีการยืนยันจำนวนมากสำหรับสถานการณ์การทดสอบที่แตกต่างกัน

  • ตอนนี้เรียกใช้ComputationUnitTestใน Android studio โดยคลิกขวาที่คลาสแล้วเรียกใช้ตัวเลือกRun 'ComputationUnitTest'ตามที่อธิบายไว้ในบทก่อนหน้า ซึ่งจะเรียกใช้กรณีทดสอบหน่วยและรายงานความสำเร็จ

ผลการทดสอบหน่วยคำนวณดังแสดงด้านล่าง -

คำอธิบายประกอบ

การใช้กรอบ JUnit คำอธิบายประกอบอย่างกว้างขวาง คำอธิบายประกอบที่สำคัญบางส่วนมีดังนี้ -

  • @Test

  • @Before

  • @After

  • @BeforeClass

  • @AfterClass

  • @Rule

@คำอธิบายประกอบการทดสอบ

@Testเป็นคำอธิบายประกอบที่สำคัญมากในกรอบงานJUnit @Testใช้เพื่อแยกความแตกต่างของวิธีการปกติจากวิธีกรณีทดสอบ เมื่อวิธีการที่มีการตกแต่งด้วย@Testคำอธิบายประกอบแล้วว่าวิธีการเฉพาะถือเป็นกรณีทดสอบและจะมีการดำเนินการโดยJUnit วิ่ง JUnit Runnerเป็นคลาสพิเศษซึ่งใช้เพื่อค้นหาและรันกรณีทดสอบ JUnit ที่มีอยู่ในคลาส java สำหรับตอนนี้เราใช้ตัวเลือก build in ของ 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เพื่อให้ทำงานก่อนทั้งกรณีทดสอบsum_isCorrectและmultiply_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

@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) แต่เราสามารถใช้คลาสTemporaryFolder (ประเภทTestRule ) ที่จัดเตรียมโดยJUnit framework เพื่อสร้างโฟลเดอร์ชั่วคราวสำหรับกรณีทดสอบทั้งหมดของเราและโฟลเดอร์ชั่วคราวจะถูกลบเมื่อและเมื่อรันกรณีทดสอบ เราจำเป็นต้องสร้างตัวแปรใหม่ประเภท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 เป็นจริง

  • assertFalse(boolean test_condition) - ตรวจสอบว่า test_condition เป็นเท็จ

  • assertEquals(expected, actual) - ตรวจสอบว่าทั้งสองค่าเท่ากัน

  • assertNull(object) - ตรวจสอบว่าวัตถุนั้นเป็นโมฆะ

  • assertNotNull(object) - ตรวจสอบว่าวัตถุนั้นไม่เป็นโมฆะ

  • assertSame(expected, actual) - ตรวจสอบว่าทั้งสองอ้างถึงวัตถุเดียวกัน

  • assertNotSame(expected, actual) - ตรวจสอบว่าทั้งสองหมายถึงวัตถุที่แตกต่างกัน

ในบทนี้ให้เราเรียนรู้เงื่อนไขของกรอบการทดสอบเอสเปรสโซวิธีเขียนกรณีทดสอบเอสเปรสโซอย่างง่ายและขั้นตอนการทำงานหรือสถาปัตยกรรมที่สมบูรณ์ของกรอบการทดสอบเอสเพรสโซ

ภาพรวม

Espresso มีคลาสจำนวนมากเพื่อทดสอบส่วนต่อประสานผู้ใช้และการโต้ตอบกับผู้ใช้ของแอปพลิเคชัน Android สามารถแบ่งออกเป็นห้าประเภทตามที่ระบุด้านล่าง -

JUnit นักวิ่ง

กรอบการทดสอบ Android ให้นักวิ่ง AndroidJUnitRunner เรียกใช้กรณีทดสอบเอสเปรสโซที่เขียนในกรณีทดสอบสไตล์ JUnit3 และ JUnit4 เป็นแอปพลิเคชัน Android โดยเฉพาะและจัดการกับการโหลดกรณีทดสอบเอสเปรสโซและแอปพลิเคชันที่อยู่ระหว่างการทดสอบทั้งในอุปกรณ์จริงหรือโปรแกรมจำลองดำเนินการกรณีทดสอบและรายงานผลกรณีทดสอบอย่างโปร่งใส ในการใช้ AndroidJUnitRunner ในกรณีทดสอบเราจำเป็นต้องใส่คำอธิบายประกอบคลาสทดสอบโดยใช้คำอธิบายประกอบ @RunWith จากนั้นส่งผ่านอาร์กิวเมนต์ AndroidJUnitRunner ตามที่ระบุด้านล่าง -

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

กฎ JUnit

กรอบการทดสอบ Android มีกฎ ActivityTestRule เพื่อเปิดกิจกรรม Android ก่อนดำเนินการกรณีทดสอบ เปิดตัวกิจกรรมก่อนแต่ละวิธีที่ใส่คำอธิบายประกอบด้วย @ Test` และ @Before จะยุติกิจกรรมหลังจากวิธีการที่มีคำอธิบายประกอบ @After โค้ดตัวอย่างมีดังนี้

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

ในที่นี้MainActivityคือกิจกรรมที่จะเปิดตัวก่อนที่จะเรียกใช้กรณีทดสอบและทำลายหลังจากเรียกใช้กรณีทดสอบเฉพาะ

ViewMatchers

Espresso มีคลาสการจับคู่มุมมองจำนวนมาก (ในแพ็คเกจ androidx.test.espresso.matcher.ViewMatchers ) เพื่อจับคู่และค้นหาองค์ประกอบ UI / มุมมองในลำดับชั้นมุมมองของหน้าจอกิจกรรม Android วิธีการ onView ของ Espresso ใช้อาร์กิวเมนต์เดียวประเภทMatcher (View matchers) ค้นหามุมมอง UI ที่สอดคล้องกันและส่งคืนอ็อบเจ็กต์ViewInteraction ที่เกี่ยวข้อง ออบเจ็กต์ViewInteraction ที่ส่งคืนโดยเมธอดonViewสามารถใช้เพื่อเรียกใช้การดำเนินการเพิ่มเติมเช่นการคลิกบนมุมมองที่ตรงกันหรือสามารถใช้เพื่อยืนยันมุมมองที่ตรงกัน โค้ดตัวอย่างเพื่อค้นหามุมมองพร้อมข้อความ“ Hello World!” มีดังนี้

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

ที่นี่withTextเป็นตัวจับคู่ซึ่งสามารถใช้เพื่อจับคู่มุมมอง UI ที่มีข้อความ“ Hello World!”

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

ขั้นตอนการทำงานของ Espresso Testing Framework

ให้เราทำความเข้าใจว่ากรอบการทดสอบเอสเปรสโซทำงานอย่างไรและมีตัวเลือกในการโต้ตอบกับผู้ใช้ในรูปแบบใด ๆ ด้วยวิธีที่ง่ายและยืดหยุ่น ขั้นตอนการทำงานของกรณีทดสอบเอสเปรสโซมีดังที่อธิบายไว้ด้านล่าง

  • ตามที่เราได้เรียนรู้ไปก่อนหน้านี้ Android JUnit runner, AndroidJUnit4จะเรียกใช้กรณีทดสอบ Android กรณีทดสอบเอสเพรสโซจะต้องมีการทำเครื่องหมายด้วย@RunWith (AndroidJUnut.class) ขั้นแรกAndroidJUnit4จะเตรียมสภาพแวดล้อมเพื่อเรียกใช้กรณีทดสอบ เริ่มต้นอุปกรณ์ Android ที่เชื่อมต่อหรือโปรแกรมจำลองติดตั้งแอปพลิเคชันและตรวจสอบให้แน่ใจว่าแอปพลิเคชันที่จะทดสอบอยู่ในสถานะพร้อมใช้งาน จะเรียกใช้กรณีทดสอบและรายงานผล

  • Espresso ต้องการกฎJUnitประเภทActivityTestRuleอย่างน้อยหนึ่งข้อเพื่อระบุกิจกรรม รองชนะเลิศอันดับ JUnit Android จะเริ่มต้นกิจกรรมที่จะเปิดใช้ActivityTestRule

  • ทุกกรณีการทดสอบต้องการการเรียกใช้เมธอด onViewหรือonDateขั้นต่ำเดียว(ใช้เพื่อค้นหามุมมองตามข้อมูลเช่นAdapterView ) เพื่อจับคู่และค้นหามุมมองที่ต้องการ onView หรือ onData ส่งกลับวัตถุViewInteraction

  • เมื่อออบเจ็กต์ViewInteractionถูกส่งคืนเราสามารถเรียกใช้การดำเนินการของมุมมองที่เลือกหรือตรวจสอบมุมมองสำหรับมุมมองที่คาดหวังของเราโดยใช้การยืนยัน

  • สามารถเรียกใช้การดำเนินการโดยใช้วิธีการดำเนินการของออบเจ็กต์ViewInteractionโดยส่งผ่านแอ็คชันมุมมองที่มีอยู่

  • สามารถเรียกใช้การยืนยันโดยใช้วิธีการตรวจสอบของออบเจ็กต์ViewInteractionโดยส่งผ่านการยืนยันมุมมองที่มีอยู่

การแสดงแผนภาพของเวิร์กโฟลว์มีดังนี้

ตัวอย่าง - ดูการยืนยัน

ให้เราเขียนกรณีทดสอบง่ายๆเพื่อค้นหามุมมองข้อความที่มีคำว่า“ 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());
   }
}

ที่นี่เราได้ใช้กับตัวจับคู่มุมมองข้อความเพื่อค้นหามุมมองข้อความที่มีคำว่า“ Hello World!” ข้อความและการจับคู่ดูการยืนยันเพื่อยืนยันว่ามุมมองข้อความแสดงอย่างถูกต้อง เมื่อมีการเรียกใช้กรณีทดสอบใน Android Studio จะเรียกใช้กรณีทดสอบและรายงานข้อความแสดงความสำเร็จดังต่อไปนี้

view_is กรณีทดสอบที่ถูกต้อง

กรอบเอสเปรสโซให้ผู้จับคู่มุมมองมากมาย จุดประสงค์ของตัวจับคู่คือการจับคู่ข้อมูลพร็อพเพอร์ตี้โดยใช้แอตทริบิวต์ที่แตกต่างกันของมุมมองเช่น Id, Text และความพร้อมใช้งานของมุมมองย่อย ตัวจับคู่แต่ละตัวจะจับคู่คุณลักษณะเฉพาะของข้อมูลพร็อพเพอร์ตี้และใช้กับข้อมูลพร็อพเพอร์ตี้บางประเภท ตัวอย่างเช่นwithId matcher จะจับคู่คุณสมบัติIdของมุมมองและใช้กับมุมมองทั้งหมดในขณะที่ withText matcher จะจับคู่คุณสมบัติTextของมุมมองและใช้กับTextViewเท่านั้น

ในบทนี้ให้เราเรียนรู้ที่แตกต่างกัน matchers ที่มีให้โดยกรอบการทดสอบเอสเพรสโซเช่นเดียวกับการเรียนรู้Hamcrestห้องสมุดตามที่ matchers เอสเพรสโซที่ถูกสร้างขึ้น

ห้องสมุด Hamcrest

ห้องสมุดHamcrestเป็นห้องสมุดที่สำคัญในขอบเขตของกรอบการทดสอบเอสเปรสโซ Hamcrestเป็นกรอบสำหรับการเขียนวัตถุที่ตรงกัน เฟรมเวิร์ก Espresso ใช้ไลบรารีHamcrestอย่างกว้างขวางและขยายออกไปเมื่อใดก็ตามที่จำเป็นเพื่อจัดหาตัวจับคู่ที่เรียบง่ายและขยายได้

Hamcrestมีฟังก์ชันassertThat ที่เรียบง่ายและชุดของ matchers เพื่อยืนยันวัตถุใด ๆ 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ส่งคืนตัวจับคู่ซึ่งตรวจสอบว่ารายการจริงได้ระบุค่าเป็นหนึ่งในรายการหรือไม่

Hamcrestมีตัวจับคู่ในตัวมากมายและยังมีตัวเลือกในการสร้างตัวจับคู่ใหม่ ตัวจับคู่ในตัวที่สำคัญบางตัวที่มีประโยชน์ในกรอบการทดสอบเอสเปรสโซมีดังนี้ -

อะไรก็ได้ - เสมอกัน

ตัวจับคู่ตามตรรกะ

  • allOf - ยอมรับผู้จับคู่และการแข่งขันจำนวนเท่าใดก็ได้เฉพาะเมื่อผู้จับคู่ทั้งหมดประสบความสำเร็จ

  • anyOf - ยอมรับจำนวนผู้จับคู่และการแข่งขันหากผู้จับคู่คนใดคนหนึ่งทำสำเร็จ

  • not - ยอมรับผู้จับคู่หนึ่งคนและจับคู่เฉพาะในกรณีที่ตัวจับคู่ล้มเหลวและในทางกลับกัน

ตัวจับคู่ตามข้อความ

  • equalToIgnoringCase - ใช้เพื่อทดสอบว่าอินพุตจริงเท่ากับสตริงที่คาดไว้โดยไม่สนใจตัวพิมพ์หรือไม่

  • equalToIgnoringWhiteSpace - ใช้เพื่อทดสอบว่าอินพุตจริงเท่ากับสตริงที่ระบุโดยไม่สนใจกรณีและช่องว่างสีขาว

  • containsString - ใช้เพื่อทดสอบว่าอินพุตจริงมีสตริงที่ระบุหรือไม่

  • endsWith - ใช้เพื่อทดสอบว่าอินพุตจริงเริ่มต้นด้วยสตริงที่ระบุหรือไม่

  • startsWith - ใช้เพื่อทดสอบว่าอินพุตจริงลงท้ายด้วยสตริงที่ระบุหรือไม่

ตัวจับคู่ตามจำนวน

  • closeTo - ใช้เพื่อทดสอบว่าอินพุตจริงใกล้เคียงกับตัวเลขที่คาดไว้หรือไม่

  • greaterThan - ใช้เพื่อทดสอบว่าอินพุตจริงมากกว่าตัวเลขที่คาดไว้หรือไม่

  • greaterThanOrEqualTo - ใช้เพื่อทดสอบว่าอินพุตจริงมากกว่าหรือเท่ากับตัวเลขที่คาดไว้หรือไม่

  • lessThan - ใช้เพื่อทดสอบว่าอินพุตจริงน้อยกว่าตัวเลขที่คาดไว้หรือไม่

  • lessThanOrEqualTo - ใช้เพื่อทดสอบว่าอินพุตจริงน้อยกว่าหรือเท่ากับจำนวนที่คาดไว้หรือไม่

ตัวจับคู่ตามวัตถุ

  • equalTo - ใช้เพื่อทดสอบว่าอินพุตจริงเท่ากับวัตถุที่คาดไว้หรือไม่

  • hasToString - ใช้เพื่อทดสอบว่าอินพุตจริงมีวิธี toString หรือไม่

  • instanceOf - ใช้เพื่อทดสอบว่าอินพุตจริงเป็นอินสแตนซ์ของคลาสที่คาดหวังหรือไม่

  • isCompatibleType - ใช้เพื่อทดสอบว่าอินพุตจริงเข้ากันได้กับประเภทที่คาดไว้หรือไม่

  • notNullValue - ใช้เพื่อทดสอบว่าอินพุตจริงไม่เป็นโมฆะหรือไม่

  • sameInstance - ใช้เพื่อทดสอบว่าอินพุตจริงและที่คาดหวังเป็นอินสแตนซ์เดียวกันหรือไม่

  • hasProperty - ใช้เพื่อทดสอบว่าอินพุตจริงมีคุณสมบัติตามที่คาดหวังหรือไม่

คือ - น้ำตาลหรือทางลัดสำหรับequalTo

คู่หู

Espresso มีวิธี onView () เพื่อจับคู่และค้นหามุมมอง ยอมรับการจับคู่มุมมองและส่งคืนวัตถุ ViewInteraction เพื่อโต้ตอบกับมุมมองที่ตรงกัน รายการตัวจับคู่การดูที่ใช้บ่อยอธิบายไว้ด้านล่าง -

withId ()

withId ()ยอมรับอาร์กิวเมนต์ประเภท int และอาร์กิวเมนต์อ้างถึง id ของมุมมอง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองโดยใช้รหัสของมุมมอง โค้ดตัวอย่างมีดังนี้

onView(withId(R.id.testView))

withText ()

withText ()ยอมรับอาร์กิวเมนต์ของสตริงชนิดและอาร์กิวเมนต์อ้างถึงค่าของคุณสมบัติข้อความของมุมมอง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองโดยใช้ค่าข้อความของมุมมอง ใช้กับTextViewเท่านั้น โค้ดตัวอย่างมีดังนี้

onView(withText("Hello World!"))

withContentDescription ()

withContentDescription ()ยอมรับอาร์กิวเมนต์ของสตริงชนิดและอาร์กิวเมนต์อ้างถึงค่าของคุณสมบัติคำอธิบายเนื้อหาของมุมมอง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองโดยใช้คำอธิบายของมุมมอง โค้ดตัวอย่างมีดังนี้

onView(withContentDescription("blah"))

เรายังสามารถส่งรหัสทรัพยากรของค่าข้อความแทนข้อความได้อีกด้วย

onView(withContentDescription(R.id.res_id_blah))

hasContentDescription ()

hasContentDescription ()ไม่มีอาร์กิวเมนต์ ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่มีคำอธิบายเนื้อหาใด ๆ โค้ดตัวอย่างมีดังนี้

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

withTagKey ()

withTagKey ()ยอมรับอาร์กิวเมนต์ของสตริงประเภทและอาร์กิวเมนต์อ้างถึงคีย์แท็กของมุมมอง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองโดยใช้คีย์แท็ก โค้ดตัวอย่างมีดังนี้

onView(withTagKey("blah"))

นอกจากนี้เรายังสามารถส่งรหัสทรัพยากรของชื่อแท็กแทนชื่อแท็กได้

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

ที่นี่endWithคือ Hamcrest matcher และส่งคืน Matcher <String>

ด้วยคำแนะนำ ()

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

ด้วยSpinnerText ()

withSpinnerText ()ยอมรับอาร์กิวเมนต์ประเภท Matcher <String> และอาร์กิวเมนต์อ้างถึงค่าของมุมมองที่เลือกในปัจจุบันของ Spinner ส่งคืนตัวจับคู่ซึ่งตรงกับตัวหมุนตามค่า toString ของรายการที่เลือก โค้ดตัวอย่างมีดังนี้

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

ยอมรับอาร์กิวเมนต์สตริงหรือรหัสทรัพยากรของสตริงด้วย โค้ดตัวอย่างมีดังนี้

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 และอาร์กิวเมนต์อ้างถึงรหัสทรัพยากรของสี จะส่งคืนตัวจับคู่ซึ่งตรงกับTextViewตามสีของมัน ใช้กับTextViewเท่านั้น โค้ดตัวอย่างมีดังนี้

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

hasEllipsizedText ()

hasEllipsizedText ()ไม่มีอาร์กิวเมนต์ จะส่งคืนตัวจับคู่ซึ่งตรงกับ TextView ที่มีข้อความยาวและเป็นจุดไข่ปลา (ตัวแรก .. สิบ .. ตัวสุดท้าย) หรือตัดออก (ตัวแรก…) โค้ดตัวอย่างมีดังนี้

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

hasMultilineText ()

hasMultilineText ()ไม่มีอาร์กิวเมนต์ ส่งคืนตัวจับคู่ซึ่งตรงกับ TextView ที่มีข้อความหลายบรรทัด โค้ดตัวอย่างมีดังนี้

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

hasBackground ()

hasBackground ()ยอมรับอาร์กิวเมนต์เดียวประเภท int และอาร์กิวเมนต์อ้างถึงรหัสทรัพยากรของทรัพยากรพื้นหลัง จะส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองตามทรัพยากรพื้นหลัง โค้ดตัวอย่างมีดังนี้

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 ()ไม่มีอาร์กิวเมนต์ จะส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่กำลังแสดงอยู่บนหน้าจออย่างสมบูรณ์ โค้ดตัวอย่างมีดังนี้

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

เปิดใช้งาน()

isEnabled ()ไม่มีอาร์กิวเมนต์ จะส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่เปิดใช้งาน โค้ดตัวอย่างมีดังนี้

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

isFocusable ()

isFocusable ()ไม่มีข้อโต้แย้ง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่มีตัวเลือกโฟกัส โค้ดตัวอย่างมีดังนี้

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

hasFocus ()

hasFocus ()ไม่มีข้อโต้แย้ง ส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่โฟกัสอยู่ในขณะนี้ โค้ดตัวอย่างมีดังนี้

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

isClickable ()

isClickable ()ไม่มีข้อโต้แย้ง จะส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่เป็นตัวเลือกคลิก โค้ดตัวอย่างมีดังนี้

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

isSelected ()

isSelected ()ไม่มีอาร์กิวเมนต์ จะส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่เลือกในปัจจุบัน โค้ดตัวอย่างมีดังนี้

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

isChecked ()

isChecked ()ไม่มีอาร์กิวเมนต์ มันส่งคืนตัวจับคู่ซึ่งตรงกับมุมมองที่เป็นประเภท CompoundButton (หรือประเภทย่อยของมัน) และอยู่ในสถานะที่ถูกตรวจสอบ โค้ดตัวอย่างมีดังนี้

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

isNotChecked ()

isNotChecked ()อยู่ตรงข้ามกับ isChecked โค้ดตัวอย่างมีดังนี้ * ดังนี้

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

isJavascriptEnabled ()

isJavascriptEnabled ()ไม่มีอาร์กิวเมนต์ จะส่งคืนตัวจับคู่ซึ่งตรงกับ WebView ที่กำลังประเมิน JavaScript โค้ดตัวอย่างมีดังนี้

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

กับผู้ปกครอง ()

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 ()ยอมรับอาร์กิวเมนต์ประเภท 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))))

เอสเพรสโซ่มีตัวเลือกที่หลากหลายเพื่อสร้างตัวเอง matchers มุมมองของเราเองและมันขึ้นอยู่กับHamcrest matchers Custom Matcher เป็นแนวคิดที่ทรงพลังมากในการขยายกรอบงานและยังปรับแต่งกรอบให้เข้ากับรสนิยมของเรา ข้อดีบางประการของการเขียนตัวจับคู่แบบกำหนดเองมีดังนี้

  • เพื่อใช้ประโยชน์จากคุณลักษณะเฉพาะของมุมมองที่กำหนดเองของเราเอง

  • Custom matcher ช่วยในกรณีทดสอบที่ใช้AdapterViewเพื่อจับคู่กับข้อมูลพื้นฐานประเภทต่างๆ

  • เพื่อลดความซับซ้อนของตัวจับคู่ปัจจุบันโดยการรวมคุณสมบัติของตัวจับคู่หลายตัว

เราสามารถสร้างตัวจับคู่ใหม่เมื่อความต้องการเกิดขึ้นและมันค่อนข้างง่าย ขอให้เราสร้างการจับคู่ที่กำหนดเองใหม่ซึ่งจะส่งกลับจับคู่เพื่อทดสอบทั้งรหัสและข้อความของTextView

Espresso มีสองคลาสต่อไปนี้เพื่อเขียนคู่หูใหม่ -

  • TypeSafeMatcher

  • BoundedMatcher

คลาสทั้งสองมีลักษณะคล้ายกันยกเว้นว่าBoundedMatcherจะจัดการการแคสต์ของอ็อบเจ็กต์อย่างโปร่งใสเพื่อแก้ไขประเภทโดยไม่ต้องตรวจสอบประเภทที่ถูกต้องด้วยตนเอง เราจะสร้างตัวจับคู่ใหม่ด้วยIdAndTextโดยใช้คลาสBoundedMatcher ให้เราตรวจสอบขั้นตอนในการเขียนตัวจับคู่ใหม่

  • เพิ่มการอ้างอิงด้านล่างในไฟล์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) {
      };
   }
}
  • แทนที่describeToและmatchesSafelyวิธีการในBoundedMatcherวัตถุ อธิบายถึงมีอาร์กิวเมนต์เดียวของประเภทคำอธิบายที่ไม่มีประเภทการส่งคืนและใช้เพื่อแสดงข้อผิดพลาดเกี่ยวกับข้อมูลที่ตรงกัน 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());
         }
      };
   }
}
  • ในที่สุดเราสามารถใช้ตัวจับคู่มิวของเราเพื่อเขียนกรณีทดสอบตามที่หว่านไว้ด้านล่าง

@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)ส่งกลับ matcher มุมมองที่จะตรงกับมุมมอง (จริง) มีรหัสแอตทริบิวต์เท่ากับmy_view

  • withText (“Hello”)นอกจากนี้ยังส่งกลับ matcher มุมมองที่จะตรงกับมุมมองแอตทริบิวต์ (คาดว่า) มีข้อความเท่ากับสวัสดี

  • ตรวจสอบเป็นวิธีการที่ยอมรับอาร์กิวเมนต์ประเภทViewAssertionและทำการยืนยันโดยใช้ส่งผ่านในออบเจ็กต์ViewAssertion

  • การจับคู่ (withText (“ สวัสดี”))ส่งคืนการยืนยันการดูซึ่งจะทำreal jobของการยืนยันว่าทั้งมุมมองจริง (พบโดยใช้withId ) และมุมมองที่คาดหวัง (พบโดยใช้withText ) เป็นหนึ่งเดียวกัน

ให้เราเรียนรู้วิธีการบางอย่างจากกรอบการทดสอบเอสเพรสโซเพื่อยืนยันการดูวัตถุ

ไม่ได้อยู่()

ส่งคืนการยืนยันมุมมองซึ่งทำให้แน่ใจว่าตัวจับคู่มุมมองไม่พบมุมมองที่ตรงกัน

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

ในกรณีนี้กรณีทดสอบจะช่วยให้มั่นใจได้ว่าไม่มีมุมมองที่มีข้อความสวัสดี

การแข่งขัน ()

ยอมรับตัวจับคู่มุมมองเป้าหมายและส่งคืนการยืนยันมุมมองซึ่งทำให้แน่ใจว่าตัวจับคู่มุมมอง (จริง) มีอยู่และตรงกับมุมมองที่จับคู่โดยตัวจับคู่มุมมองเป้าหมาย

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อยู่และอยู่ในตำแหน่งที่สมบูรณ์ดังต่อไปนี้รหัสมุมมองมี, 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อยู่และบางส่วนอยู่ในตำแหน่งดังต่อไปนี้รหัสมุมมองมี, 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” มีวิธีการดำเนินการซึ่งยอมรับการดำเนินการของมุมมองและเรียกใช้ / ดำเนินการอัตโนมัติของผู้ใช้ที่เกี่ยวข้องในสภาพแวดล้อมการทดสอบ ตัวอย่างเช่น“ click ()” เป็นการดำเนินการดูซึ่งเมื่อส่งผ่านไปยังเมธอด onView ( R.id.myButton ) .perform (click ())จะเริ่มการทำงานของเหตุการณ์การคลิกของปุ่ม (โดยมี id:“ myButton” ) ในสภาพแวดล้อมการทดสอบ

ในบทนี้ให้เราเรียนรู้เกี่ยวกับมุมมองการดำเนินการจากกรอบการทดสอบเอสเปรสโซ

typeText ()

typeText ()ยอมรับอาร์กิวเมนต์ (ข้อความ) หนึ่งรายการของประเภทStringและส่งกลับแอ็คชันมุมมอง แอ็คชันมุมมองที่ส่งคืนพิมพ์ข้อความที่ให้ไว้ในมุมมอง ก่อนวางข้อความมันจะแตะมุมมองหนึ่งครั้ง เนื้อหาอาจถูกวางไว้ที่ตำแหน่งใดก็ได้หากมีข้อความอยู่แล้ว

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

typeTextIntoFocusedView ()

typeTextIntoFocusedView ()คล้ายกับtypeText ()ยกเว้นว่าจะวางข้อความไว้ข้างตำแหน่งเคอร์เซอร์ในมุมมอง

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

แทนที่ข้อความ ()

replaceText ()คล้ายกับtypeText ()ยกเว้นว่าจะแทนที่เนื้อหาของมุมมอง

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

clearText ()

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 () ไม่มีอาร์กิวเมนต์และส่งคืนแอ็คชันมุมมองซึ่งจะคลิกปุ่มย้อนกลับ

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

pressBackUnconditionally ()

pressBackUnconditionally ()ไม่มีอาร์กิวเมนต์และส่งกลับแอ็คชันมุมมองซึ่งจะคลิกปุ่มย้อนกลับและจะไม่เกิดข้อยกเว้นหากการดำเนินการปุ่มย้อนกลับออกจากแอปพลิเคชันเอง

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

เปิดลิ้งค์()

openLink ()มีสองอาร์กิวเมนต์ อาร์กิวเมนต์แรก (ข้อความลิงก์) เป็นประเภทMatcherและอ้างถึงข้อความของแท็กจุดยึด HTML อาร์กิวเมนต์ที่สอง (url) เป็นประเภทMatcherและอ้างถึง url ของแท็กจุดยึด HTML ใช้ได้กับ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 ()มีหนึ่งอาร์กิวเมนต์ซึ่งอาจเป็นประเภทStringหรือ Matcher เป็นเพียงการตัดวิธีการopenLink * ให้สั้นลง

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

pressImeActionButton ()

pressImeActionButton ()มีการขัดแย้งใดและผลตอบแทนการกระทำมุมมองที่จะดำเนินการชุดการดำเนินการในหุ่นยนต์: 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 ()ไม่มีอาร์กิวเมนต์และส่งคืนแอ็คชันมุมมองซึ่งจะเริ่มการดำเนินการปัดไปทางขวาบนหน้าจอ

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

ปัดไปทางซ้าย()

SwipeLeft ()ไม่มีอาร์กิวเมนต์และส่งคืนแอ็คชันมุมมองซึ่งจะเริ่มการทำงานโดยปัดไปทางซ้ายบนหน้าจอ

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

AdapterViewเป็นชนิดพิเศษในมุมมองของการออกแบบเป็นพิเศษที่จะทำให้การเก็บรวบรวมข้อมูลที่คล้ายกันเช่นรายการสินค้าและรายชื่อผู้ใช้เรียกจากแหล่งข้อมูลพื้นฐานโดยใช้อะแดปเตอร์ แหล่งข้อมูลอาจเป็นรายการที่เรียบง่ายไปจนถึงรายการฐานข้อมูลที่ซับซ้อน บางส่วนของมุมมองที่ได้มาจากAdapterViewมีListView , GridViewและปินเนอร์

AdapterViewแสดงผลอินเทอร์เฟซผู้ใช้แบบไดนามิกโดยขึ้นอยู่กับจำนวนข้อมูลที่มีอยู่ในแหล่งข้อมูลพื้นฐาน นอกจากนี้AdapterView ยังแสดงเฉพาะข้อมูลที่จำเป็นขั้นต่ำซึ่งสามารถแสดงผลได้ในพื้นที่ที่มองเห็นได้ของหน้าจอ AdapterViewทำสิ่งนี้เพื่อประหยัดหน่วยความจำและทำให้อินเทอร์เฟซผู้ใช้ดูราบรื่นแม้ว่าข้อมูลพื้นฐานจะมีขนาดใหญ่ก็ตาม

ในการวิเคราะห์ลักษณะของสถาปัตยกรรมAdapterViewทำให้ตัวเลือกonViewและตัวจับคู่มุมมองไม่เกี่ยวข้องเนื่องจากมุมมองเฉพาะที่จะทดสอบอาจไม่แสดงผลเลยในตอนแรก โชคดีที่เอสเปรสโซมีวิธีการคือonData ( ) ซึ่งยอมรับตัวจับคู่แฮมเครสต์ (ที่เกี่ยวข้องกับประเภทข้อมูลของข้อมูลพื้นฐาน) เพื่อจับคู่ข้อมูลพื้นฐานและส่งคืนวัตถุประเภทDataInteraction ที่สอดคล้องกับมุมมอง o ข้อมูลที่ตรงกัน โค้ดตัวอย่างมีดังนี้

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

ที่นี่onData ()ตรงกับรายการ“ Apple” หากมีอยู่ในข้อมูลพื้นฐาน (รายการอาร์เรย์) และส่งคืนวัตถุDataInteractionเพื่อโต้ตอบกับมุมมองที่ตรงกัน (TextView ที่สอดคล้องกับรายการ“ Apple”)

วิธีการ

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เฉพาะตามการจับคู่มุมมองที่ส่งผ่านและส่งคืนวัตถุDataInteractionเพื่อโต้ตอบกับAdapterView ที่ตรงกัน

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

ที่ตำแหน่ง ()

สิ่งนี้ยอมรับอาร์กิวเมนต์ประเภทจำนวนเต็มและอ้างถึงตำแหน่งของรายการในข้อมูลพื้นฐาน มันเลือกมุมมองที่สอดคล้องกับค่าตำแหน่งที่ส่งผ่านของข้อมูลและส่งคืนออบเจ็กต์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 studio

  • สร้างโครงการใหม่ตามที่กล่าวไว้ก่อนหน้านี้และชื่อมันMyFruitApp

  • ย้ายแอปพลิเคชันไปยังเฟรมเวิร์ก AndroidX โดยใช้RefactorMigrate to AndroidX option menu

  • นำการออกแบบเริ่มต้นในกิจกรรมหลักและเพิ่ม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มีดังนี้

  • ตอนนี้เปิดไฟล์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เป็นมุมมองพิเศษที่จัดเตรียมโดย Android เพื่อแสดงหน้าเว็บภายในแอปพลิเคชัน WebViewไม่มีคุณสมบัติทั้งหมดของแอปพลิเคชันเบราว์เซอร์ที่มีคุณสมบัติครบถ้วนเช่น chrome และ firefox อย่างไรก็ตามมันให้การควบคุมอย่างสมบูรณ์สำหรับเนื้อหาที่จะแสดงและเปิดเผยคุณลักษณะทั้งหมดของ Android ที่จะเรียกใช้ในหน้าเว็บ เปิดใช้งานWebViewและจัดเตรียมสภาพแวดล้อมพิเศษที่สามารถออกแบบ UI ได้อย่างง่ายดายโดยใช้เทคโนโลยี HTML และคุณสมบัติดั้งเดิมเช่นกล้องถ่ายรูปและหมุนผู้ติดต่อ ชุดคุณลักษณะนี้ช่วยให้WebViewสามารถจัดหาแอปพลิเคชันรูปแบบใหม่ที่เรียกว่าแอปพลิเคชันแบบไฮบริดโดยที่ UI จะทำใน HTML และตรรกะทางธุรกิจจะทำในJavaScriptหรือผ่านปลายทาง API ภายนอก

โดยปกติการทดสอบWebViewจะต้องเป็นเรื่องท้าทายเนื่องจากใช้เทคโนโลยี HTML สำหรับองค์ประกอบอินเทอร์เฟซผู้ใช้แทนที่จะใช้อินเทอร์เฟซ / มุมมองผู้ใช้ดั้งเดิม เอสเปรสโซมีความสามารถในด้านนี้ด้วยการมอบชุดใหม่ให้กับผู้จับคู่เว็บและการยืนยันทางเว็บซึ่งมีเจตนาคล้ายกับผู้จับคู่การดูพื้นเมืองและการยืนยันการดู ในขณะเดียวกันก็ให้แนวทางที่สมดุลโดยรวมสภาพแวดล้อมการทดสอบตามเทคโนโลยีเว็บด้วยเช่นกัน

เว็บ Espresso สร้างขึ้นจากเฟรมเวิร์กWebDriver Atom ซึ่งใช้ในการค้นหาและจัดการองค์ประกอบของเว็บ Atomคล้ายกับการดูการกระทำ Atom จะทำการโต้ตอบทั้งหมดภายในเว็บเพจ WebDriverแสดงชุดวิธีการที่กำหนดไว้ล่วงหน้าเช่นfindElement () , getElement ()เพื่อค้นหาองค์ประกอบของเว็บและส่งคืนอะตอมที่เกี่ยวข้อง (เพื่อดำเนินการในหน้าเว็บ)

ข้อความทดสอบเว็บมาตรฐานดูเหมือนโค้ดด้านล่าง

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

ที่นี่

  • onWebView () - คล้ายกับ onView () ซึ่งจะแสดงชุดของ API เพื่อทดสอบ WebView

  • withElement () - หนึ่งในหลายวิธีที่ใช้ในการค้นหาองค์ประกอบของเว็บภายในเว็บเพจโดยใช้ Atom และส่งคืนอ็อบเจ็กต์ WebInteration ซึ่งคล้ายกับ ViewInteraction

  • ดำเนินการ () - ดำเนินการดำเนินการภายในเว็บเพจโดยใช้ Atom และส่งคืน WebInteraction

  • check () - นี่เป็นการยืนยันที่จำเป็นโดยใช้ WebAssertion

ตัวอย่างโค้ดทดสอบเว็บมีดังนี้

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

ที่นี่

  • findElement ()ค้นหาองค์ประกอบและส่งคืน Atom

  • webMatchesคล้ายกับวิธีการจับคู่

เขียนใบสมัครตัวอย่าง

ขอให้เราเขียนโปรแกรมที่ง่ายซึ่งเป็นไปตาม WebView และเขียนกรณีทดสอบโดยใช้onWebView ()วิธีการ ทำตามขั้นตอนเหล่านี้เพื่อเขียนแอปพลิเคชันตัวอย่าง -

  • เริ่ม Android studio

  • สร้างโครงการใหม่ตามที่กล่าวไว้ก่อนหน้านี้และชื่อมันMyWebViewApp

  • ย้ายแอปพลิเคชันไปยังเฟรมเวิร์ก AndroidX โดยใช้RefactorMigrate to AndroidX option menu

  • เพิ่มตัวเลือกการกำหนดค่าด้านล่างในไฟล์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วิธีการในการดำเนินการเชื่อมโยงในการโหลดเดียวกันWebView ; มิฉะนั้นจะเปิดหน้าต่างเบราว์เซอร์ใหม่นอกแอปพลิเคชัน วางไว้ในMainActivity.java

private class ExtendedWebViewClient extends WebViewClient {
   @Override
   public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
   }
}
  • ตอนนี้เพิ่มด้านล่างรหัสในวิธี onCreate ของMainActivity วัตถุประสงค์ของรหัสคือการค้นหา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>
  • เนื้อหาของไฟล์apple.html ที่อ้างถึงในindex.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 ในเว็บเซิร์ฟเวอร์

  • แทนที่ url ในเมธอด loadUrl ด้วย 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")));
}

ที่นี่การทดสอบเสร็จสิ้นตามลำดับต่อไปนี้

  • พบลิงค์แอปเปิ้ลใช้แอตทริบิวต์ id ผ่านfindElement () method และLocator.ID enumeration

  • ตรวจสอบข้อความของการเชื่อมโยงโดยใช้ที่webMatches ()วิธีการ

  • ดำเนินการคลิกที่ลิงค์ จะเปิดหน้าapple.html

  • พบองค์ประกอบ h1 อีกครั้งโดยใช้เมธอด findElement () และLocator.TAG_NAME enumeration

  • ในที่สุดอีกครั้งตรวจสอบข้อความของh1แท็กใช้webMatches ()วิธีการ

  • สุดท้ายเรียกใช้กรณีทดสอบโดยใช้เมนูบริบทของสตูดิโอ Android

ในบทนี้เราจะเรียนรู้วิธีทดสอบการทำงานแบบอะซิงโครนัสโดยใช้ Espresso Idling Resources

ความท้าทายประการหนึ่งของแอปพลิเคชันสมัยใหม่คือการมอบประสบการณ์การใช้งานที่ราบรื่น การมอบประสบการณ์การใช้งานที่ราบรื่นนั้นเกี่ยวข้องกับการทำงานอยู่เบื้องหลังเป็นจำนวนมากเพื่อให้แน่ใจว่าขั้นตอนการสมัครใช้เวลาไม่เกินสองสามมิลลิวินาที งานเบื้องหลังมีตั้งแต่งานง่ายๆไปจนถึงงานที่มีราคาแพงและซับซ้อนในการดึงข้อมูลจาก API / ฐานข้อมูลระยะไกล ในการเผชิญหน้ากับความท้าทายในอดีตนักพัฒนาเคยเขียนงานที่ต้องเสียค่าใช้จ่ายและใช้งานมานานในเธรดพื้นหลังและซิงค์กับUIThreadหลักเมื่อเธรดพื้นหลังเสร็จสิ้น

หากการพัฒนาแอปพลิเคชันแบบมัลติเธรดมีความซับซ้อนการเขียนกรณีทดสอบก็ยิ่งซับซ้อนมากขึ้น ตัวอย่างเช่นเราไม่ควรทดสอบAdapterViewก่อนที่ข้อมูลที่จำเป็นจะถูกโหลดจากฐานข้อมูล หากดึงข้อมูลในเธรดแยกต่างหากการทดสอบจะต้องรอจนกว่าเธรดจะเสร็จสมบูรณ์ ดังนั้นสภาพแวดล้อมการทดสอบควรซิงค์ระหว่างเธรดพื้นหลังและเธรด UI Espresso ให้การสนับสนุนที่ดีเยี่ยมสำหรับการทดสอบแอปพลิเคชันแบบมัลติเธรด แอปพลิเคชันใช้เธรดด้วยวิธีต่อไปนี้และเอสเปรสโซรองรับทุกสถานการณ์

เธรดอินเทอร์เฟซผู้ใช้

Android SDK ใช้เป็นการภายในเพื่อมอบประสบการณ์การใช้งานที่ราบรื่นด้วยองค์ประกอบ UI ที่ซับซ้อน เอสเปรสโซสนับสนุนสถานการณ์นี้อย่างโปร่งใสและไม่ต้องการการกำหนดค่าและการเข้ารหัสพิเศษใด ๆ

งาน Async

ภาษาโปรแกรมสมัยใหม่รองรับการเขียนโปรแกรมแบบ async เพื่อทำเธรดน้ำหนักเบาโดยไม่ต้องมีความซับซ้อนของการเขียนโปรแกรมเธรด งาน Async ได้รับการสนับสนุนอย่างโปร่งใสโดยกรอบเอสเปรสโซ

เธรดผู้ใช้

นักพัฒนาอาจเริ่มเธรดใหม่เพื่อดึงข้อมูลที่ซับซ้อนหรือขนาดใหญ่จากฐานข้อมูล เพื่อรองรับสถานการณ์นี้เอสเปรสโซให้แนวคิดทรัพยากรที่ไม่ได้ใช้งาน

ให้ใช้เรียนรู้แนวคิดของทรัพยากรที่ไม่ทำงานและวิธีการใช้งานในบทนี้

ภาพรวม

แนวคิดของทรัพยากรที่ไม่ทำงานนั้นเรียบง่ายและใช้งานง่าย แนวคิดพื้นฐานคือการสร้างตัวแปร (ค่าบูลีน) เมื่อใดก็ตามที่กระบวนการที่รันเป็นเวลานานเริ่มต้นในเธรดแยกต่างหากเพื่อระบุว่ากระบวนการกำลังทำงานอยู่หรือไม่และลงทะเบียนในสภาพแวดล้อมการทดสอบ ในระหว่างการทดสอบผู้ทดสอบจะตรวจสอบตัวแปรที่ลงทะเบียนหากพบแล้วจะพบสถานะการทำงาน หากสถานะการวิ่งเป็นจริงนักวิ่งทดสอบจะรอจนกว่าสถานะจะกลายเป็นเท็จ

Espresso มีอินเทอร์เฟซ IdlingResources สำหรับจุดประสงค์ในการรักษาสถานะการทำงาน วิธีการหลักในการดำเนินการคือ isIdleNow () ถ้า isIdleNow () คืนค่าเป็นจริงเอสเปรสโซจะดำเนินการทดสอบต่อหรือมิฉะนั้นจะรอจนกว่า isIdleNow () จะส่งคืนเท็จ เราจำเป็นต้องใช้ IdlingResources และใช้คลาสที่ได้รับ นอกจากนี้เอสเปรสโซยังมีการใช้งาน IdlingResources ในตัวเพื่อลดภาระงานของเรา มีดังนี้

CountingIdlingResource

สิ่งนี้รักษาการนับภายในของงานที่กำลังรันอยู่ มันเสี่ยงที่เพิ่มขึ้น ()และลดลง ()วิธีการ Increment ()เพิ่มหนึ่งในตัวนับและการลด ()ลบหนึ่งตัวออกจากตัวนับ isIdleNow ()คืนค่าจริงเฉพาะเมื่อไม่มีงานแอ็คทีฟ

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

แอปพลิเคชันตัวอย่าง

ให้เราสร้างแอปพลิเคชั่นง่ายๆเพื่อแสดงรายการผลไม้โดยดึงมาจากบริการเว็บในเธรดแยกต่างหากจากนั้นทดสอบโดยใช้แนวคิดทรัพยากรที่ไม่ได้ใช้งาน

  • เริ่ม Android studio

  • สร้างโครงการใหม่ตามที่กล่าวไว้ก่อนหน้านี้และตั้งชื่อว่า MyIdlingFruitApp

  • ย้ายแอปพลิเคชันไปยังเฟรมเวิร์ก AndroidX โดยใช้Refactor → Migrate to AndroidX option menu

  • เพิ่มไลบรารีทรัพยากรที่ไม่ได้ใช้งานเอสเปรสโซในแอพ / 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;
   }
}
  • ประกาศตัวแปรส่วนกลางmIdlingResourceประเภทCountingIdlingResourceในคลาสMainActivityดังต่อไปนี้

@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: // <โดเมนของคุณหรือ 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 Intent ใช้เพื่อเปิดกิจกรรมใหม่ทั้งภายใน (การเปิดหน้าจอรายละเอียดผลิตภัณฑ์จากหน้าจอรายการผลิตภัณฑ์) หรือภายนอก (เช่นการเปิดแป้นหมุนเพื่อโทรออก) กิจกรรมความตั้งใจภายในได้รับการจัดการอย่างโปร่งใสโดยกรอบการทดสอบเอสเพรสโซและไม่จำเป็นต้องมีการทำงานที่เฉพาะเจาะจงจากฝั่งผู้ใช้ อย่างไรก็ตามการเรียกใช้กิจกรรมภายนอกถือเป็นความท้าทายอย่างแท้จริงเพราะมันอยู่นอกขอบเขตของเราแอปพลิเคชันที่อยู่ระหว่างการทดสอบ เมื่อผู้ใช้เรียกใช้แอปพลิเคชันภายนอกและออกจากแอปพลิเคชันภายใต้การทดสอบโอกาสที่ผู้ใช้จะกลับมาที่แอปพลิเคชันโดยมีลำดับการดำเนินการที่กำหนดไว้ล่วงหน้าค่อนข้างน้อย ดังนั้นเราต้องถือว่าการกระทำของผู้ใช้ก่อนที่จะทดสอบแอปพลิเคชัน เอสเปรสโซมีสองทางเลือกในการจัดการกับสถานการณ์นี้ มีดังนี้

ตั้งใจ

สิ่งนี้ช่วยให้ผู้ใช้ตรวจสอบว่าเจตนาที่ถูกต้องถูกเปิดจากแอปพลิเคชันที่อยู่ระหว่างการทดสอบ

ตั้งใจ

สิ่งนี้ช่วยให้ผู้ใช้สามารถล้อเลียนกิจกรรมภายนอกเช่นถ่ายภาพจากกล้องหมุนหมายเลขจากรายชื่อผู้ติดต่อ ฯลฯ และกลับไปที่แอปพลิเคชันพร้อมชุดค่าที่กำหนดไว้ล่วงหน้า (เช่นภาพที่กำหนดไว้ล่วงหน้าจากกล้องแทนภาพจริง) .

ติดตั้ง

Espresso รองรับตัวเลือกความตั้งใจผ่านไลบรารีปลั๊กอินและไลบรารีจะต้องได้รับการกำหนดค่าในไฟล์ gradle ของแอปพลิเคชัน ตัวเลือกการกำหนดค่ามีดังนี้

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

ตั้งใจ ()

ปลั๊กอิน Espresso Intent ให้ตัวจับคู่พิเศษเพื่อตรวจสอบว่าเจตนาที่เรียกนั้นเป็นเจตนาที่คาดหวังหรือไม่ ผู้จับคู่ที่ระบุและวัตถุประสงค์ของผู้จับคู่มีดังนี้

hasAction

สิ่งนี้ยอมรับการดำเนินการตามเจตนาและส่งคืนตัวจับคู่ซึ่งตรงกับเจตนาที่ระบุ

hasData

สิ่งนี้ยอมรับข้อมูลและส่งคืนตัวจับคู่ซึ่งตรงกับข้อมูลที่ให้ไว้กับเจตนาในขณะที่เรียกใช้

toPackage

สิ่งนี้ยอมรับชื่อแพ็กเกจความตั้งใจและส่งคืนตัวจับคู่ซึ่งตรงกับชื่อแพ็กเกจของเจตนาที่เรียกใช้

ตอนนี้ให้เราสร้างแอปพลิเคชันใหม่และทดสอบแอปพลิเคชันสำหรับกิจกรรมภายนอกโดยใช้ตั้งใจ ()เพื่อทำความเข้าใจแนวคิด

  • เริ่ม Android studio

  • สร้างโครงการใหม่ตามที่กล่าวไว้ก่อนหน้านี้และตั้งชื่อว่า IntentSampleApp

  • ย้ายแอปพลิเคชันไปยังเฟรมเวิร์ก AndroidX โดยใช้Refactor → Migrate to AndroidX option menu

  • สร้างกล่องข้อความปุ่มเพื่อเปิดรายชื่อผู้ติดต่อและอีกช่องหนึ่งเพื่อโทรออกโดยเปลี่ยน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>
  • ตอนนี้เพิ่มด้านล่างรหัสในกิจกรรมหลัก ( MainActivity.java ) ภายใต้onCreateวิธี

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 ปุ่มเพื่อโทรออก

  • เพิ่มตัวแปรคงREQUEST_CODEในคลาสMainActivityดังที่แสดงด้านล่าง

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • ตอนนี้เพิ่มเมธอดonActivityResultในคลาสMainActivityดังต่อไปนี้

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'
}
  • คลิกตัวเลือกเมนูSync Now ที่ Android Studio ให้มา สิ่งนี้จะดาวน์โหลดไลบรารีการทดสอบความตั้งใจและกำหนดค่าอย่างถูกต้อง

  • เปิด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
}
  • แก้ไขปัญหาการนำเข้าโดยใช้ตัวเลือก Alt + Enter ที่จัดเตรียมโดย android studio หรืออื่น ๆ รวมถึงคำสั่งการนำเข้าด้านล่าง

import android.content.Context;
import android.content.Intent;

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

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

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • เพิ่มกรณีทดสอบด้านล่างเพื่อทดสอบว่าโทรออกถูกเรียกอย่างถูกต้องหรือไม่

public class ExampleInstrumentedTest {
   // ... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
   // ... code
}

ที่นี่มีการใช้ตัวจับคู่hasAction , hasDataและtoPackageร่วมกับตัวจับคู่allOfเพื่อให้สำเร็จก็ต่อเมื่อตัวจับคู่ทั้งหมดถูกส่งผ่าน

  • ตอนนี้เรียกใช้ExampleInstrumentedTestผ่านเมนูเนื้อหาใน Android studio

ตั้งใจ ()

เอสเปรสโซมีวิธีพิเศษ - ตั้งใจ ()เพื่อล้อเลียนการกระทำโดยเจตนาภายนอก ตั้งใจ ()ยอมรับชื่อแพคเกจของเจตนาที่จะล้อเลียนและให้วิธีการ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)));
}

ที่นี่เราได้สร้างความตั้งใจและการตั้งค่าตอบแทนใหม่ (เมื่ออัญเชิญเจตนา) เป็นรายการแรกของรายชื่อผู้ติดต่อของเนื้อหา: //com.android.contacts/data/1 จากนั้นเราได้ตั้งค่าวิธีการตั้งใจที่จะล้อเลียนเจตนาที่สร้างขึ้นใหม่แทนรายชื่อผู้ติดต่อ ตั้งค่าและเรียกใช้ความตั้งใจที่สร้างขึ้นใหม่ของเราเมื่อมีการเรียกใช้แพ็คเกจcom.android.contactsและรายการแรกเริ่มต้นของรายการจะถูกส่งกลับ จากนั้นเราจึงเริ่มการดำเนินการclick ()เพื่อเริ่มเจตนาล้อเลียนและสุดท้ายตรวจสอบว่าหมายเลขโทรศัพท์จากการเรียกเจตนาล้อเลียนและหมายเลขของรายการแรกในรายชื่อผู้ติดต่อนั้นเหมือนกันหรือไม่

มีปัญหาการนำเข้าที่ขาดหายไปจากนั้นแก้ไขปัญหาการนำเข้าเหล่านั้นโดยใช้ตัวเลือก Alt + Enter ที่จัดทำโดย android studio หรืออื่น ๆ รวมถึงคำสั่งการนำเข้าด้านล่าง

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 Automatorช่วยให้เราทดสอบแอปพลิเคชัน UI Automatorถือได้ว่าเป็นคู่หูที่ดีสำหรับกรอบการทดสอบเอสเพรสโซ เราสามารถใช้ประโยชน์จากความประสงค์ ()ตัวเลือกในกรอบการทดสอบเอสเพรสโซก่อนการเลือกสำหรับUI อัตโนมัติ

คำแนะนำในการติดตั้ง

Android มี UI Automator เป็นปลั๊กอินแยกต่างหาก จำเป็นต้องกำหนดค่าในapp / build.gradleตามที่ระบุด้านล่าง

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

เวิร์กโฟลว์สำหรับการเขียนกรณีทดสอบ

ให้เราเข้าใจวิธีการเขียนกรณีทดสอบตามUI Automator

  • รับวัตถุUiDeviceโดยเรียกใช้เมธอดgetInstance ()และส่งผ่านวัตถุInstrumentation

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 ()เพื่อแก้ไขช่องข้อความและคลิก ()เพื่อเริ่มเหตุการณ์การคลิกของปุ่ม

if(button.exists() && button.isEnabled()) {
   button.click();
}
  • สุดท้ายเราตรวจสอบว่า UI แสดงสถานะที่คาดไว้หรือไม่

กรณีทดสอบการเขียนเป็นงานที่น่าเบื่อ แม้ว่าเอสเปรสโซจะให้ API ที่ง่ายและยืดหยุ่น แต่กรณีทดสอบการเขียนอาจเป็นงานที่น่าเกียจและใช้เวลานาน เพื่อเอาชนะสิ่งนี้สตูดิโอ Android มีคุณลักษณะในการบันทึกและสร้างกรณีทดสอบเอสเปรสโซ Record Espresso Testอยู่ในเมนูRun

ให้เราบันทึกกรณีทดสอบง่ายๆในHelloWorldAppของเราโดยทำตามขั้นตอนที่อธิบายไว้ด้านล่าง

  • เปิดสตูดิโอ Android ตามด้วยแอปพลิเคชันHelloWorldApp

  • คลิกRunบันทึกเอสเพรสโซ่การทดสอบและเลือกMainActivity

  • บันทึกภาพหน้าจอจะเป็นดังนี้

  • คลิกเพิ่มยืนยัน จะเปิดหน้าจอแอปพลิเคชันดังที่แสดงด้านล่าง

  • คลิกHello World! . บันทึกหน้าจอเพื่อเลือกมุมมองข้อความมีดังนี้

  • คลิกบันทึกการยืนยันอีกครั้งซึ่งจะบันทึกการยืนยันและแสดงดังต่อไปนี้

  • คลิกตกลง มันจะเปิดหน้าต่างใหม่และถามชื่อของกรณีทดสอบ ชื่อเริ่มต้นคือ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 เฟรมต่อวินาทีเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดี

ให้เราเรียนรู้บางตัวเลือกที่มีอยู่ใน Android เพื่อวิเคราะห์ประสิทธิภาพ UI ในบทนี้

ทิ้ง

dumpsysเป็นเครื่องมือในตัวที่มีอยู่ในอุปกรณ์ Android แสดงข้อมูลปัจจุบันเกี่ยวกับบริการระบบ dumpsysมีตัวเลือกในการถ่ายโอนข้อมูลเกี่ยวกับหมวดหมู่เฉพาะ การผ่านgfxinfoจะให้ข้อมูลภาพเคลื่อนไหวของแพ็คเกจที่ให้มา คำสั่งมีดังนี้

> adb shell dumpsys gfxinfo <PACKAGE_NAME>

Framestats

framestatsเป็นตัวเลือกของคำสั่ง dumpsys เมื่อมีการเรียกใช้dumpsysด้วยframestatsระบบจะถ่ายโอนข้อมูลเวลาเฟรมโดยละเอียดของเฟรมล่าสุด คำสั่งมีดังนี้

> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

แสดงข้อมูลเป็น CSV (ค่าที่คั่นด้วยจุลภาค) ผลลัพธ์ในรูปแบบ CSV ช่วยให้สามารถส่งข้อมูลไปยัง excel ได้อย่างง่ายดายและแยกข้อมูลที่เป็นประโยชน์ผ่านสูตรและแผนภูมิ excel

systrace

systraceยังเป็นเครื่องมือในการสร้างที่มีอยู่ในอุปกรณ์ Android จับและแสดงเวลาดำเนินการของกระบวนการแอปพลิเคชัน systraceสามารถเรียกใช้โดยใช้คำสั่งด้านล่างในเทอร์มินัลของ android studio

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

คุณสมบัติการเข้าถึงเป็นหนึ่งในคุณสมบัติหลักสำหรับแอปพลิเคชันใด ๆ แอปพลิเคชันที่พัฒนาโดยผู้จำหน่ายควรสนับสนุนแนวทางการเข้าถึงขั้นต่ำที่กำหนดโดย android SDK เพื่อให้เป็นแอปพลิเคชันที่ประสบความสำเร็จและมีประโยชน์ การปฏิบัติตามมาตรฐานการช่วยสำหรับการเข้าถึงมีความสำคัญมากและไม่ใช่เรื่องง่าย Android SDK ให้การสนับสนุนที่ดีเยี่ยมโดยให้มุมมองที่ออกแบบมาอย่างเหมาะสมเพื่อสร้างอินเทอร์เฟซผู้ใช้ที่สามารถเข้าถึงได้

ในทำนองเดียวกันกรอบการทดสอบ Espresso เป็นประโยชน์อย่างยิ่งสำหรับทั้งนักพัฒนาและผู้ใช้ปลายทางด้วยการสนับสนุนคุณสมบัติการทดสอบการเข้าถึงในเครื่องมือทดสอบหลักอย่างโปร่งใส

ใน Espresso นักพัฒนาสามารถเปิดใช้งานและกำหนดค่าการทดสอบการเข้าถึงผ่านคลาสAccessibilityChecks โค้ดตัวอย่างมีดังนี้

AccessibilityChecks.enable();

ตามค่าเริ่มต้นการตรวจสอบการช่วยสำหรับการเข้าถึงจะทำงานเมื่อคุณดำเนินการดู การตรวจสอบรวมถึงมุมมองที่ดำเนินการดำเนินการตลอดจนมุมมองที่สืบทอดมาทั้งหมด คุณสามารถตรวจสอบลำดับชั้นมุมมองทั้งหมดของหน้าจอโดยใช้รหัสต่อไปนี้ -

AccessibilityChecks.enable().setRunChecksFromRootView(true);

สรุป

Espresso เป็นเครื่องมือที่ยอดเยี่ยมสำหรับนักพัฒนาแอนดรอยด์ในการทดสอบแอปพลิเคชันของพวกเขาอย่างสมบูรณ์ด้วยวิธีที่ง่ายมากและไม่ต้องใช้ความพยายามเพิ่มเติมตามปกติตามกรอบการทดสอบ มีเครื่องบันทึกเพื่อสร้างกรณีทดสอบโดยไม่ต้องเขียนรหัสด้วยตนเอง นอกจากนี้ยังรองรับการทดสอบอินเทอร์เฟซผู้ใช้ทุกประเภท ด้วยการใช้กรอบการทดสอบเอสเปรสโซนักพัฒนา Android สามารถพัฒนาแอปพลิเคชันที่ดูดีและแอปพลิเคชันที่ประสบความสำเร็จได้อย่างมั่นใจโดยไม่มีปัญหาใด ๆ ในช่วงเวลาสั้น ๆ