กรอบการทดสอบ 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โดยคลิกที่Refactor → Migrate 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 โดยใช้Refactor → Migrate 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 โดยใช้Refactor → Migrate 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 สามารถพัฒนาแอปพลิเคชันที่ดูดีและแอปพลิเคชันที่ประสบความสำเร็จได้อย่างมั่นใจโดยไม่มีปัญหาใด ๆ ในช่วงเวลาสั้น ๆ