บทช่วยสอนการทดสอบ Android: การทดสอบหน่วยอย่าง True Green Droid
เผยแพร่แล้ว: 2022-03-11ในฐานะนักพัฒนาแอปที่มีประสบการณ์ เมื่อแอปพลิเคชันที่เราพัฒนาเติบโตเต็มที่ เรามีความรู้สึกว่าถึงเวลาที่จะเริ่มการทดสอบแล้ว กฎเกณฑ์ทางธุรกิจมักบอกเป็นนัยว่าระบบต้องให้ความเสถียรตลอดรุ่นต่างๆ นอกจากนี้เรายังต้องการให้กระบวนการสร้างเป็นอัตโนมัติและเผยแพร่แอปพลิเคชันโดยอัตโนมัติ สำหรับสิ่งนี้ เราจำเป็นต้องมีเครื่องมือทดสอบ Adnroid เพื่อรับประกันว่าบิลด์จะทำงานตามที่คาดไว้
การทดสอบช่วยเพิ่มความมั่นใจให้กับสิ่งที่เราสร้างขึ้นได้ เป็นเรื่องยาก (ถ้าเป็นไปไม่ได้) ในการสร้างผลิตภัณฑ์ที่สมบูรณ์แบบและไม่มีข้อบกพร่อง ดังนั้น เป้าหมายของเราคือการปรับปรุงโอกาสในการประสบความสำเร็จในตลาดโดยการตั้งค่าชุดทดสอบที่จะระบุจุดบกพร่องที่เพิ่งเปิดตัวใหม่อย่างรวดเร็วในแอปพลิเคชันของเรา
เมื่อพูดถึง Android และแพลตฟอร์มมือถือต่างๆ โดยทั่วไป การทดสอบแอปอาจเป็นเรื่องที่ท้าทาย การใช้การทดสอบหน่วยและการปฏิบัติตามหลักการของการพัฒนาที่ขับเคลื่อนด้วยการทดสอบหรือสิ่งที่คล้ายคลึงกันมักจะรู้สึกว่าไม่ได้ใช้งานง่ายอย่างน้อย อย่างไรก็ตาม การทดสอบมีความสำคัญ และไม่ควรมองข้ามหรือมองข้ามไป David, Kent และ Martin ได้พูดคุยถึงประโยชน์และข้อผิดพลาดของการทดสอบในชุดการสนทนาระหว่างกันซึ่งรวบรวมไว้ในบทความเรื่อง "TDD ตายหรือไม่" คุณยังสามารถค้นหาการสนทนาทางวิดีโอจริงได้ที่นั่น และรับข้อมูลเชิงลึกมากขึ้นหากการทดสอบเหมาะสมกับกระบวนการพัฒนาของคุณและคุณสามารถรวมเข้ากับขอบเขตใดได้บ้าง เริ่มตั้งแต่ตอนนี้
ในบทช่วยสอนการทดสอบ Android นี้ ฉันจะแนะนำคุณเกี่ยวกับหน่วยการเรียนรู้และการยอมรับ การทดสอบการถดถอยบน Android เราจะเน้นที่การทำให้เป็นนามธรรมของหน่วยการทดสอบบน Android ตามด้วยตัวอย่างการทดสอบการยอมรับ โดยมุ่งเน้นที่การทำให้กระบวนการรวดเร็วและเรียบง่ายที่สุดเท่าที่จะทำได้เพื่อลดรอบการตอบกลับของ Developer-QA
ฉันควรอ่านหรือไม่
บทแนะนำนี้จะสำรวจความเป็นไปได้ต่างๆ ในการทดสอบแอปพลิเคชัน Android นักพัฒนาหรือผู้จัดการโครงการที่ต้องการทำความเข้าใจความเป็นไปได้ในการทดสอบปัจจุบันของแพลตฟอร์ม Android สามารถตัดสินใจโดยใช้บทช่วยสอนนี้หากต้องการใช้แนวทางใด ๆ ที่กล่าวถึงในบทความนี้ อย่างไรก็ตาม นี่ไม่ใช่สัญลักษณ์แสดงหัวข้อย่อยสีเงิน เนื่องจากการอภิปรายที่เกี่ยวข้องในหัวข้อดังกล่าวโดยเนื้อแท้แตกต่างกันไปในแต่ละผลิตภัณฑ์พร้อมกับกำหนดเวลา คุณภาพของโค้ดเบสโค้ด ระดับการเชื่อมต่อของระบบ ความชอบของนักพัฒนาในการออกแบบสถาปัตยกรรม อายุการใช้งานที่คาดการณ์ไว้ของคุณลักษณะเป็น ทดสอบ ฯลฯ
การคิดในหน่วย: การทดสอบ Android
ตามหลักการแล้ว เราต้องการทดสอบหนึ่งหน่วยเชิงตรรกะ/ส่วนประกอบหนึ่งของสถาปัตยกรรมอย่างอิสระ ด้วยวิธีนี้ เรารับประกันได้ว่าคอมโพเนนต์ของเราทำงานอย่างถูกต้องสำหรับชุดอินพุตที่เราคาดหวัง การอ้างอิงสามารถจำลองได้ ซึ่งจะทำให้เราสามารถเขียนการทดสอบที่ดำเนินการได้อย่างรวดเร็ว นอกจากนี้ เราจะสามารถจำลองสถานะระบบต่างๆ ตามข้อมูลป้อนเข้าในการทดสอบ ซึ่งครอบคลุมกรณีที่แปลกใหม่ในกระบวนการ
เป้าหมายของการทดสอบหน่วย Android คือการแยกแต่ละส่วนของโปรแกรมและแสดงว่าแต่ละส่วนถูกต้อง การทดสอบหน่วยให้สัญญาที่เข้มงวดและเป็นลายลักษณ์อักษรซึ่งชิ้นส่วนของรหัสต้องปฏิบัติตาม ส่งผลให้มีประโยชน์หลายประการ —วิกิพีเดีย
หุ่นยนต์ไฟฟ้า
Robolectric เป็นเฟรมเวิร์กการทดสอบหน่วย Android ที่ให้คุณเรียกใช้การทดสอบภายใน JVM บนเวิร์กสเตชันการพัฒนาของคุณ Robolectric เขียนคลาส Android SDK ใหม่ขณะที่กำลังโหลด และทำให้สามารถรันบน JVM ปกติได้ ส่งผลให้เวลาทดสอบรวดเร็ว นอกจากนี้ ยังจัดการอัตราเงินเฟ้อของมุมมอง การโหลดทรัพยากร และสิ่งอื่น ๆ ที่ใช้ในโค้ด C ดั้งเดิมบนอุปกรณ์ Android ทำให้ความต้องการอีมูเลเตอร์และอุปกรณ์ทางกายภาพเพื่อเรียกใช้การทดสอบอัตโนมัติล้าสมัย
ม็อกคิโต
Mockito เป็นเฟรมเวิร์กเยาะเย้ยที่ช่วยให้เราสามารถเขียนการทดสอบที่สะอาดในจาวา ช่วยลดความยุ่งยากในกระบวนการสร้างชุดทดสอบคู่ (จำลอง) ซึ่งใช้เพื่อแทนที่การขึ้นต่อกันดั้งเดิมของส่วนประกอบ/โมดูลที่ใช้ในการผลิต คำตอบ StackOverflow กล่าวถึงความแตกต่างระหว่าง mocks และ stubs ในแง่ที่ค่อนข้างง่าย ซึ่งคุณสามารถอ่านเพื่อเรียนรู้เพิ่มเติม
// you can mock concrete classes, not only interfaces LinkedList mockedList = mock(LinkedList.class); // stubbing appears before the actual execution when(mockedList.get(0)).thenReturn("first"); // the following prints "first" System.out.println(mockedList.get(0)); // the following prints "null" because get(999) was not stubbed System.out.println(mockedList.get(999));
นอกจากนี้ ด้วย Mockito เราสามารถตรวจสอบได้ว่ามีการเรียกใช้เมธอดหรือไม่:
// mock creation List mockedList = mock(List.class); // using mock object - it does not throw any "unexpected interaction" exception mockedList.add("one"); mockedList.clear(); // selective, explicit, highly readable verification verify(mockedList).add("one"); verify(mockedList).clear();
ตอนนี้ เรารู้ว่าเราสามารถระบุคู่การกระทำ-ปฏิกิริยาที่กำหนดสิ่งที่เกิดขึ้นเมื่อเราดำเนินการเฉพาะบนวัตถุ/ส่วนประกอบที่จำลอง ดังนั้น เราสามารถจำลองโมดูลทั้งหมดของแอปพลิเคชันของเรา และสำหรับกรณีทดสอบแต่ละกรณี ทำให้โมดูลที่จำลองมีปฏิกิริยาแตกต่างกัน วิธีต่างๆ จะสะท้อนถึงสถานะที่เป็นไปได้ของส่วนประกอบที่ทดสอบและคู่ส่วนประกอบที่จำลอง
การทดสอบหน่วย
ในส่วนนี้ เราจะถือว่าสถาปัตยกรรม MVP (Model View Presenter) กิจกรรมและส่วนย่อยคือมุมมอง โมเดลเป็นชั้นที่เก็บสำหรับการเรียกใช้ฐานข้อมูลหรือบริการระยะไกล และผู้นำเสนอคือ "สมอง" ที่รวมสิ่งเหล่านี้เข้าด้วยกันโดยใช้ตรรกะเฉพาะเพื่อควบคุมมุมมอง โมเดล และการไหลของข้อมูลผ่าน แอปพลิเคชัน.
องค์ประกอบนามธรรม
การเยาะเย้ยมุมมองและรุ่น
ในตัวอย่างการทดสอบ Android นี้ เราจะจำลองมุมมอง โมเดล และองค์ประกอบของพื้นที่เก็บข้อมูล และเราจะทดสอบผู้นำเสนอเป็นหน่วย นี่เป็นหนึ่งในการทดสอบที่เล็กที่สุด โดยกำหนดเป้าหมายองค์ประกอบเดียวในสถาปัตยกรรม นอกจากนี้ เราจะใช้วิธีการ stubbing เพื่อตั้งค่าปฏิกิริยาลูกโซ่ที่เหมาะสมและทดสอบได้:
@RunWith(RobolectricTestRunner.class) @Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18) public class FitnessListPresenterTest { private Calendar cal = Calendar.getInstance(); @Mock private IFitnessListModel model; @Mock private IFitnessListView view; private IFitnessListPresenter presenter; @Before public void setup() { MockitoAnnotations.initMocks(this); final FitnessEntry entryMock = mock(FitnessEntry.class); presenter = new FitnessListPresenter(view, model); /* Define the desired behaviour. Queuing the action in "doAnswer" for "when" is executed. Clear and synchronous way of setting reactions for actions (stubbing). */ doAnswer((new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { ArrayList<FitnessEntry> items = new ArrayList<>(); items.add(entryMock); ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); return null; } })).when(model).fetchAllItems((IFitnessListPresenterCallback) presenter); } /** Verify if model.fetchItems was called once. Verify if view.onFetchSuccess is called once with the specified list of type FitnessEntry The concrete implementation of ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); calls the view.onFetchSuccess(...) method. This is why we verify that view.onFetchSuccess is called once. */ @Test public void testFetchAll() { presenter.fetchAllItems(false); // verify can be called only on mock objects verify(model, times(1)).fetchAllItems((IFitnessListPresenterCallback) presenter); verify(view, times(1)).onFetchSuccess(new ArrayList<>(anyListOf(FitnessEntry.class))); } }
จำลองเลเยอร์เครือข่ายทั่วโลกด้วย MockWebServer
มักจะสะดวกที่จะสามารถจำลองเลเยอร์เครือข่ายทั่วโลกได้ MockWebServer ช่วยให้เราสามารถจัดคิวการตอบสนองสำหรับคำขอเฉพาะที่เราดำเนินการในการทดสอบของเรา สิ่งนี้ทำให้เรามีโอกาสจำลองการตอบสนองที่คลุมเครือซึ่งเราคาดหวังจากเซิร์ฟเวอร์ แต่ไม่สามารถทำซ้ำได้โดยตรง ช่วยให้เรามั่นใจในความครอบคลุมทั้งหมดในขณะที่เขียนโค้ดเพิ่มเติมเล็กน้อย
ที่เก็บโค้ดของ MockWebServer ให้ตัวอย่างที่ชัดเจนซึ่งคุณสามารถอ้างอิงถึงเพื่อทำความเข้าใจไลบรารีนี้ได้ดียิ่งขึ้น
การทดสอบแบบกำหนดเองคู่ผสม
คุณสามารถเขียนโมเดลหรือส่วนประกอบ respoistory ของคุณเอง แล้วฉีดเข้าไปในการทดสอบโดยจัดเตรียมโมดูลอื่นให้กับกราฟวัตถุโดยใช้ Dagger (http://square.github.io/dagger/) เรามีตัวเลือกในการตรวจสอบว่าสถานะการดูได้รับการอัปเดตอย่างถูกต้องตามข้อมูลที่ได้รับจากส่วนประกอบแบบจำลองจำลองหรือไม่:
/** Custom mock model class */ public class FitnessListErrorTestModel extends FitnessListModel { // ... @Override public void fetchAllItems(IFitnessListPresenterCallback callback) { callback.onError(); } @Override public void fetchItemsInRange(final IFitnessListPresenterCallback callback, DateFilter filter) { callback.onError(); } }
@RunWith(RobolectricTestRunner.class) @Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18) public class FitnessListPresenterDaggerTest { private FitnessActivity activity; private FitnessListFragment fitnessListFragment; @Before public void setup() { /* setupActivity runs the Activity lifecycle methods on the specified class */ activity = Robolectric.setupActivity(FitnessActivity.class); fitnessListFragment = activity.getFitnessListFragment(); /* Create the objectGraph with the TestModule */ ObjectGraph localGraph = ObjectGraph.create(TestModule.newInstance(fitnessListFragment)); /* Injection */ localGraph.inject(fitnessListFragment); localGraph.inject(fitnessListFragment.getPresenter()); } @Test public void testInteractorError() { fitnessListFragment.getPresenter().fetchAllItems(false); /* suppose that our view shows a Toast message with the specified text below when an error is reported, so we check for it. */ assertEquals(ShadowToast.getTextOfLatestToast(), "Something went wrong!"); } @Module( injects = { FitnessListFragment.class, FitnessListPresenter.class }, overrides = true, library = true ) static class TestModule { private IFitnessListView view; private TestModule(IFitnessListView view){ this.view = view; } public static TestModule newInstance(IFitnessListView view){ return new TestModule(view); } @Provides public IFitnessListInteractor provideFitnessListInteractor(){ return new FitnessListErrorTestModel(); } @Provides public IFitnessListPresenter provideFitnessPresenter(){ return new FitnessListPresenter(view); } } }
วิ่งทดสอบ
Android Studio
คุณสามารถคลิกขวาที่คลาสการทดสอบ เมธอด หรือแพ็คเกจการทดสอบทั้งหมด แล้วรันการทดสอบจากไดอะล็อกตัวเลือกใน IDE
เทอร์มินัล
การรันการทดสอบแอพ Android จากเทอร์มินัลจะสร้างรายงานสำหรับคลาสที่ทดสอบในโฟลเดอร์ "build" ของโมดูลเป้าหมาย ยิ่งไปกว่านั้น หากคุณวางแผนที่จะตั้งค่ากระบวนการสร้างแบบอัตโนมัติ คุณจะใช้วิธีเทอร์มินัล ด้วย Gradle คุณสามารถเรียกใช้การทดสอบที่มีการดีบักทั้งหมดโดยดำเนินการดังต่อไปนี้:
gradle testDebug
การเข้าถึง Source Set “test” จาก Android Studio Version
Android Studio เวอร์ชัน 1.1 และปลั๊กอิน Android Gradle รองรับการทดสอบหน่วยโค้ดของคุณ คุณสามารถเรียนรู้เพิ่มเติมโดยอ่านเอกสารประกอบที่ยอดเยี่ยมของพวกเขา คุณลักษณะนี้เป็นการทดลอง แต่ยังเป็นส่วนรวมที่ยอดเยี่ยม เนื่องจากขณะนี้คุณสามารถสลับระหว่างการทดสอบหน่วยและชุดแหล่งที่มาของการทดสอบเครื่องมือวัดจาก IDE ได้อย่างง่ายดาย มันทำงานในลักษณะเดียวกับที่คุณจะเปลี่ยนรสชาติใน IDE
ทำให้กระบวนการง่ายขึ้น
การเขียนการทดสอบแอพ Android อาจไม่สนุกเท่าการพัฒนาแอปพลิเคชันดั้งเดิม ดังนั้นเคล็ดลับบางประการเกี่ยวกับวิธีการลดความยุ่งยากในการเขียนแบบทดสอบและหลีกเลี่ยงปัญหาทั่วไปขณะตั้งค่าโครงการจะช่วยได้มาก
AssertJ Android
AssertJ Android อย่างที่คุณอาจเดาได้จากชื่อนี้ เป็นชุดของฟังก์ชันตัวช่วยที่สร้างขึ้นโดยคำนึงถึง Android เป็นส่วนขยายของห้องสมุดยอดนิยม AssertJ ฟังก์ชันการทำงานที่ AssertJ Android มีให้นั้นมีตั้งแต่การยืนยันแบบง่ายๆ เช่น “assertThat(view).isGone()” ไปจนถึงสิ่งต่าง ๆ ที่ซับซ้อน เช่น:

assertThat(layout).isVisible() .isVertical() .hasChildCount(4) .hasShowDividers(SHOW_DIVIDERS_MIDDLE)
ด้วย AssertJ Android และความสามารถในการขยาย คุณจะรับประกันได้ว่าจะเป็นจุดเริ่มต้นที่ดีสำหรับการเขียนการทดสอบสำหรับแอปพลิเคชัน Android
หุ่นยนต์ไฟฟ้าและ Manifest Path
ขณะใช้ Robolectric คุณอาจสังเกตเห็นว่าคุณต้องระบุตำแหน่งไฟล์ Manifest และเวอร์ชัน SDK ถูกตั้งค่าเป็น 18 คุณสามารถทำได้โดยใส่คำอธิบายประกอบ "Config"
@Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18)
การทดสอบการทำงานที่ต้องใช้ Robolectric จากเทอร์มินัลสามารถทำให้เกิดความท้าทายใหม่ๆ ตัวอย่างเช่น คุณอาจเห็นข้อยกเว้น เช่น "ไม่ได้ตั้งค่าธีม" หากการทดสอบดำเนินการอย่างถูกต้องจาก IDE แต่ไม่ใช่จากเทอร์มินัล คุณอาจกำลังพยายามเรียกใช้จากเส้นทางในเทอร์มินัลซึ่งไม่สามารถแก้ไขเส้นทางของรายการที่ระบุได้ ค่าการกำหนดค่าตายตัวสำหรับเส้นทางรายการอาจไม่ชี้ไปยังตำแหน่งที่ถูกต้องจากจุดดำเนินการของคำสั่ง ซึ่งสามารถแก้ไขได้โดยใช้นักวิ่งแบบกำหนดเอง:
public class RobolectricGradleTestRunner extends RobolectricTestRunner { public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError { super(testClass); } @Override protected AndroidManifest getAppManifest(Config config) { String appRoot = "../app/src/main/"; String manifestPath = appRoot + "AndroidManifest.xml"; String resDir = appRoot + "res"; String assetsDir = appRoot + "assets"; AndroidManifest manifest = createAppManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)); return manifest; } }
การกำหนดค่า Gradle
คุณสามารถใช้สิ่งต่อไปนี้เพื่อกำหนดค่า Gradle สำหรับการทดสอบหน่วย คุณอาจต้องแก้ไขชื่อและเวอร์ชันการพึ่งพาที่จำเป็นตามความต้องการของโครงการ
// Robolectric testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'com.squareup.dagger:dagger:1.2.2' testProvided 'com.squareup.dagger:dagger-compiler:1.2.2' testCompile 'com.android.support:support-v4:21.0.+' testCompile 'com.android.support:appcompat-v7:21.0.3' testCompile('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'commons-logging' exclude module: 'httpclient' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' exclude module: 'maven-error-diagnostics' exclude module: 'maven-model' exclude module: 'maven-project' exclude module: 'maven-settings' exclude module: 'plexus-container-default' exclude module: 'plexus-interpolation' exclude module: 'plexus-utils' exclude module: 'wagon-file' exclude module: 'wagon-http-lightweight' exclude module: 'wagon-provider-api' }
บริการหุ่นยนต์และการเล่น
หากคุณกำลังใช้บริการ Google Play คุณจะต้องสร้างค่าคงที่จำนวนเต็มของคุณเองสำหรับเวอร์ชัน Play Services เพื่อให้ Robolectric ทำงานอย่างถูกต้องในการกำหนดค่าแอปพลิเคชันนี้
<meta-data android:name="com.google.android.gms.version" android:value="@integer/gms_version" tools:replace="android:value" />
การพึ่งพา Robolectric เพื่อรองรับห้องสมุด
ปัญหาการทดสอบที่ น่าสนใจ อีกประการหนึ่งคือ Robolectric ไม่สามารถอ้างอิงไลบรารีสนับสนุนได้อย่างถูกต้อง วิธีแก้ไขคือการเพิ่มไฟล์ "project.properties" ลงในโมดูลที่มีการทดสอบ ตัวอย่างเช่น สำหรับไลบรารี Support-v4 และ AppCompat ไฟล์ควรมี:
android.library.reference.1=../../build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3 android.library.reference.2=../../build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3
การทดสอบการยอมรับ/การถดถอย
การทดสอบการยอมรับ/การถดถอยเป็นส่วนหนึ่งของขั้นตอนสุดท้ายของการทดสอบในสภาพแวดล้อม Android ที่แท้จริง 100% เราไม่ได้ใช้คลาส Android OS ที่จำลองขึ้นในระดับนี้ - การทดสอบทำงานบนอุปกรณ์และอีมูเลเตอร์จริง
สถานการณ์เหล่านี้ทำให้กระบวนการไม่เสถียรมากขึ้นเนื่องจากความหลากหลายของอุปกรณ์ทางกายภาพ การกำหนดค่าโปรแกรมจำลอง สถานะของอุปกรณ์ และชุดคุณลักษณะของแต่ละอุปกรณ์ นอกจากนี้ยังขึ้นอยู่กับเวอร์ชันของระบบปฏิบัติการและขนาดหน้าจอของโทรศัพท์เป็นอย่างมากในการตัดสินใจว่าเนื้อหาจะแสดงอย่างไร
การสร้างการทดสอบที่ถูกต้องนั้นค่อนข้างซับซ้อนและผ่านในอุปกรณ์ที่หลากหลาย แต่เช่นเคย คุณควรฝันให้ใหญ่แต่เริ่มจากเล็ก การสร้างการทดสอบด้วย Robotium เป็นกระบวนการที่ทำซ้ำได้ ด้วยลูกเล่นเล็ก ๆ น้อย ๆ ก็สามารถทำให้ง่ายขึ้นได้มาก
หุ่นยนต์
Robotium เป็นเฟรมเวิร์กการทดสอบระบบอัตโนมัติแบบโอเพ่นซอร์สของ Android ที่มีมาตั้งแต่มกราคม 2010 เป็นมูลค่าการกล่าวขวัญว่า Robotium เป็นโซลูชันแบบชำระเงิน แต่มาพร้อมกับการทดลองใช้ฟรีที่ยุติธรรม
เพื่อเร่งกระบวนการเขียนการทดสอบ Robotium เราจะย้ายจากการเขียนการทดสอบด้วยตนเองไปเป็นการบันทึกการทดสอบ การแลกเปลี่ยนระหว่างคุณภาพของโค้ดและความเร็ว หากคุณกำลังทำการเปลี่ยนแปลงอย่างมากกับอินเทอร์เฟซผู้ใช้ คุณจะได้รับประโยชน์มากมายจากวิธีการบันทึกการทดสอบและสามารถบันทึกการทดสอบใหม่ได้อย่างรวดเร็ว
Testdroid Recorder เป็นเครื่องบันทึกการทดสอบฟรีที่สร้างการทดสอบ Robotium โดยจะบันทึกการคลิกที่คุณดำเนินการบนอินเทอร์เฟซผู้ใช้ การติดตั้งเครื่องมือนั้นง่ายมาก ตามที่อธิบายไว้ในเอกสารประกอบพร้อมกับวิดีโอทีละขั้นตอน
เนื่องจาก Testdroid Recorder เป็นปลั๊กอิน Eclipse และเราหมายถึง Android Studio ตลอดทั้งบทความนี้ จึงเป็นเหตุผลที่น่าเป็นห่วง อย่างไรก็ตาม ในกรณีนี้ ไม่มีปัญหา เนื่องจากคุณสามารถใช้ปลั๊กอินกับ APK ได้โดยตรงและบันทึกการทดสอบกับมัน
เมื่อคุณสร้างการทดสอบแล้ว คุณสามารถคัดลอกและวางการทดสอบใน Android Studio ร่วมกับการพึ่งพาใดๆ ที่ตัวบันทึก Testdroid ต้องการ และคุณพร้อมที่จะดำเนินการ การทดสอบที่บันทึกไว้จะดูเหมือนชั้นเรียนด้านล่าง:
public class LoginTest extends ActivityInstrumentationTestCase2<Activity> { private static final String LAUNCHER_ACTIVITY_CLASSNAME = "com.toptal.fitnesstracker.view.activity.SplashActivity"; private static Class<?> launchActivityClass; static { try { launchActivityClass = Class.forName(LAUNCHER_ACTIVITY_CLASSNAME); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private ExtSolo solo; @SuppressWarnings("unchecked") public LoginTest() { super((Class<Activity>) launchActivityClass); } // executed before every test method @Override public void setUp() throws Exception { super.setUp(); solo = new ExtSolo(getInstrumentation(), getActivity(), this.getClass() .getCanonicalName(), getName()); } // executed after every test method @Override public void tearDown() throws Exception { solo.finishOpenedActivities(); solo.tearDown(); super.tearDown(); } public void testRecorded() throws Exception { try { assertTrue( "Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_username_input) failed.", solo.waitForEditTextById( "com.toptal.fitnesstracker.R.id.login_username_input", 20000)); solo.enterText( (EditText) solo .findViewById("com.toptal.fitnesstracker.R.id.login_username_input"), "[email protected]"); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( "Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_password_input) failed.", solo.waitForEditTextById( "com.toptal.fitnesstracker.R.id.login_password_input", 20000)); solo.enterText( (EditText) solo .findViewById("com.toptal.fitnesstracker.R.id.login_password_input"), "123456"); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( "Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.", solo.waitForButtonById( "com.toptal.fitnesstracker.R.id.parse_login_button", 20000)); solo.clickOnButton((Button) solo .findViewById("com.toptal.fitnesstracker.R.id.parse_login_button")); assertTrue("Wait for text fitness list activity.", solo.waitForActivity(FitnessActivity.class)); assertTrue("Wait for text KM.", solo.waitForText("KM", 20000)); /* Custom class that enables proper clicking of ActionBar action items */ TestUtils.customClickOnView(solo, R.id.action_logout); solo.waitForDialogToOpen(); solo.waitForText("OK"); solo.clickOnText("OK"); assertTrue("waiting for ParseLoginActivity after logout", solo.waitForActivity(ParseLoginActivity.class)); assertTrue( "Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.", solo.waitForButtonById( "com.toptal.fitnesstracker.R.id.parse_login_button", 20000)); } catch (AssertionFailedError e) { solo.fail( "com.example.android.apis.test.Test.testRecorded_scr_fail", e); throw e; } catch (Exception e) { solo.fail( "com.example.android.apis.test.Test.testRecorded_scr_fail", e); throw e; } } }
หากคุณสังเกตดีๆ คุณจะสังเกตเห็นว่าโค้ดค่อนข้างตรงไปตรงมา
เมื่อทำการบันทึกการทดสอบ อย่าใช้คำว่า "รอ" แบบขาดๆ เกินๆ รอให้กล่องโต้ตอบปรากฏขึ้น กิจกรรมจะปรากฏขึ้น ข้อความจะปรากฏขึ้น สิ่งนี้จะรับประกันว่ากิจกรรมและลำดับชั้นการดูพร้อมที่จะโต้ตอบเมื่อคุณดำเนินการบนหน้าจอปัจจุบัน ในเวลาเดียวกัน ให้จับภาพหน้าจอ การทดสอบอัตโนมัติมักจะไม่ต้องมีคนดูแล และภาพหน้าจอเป็นวิธีหนึ่งที่คุณจะได้เห็นว่าเกิดอะไรขึ้นระหว่างการทดสอบเหล่านั้น
ไม่ว่าการทดสอบจะผ่านหรือล้มเหลว รายงานคือเพื่อนที่ดีที่สุดของคุณ คุณสามารถค้นหาได้ภายใต้ไดเร็กทอรี build “module/build/outputs/reports”:
ตามทฤษฎีแล้ว ทีม QA สามารถบันทึกการทดสอบและเพิ่มประสิทธิภาพได้ โดยการใส่ความพยายามในแบบจำลองมาตรฐานสำหรับการเพิ่มประสิทธิภาพกรณีทดสอบ ก็สามารถทำได้ เมื่อคุณบันทึกการทดสอบตามปกติ คุณจะต้องปรับแต่งสองสามสิ่งเพื่อให้ทำงานได้อย่างไร้ที่ติ
สุดท้าย ในการเรียกใช้การทดสอบเหล่านี้จาก Android Studio คุณสามารถเลือกการทดสอบและเรียกใช้แบบเดียวกับที่คุณเรียกใช้การทดสอบหน่วย จากเทอร์มินัล เป็นสายการบินเดียว:
gradle connectedAndroidTest
ประสิทธิภาพการทดสอบ
การทดสอบหน่วย Android ด้วย Robolectric นั้นเร็วมากเพราะทำงานโดยตรงภายใน JVM บนเครื่องของคุณ การทดสอบการยอมรับบนอีมูเลเตอร์และอุปกรณ์ทางกายภาพนั้นช้ากว่ามาก ขึ้นอยู่กับขนาดของโฟลว์ที่คุณกำลังทดสอบ อาจใช้เวลาไม่กี่วินาทีถึงสองสามนาทีต่อกรณีทดสอบ ระยะการทดสอบการยอมรับควรใช้เป็นส่วนหนึ่งของกระบวนการสร้างอัตโนมัติบนเซิร์ฟเวอร์การรวมแบบต่อเนื่อง
สามารถปรับปรุงความเร็วได้ด้วยการทำให้ขนานกันบนอุปกรณ์หลายเครื่อง ลองดูเครื่องมือที่ยอดเยี่ยมนี้จาก Jake Wharton และพวกที่ Square http://square.github.io/spoon/ มีการรายงานที่ดีเช่นกัน
The Takeaway
มีเครื่องมือทดสอบ Android มากมาย และเมื่อระบบนิเวศเติบโตเต็มที่ กระบวนการตั้งค่าสภาพแวดล้อมที่ทดสอบได้และการเขียนการทดสอบจะง่ายขึ้น ยังมีความท้าทายอีกมากที่ต้องจัดการ และด้วยชุมชนนักพัฒนาจำนวนมากที่ทำงานเกี่ยวกับปัญหาประจำวัน มีพื้นที่มากมายสำหรับการอภิปรายที่สร้างสรรค์และข้อเสนอแนะที่รวดเร็ว
ใช้แนวทางที่อธิบายไว้ในบทช่วยสอนการทดสอบ Android นี้เพื่อเป็นแนวทางในการจัดการกับความท้าทายที่อยู่ข้างหน้าคุณ หากและเมื่อคุณประสบปัญหา ให้กลับมาตรวจสอบกับบทความนี้หรือข้อมูลอ้างอิงที่เชื่อมโยงภายในเพื่อดูวิธีแก้ไขปัญหาที่ทราบ
ในโพสต์ต่อๆ ไป เราจะหารือเกี่ยวกับการทำให้ขนานกัน การสร้างระบบอัตโนมัติ การผสานรวมอย่างต่อเนื่อง Github/BitBucket hooks การกำหนดเวอร์ชันของสิ่งประดิษฐ์ และแนวทางปฏิบัติที่ดีที่สุดสำหรับการจัดการโครงการแอปพลิเคชันมือถือขนาดใหญ่ในเชิงลึกยิ่งขึ้น