【Kapil Bakshi】产品等级测试驱动开发和最新动态

蓟琳怡

2017/12/31 发布于 技术 分类

2017年,droidcon 第2次来到中国,并将于2017年11月在北京盛大开幕。参会人群包括业界领袖、技术大咖、技术开发者、大众创业者及领域从业者。大会将邀请来自Google、微软、Facebook、Ebay、Intel、Telenav、阿里巴巴、腾讯、小米、乐视、联想等国内外安卓技术与应用领域的大咖,沿袭历年国际大会特色,聚焦行业最前沿技术,碰撞切磋技术火花。

文字内容
1. Production level Test Driven Development Dagger 2 Fire Base Test Labs MVVM + DataBinding
2. About Me KAPIL BAKSHI FOODIE TRAVELLER MUSIC LOVER SUSPENSE WATCHER LOGISTICS + FINTECH + EDTECH Software Engineer akapil167 kapilbakshi167@gmail.com
3. The Major Release is Coming And The Bugs Come With It
4. The War is Between The Features And The Bugs And The Bugs Come With It
5. And Make No Mistake The Bugs Are Coming
6. And That is Why We Have Gathered Here To Find A Solution
7. This talk will clear all your confusions Which Framework to choose ? Writing Testable Code Running Tests On Different Devices Umm.. Unit Testing, Instrumentation Testing or End To End Tests ??
8. Different Types Of Testing 1. Idea :- Testing Business Logic 2. No external dependency 3. Options :- Robolectric, JUnit, Mockito
9. Different Types Of Testing 1. Idea :- Testing User Dependencies, Behaviour, Integration 2. Options :- Espresso, Robotium,Appium
10. Flakiness and Its Mitigation Ferocious Flaky Same Code Passing ? Failing Concurrency Flaky third party code Infrastructure problems
11. Flakiness and Its Mitigation Ferocious Flaky Passing Same Code ? Hero Hermetic to the Rescue Failing Mocking Dependencies Concurrency Relying More on Unit Tests Flaky third party code Infrastructure problems
12. Genuine Production Level Scenarios
13. Testing Error Handling
14. Now to Test this Would you actually Turn Off the internet on your device ???? Would you actually stop your server ????
15. Now to Test this This would simply Defeat the purpose of Automation and make testing Cumbersome
16. An App Accepting Different Types Of Orders
17. What would happen if you Don’t test this Hermetically
18. Handle Complex Order Lifecycle Order Placed Packed Dispatched Delivered App Packing team Dashboard Dispatchment team Dashboard Delivery App
19. External You would have to HIT From Your TEST CODE APIs Order Placed Packed Dispatched Delivered App Packing team Dashboard Dispatchment team Dashboard Delivery App
20. Then you’ll realize It’s taking much longer to “Make Arrangements” to write Test Cases than to actually Write Test Cases It’s taking much Longer to write Test cases than to develop features
21. Then you’ll realize You are testing What You Haven’t Even Coded
22. Then you’ll realize The goal of testing The Code you have actually written gets Farther .. Farther …. Farther………… Farther Away
23. Then solution is quite simple Let the Code Take Control Of Everything
24. Let’s Explore How
25. Repository Pattern Domain Layer Persist Business Logic Query Repository Order getOrders() fetchOrderDetails() updateOrderDetails() Data Layer Data Source Cloud Local Storage Mock Data Source
26. Repository Pattern - Advantages Persist Business Logic Query Repository Order getOrders() fetchOrderDetails() updateOrderDetails() Data Source Cloud Local Storage Mock Data Source Provides Abstraction of Data
27. Repository Pattern - Advantages Persist Business Logic Query Repository Order getOrders() fetchOrderDetails() updateOrderDetails() Data Source Cloud Local Storage Mock Data Source Makes the code Highly Maintainable and Extensible
28. Repository Pattern - Advantages Persist Business Logic Query Repository Order getOrders() fetchOrderDetails() updateOrderDetails() Data Source Cloud Local Storage Mock Data Source Makes the code Highly Configurable and Testable
29. Repository Repository Pattern - In Action
30. OrdersDataSource Repository public interface OrdersDataSource { interface LoadOrdersCallback { void onGetOrdersResponse(Observable<AllOrdersResponse> ordersResponseObservable); } void getOrdersResponse(@NonNull OrdersDataSource.LoadOrdersCallback callback); } Interface which would be implemented by All the Data Sources and the Repository
31. OrdersRepository public class OrdersRepository implements OrdersDataSource { Repository private final OrdersDataSource ordersDataSource; private OrdersRepository ( @NonNull OrdersDataSource ordersDataSource ) { this.ordersDataSource = ordersDataSource; } Accepting a Data Source @Override public void getOrdersResponse(@NonNull final OrdersDataSource.LoadOrdersCallback callback) { ordersDataSource.getOrdersResponse(new OrdersDataSource.LoadOrdersCallback() { @Override public void onGetOrdersResponse(Observable<AllOrdersResponse> ordersResponseObservable) { callback.onGetOrdersResponse(ordersResponseObservable); } }); }
32. OrdersRemoteDataSource public class OrdersRemoteDataSource implements OrdersDataSource { Repository private static OrdersRemoteDataSource INSTANCE; @Inject Retrofit retrofit; @Override public void getOrdersResponse(@NonNull LoadOrdersCallback callback) { MyApplication.getComponent().inject(this); NetworkApis networkApis = retrofit.create(NetworkApis.class); callback.onGetOrdersResponse(networkApis.getOrders()); } public static OrdersRemoteDataSource getInstance() { if (INSTANCE == null) { INSTANCE = new OrdersRemoteDataSource(); } return INSTANCE; } } Fetching Orders From The Server Using Retrofit
33. FakeDataSource public class FakeOrderDataSource impleRmeepntossOirtdoerrsyDataSource { @Override public void getOrdersResponse(@NonNull LoadOrdersCallback callback) { callback.onGetOrdersResponse(getAllOrderResponseObservable()); } public static void createAll_Order_Response() { String errorMessage = null; boolean success = true; List<Order> orderList = new ArrayList<Order>(); ALL_ORDER_RESPONSE = new AllOrdersResponse(success, errorMessage, orderList); } Fetching an Observable Of Mocked Orders
34. FakeDataSource public class FakeOrderDataSource impRleempenotss iOtordreyrsDataSource { @Override public void getOrdersResponse(@NonNull LoadOrdersCallback callback) { callback.onGetOrdersResponse(getAllOrderResponseObservable()); } public static void createAll_Order_Response() { Creates All Orders String errorMessage = null; boolean success = true; response which can be modified further List<Order> orderList = new ArrayList<Order>(); ALL_ORDER_RESPONSE = new AllOrdersResponse(success, errorMessage, orderList); } }
35. FakeDataSource Repository public class FakeOrderDataSource implements OrdersDataSource { public void createOrdersObservable(String...statuses) { reCreateAll_Order_Response(); for(String status:statuses) { Order order = createOrderBasedOnStatus(status, new Random().nextInt(Integer.MAX_VALUE)); addOrders(order); } Fetching an Observable Of Mocked Orders as per the given statuses ALL_ORDER_RESPONSE_OBSERVABLE = Observable.just(getAllOrderResponse()); } }
36. FakeDataSource Repository public class FakeOrderDataSource implements OrdersDataSource { public void createAllOrderResponseWithServerErrorObservable(String errorMessage) { reCreateAll_Order_Response(); addErrorToAllOrdersResponse(errorMessage); toggleSuccess(false); ALL_ORDER_RESPONSE_OBSERVABLE = Observable.just(getAllOrderResponse()); } Creates All Orders } Observable with an Error to mock Server Error
37. FakeDataSource Repository public class FakeOrderDataSource implements OrdersDataSource { public void create_Exception_Error_Observable(String exceptionMessage) { ALL_ORDER_RESPONSE_OBSERVABLE = Observable.<AllOrdersResponse>error(new NullPointerException(exceptionMessage)); } Creates All Orders Observable with an Exception
38. Now How do we interchange these Data Sources while Running our Tests ??
39. Now How do we interchange these Data Sources while Running our Tests ?? Dependency Injection is the way to go!!
40. Dependency Injection The client delegates the responsibility of providing its dependencies to external code (The Injector) Without The client having to build it.
41. Dependency Injection Advantages The client becomes highly Configurable and Reusable. The Code becomes Decoupled.
42. Dependency Injection Using Dagger 2- In Action
43. Modules In Dagger 2 @Module public class OrdersModule { Responsible for providing objects which can be injected @Provides @Singleton public OrdersRepository providesNotesRepository() { return OrdersRepository.getInstance( OrdersRemoteDataSource.getInstance()); } } Used for methods which provide objects for dependencies injection Notice Remote Order Data Source is being used here
44. Modules In Dagger 2 Test Order Module public class OrdersTestModule extends OrdersModule { @Override public OrdersRepository providesNotesRepository() { return OrdersRepository.getInstance( FakeOrderDataSource.getInstance()); } } Notice Mocked Order Data Source is being used here
45. Components In Dagger 2 @Singleton @Component (modules = { NotesModule.class, NetworkModule.class, OrdersModule.class }) public interface AppComponent { void inject(AddEditNoteActivity addEditNoteActivity); void inject(AllNotesActivity allNotesActivity); void inject(OrdersRemoteDataSource ordersRemoteDataSource); void inject(AllOrdersActivity allOrdersActivity); } This interface is used by Dagger 2 to generate code which uses the modules to fulfill the requested dependencies.
46. How does Injection Take Place public class MyApplication extends Application { private static AppComponent component; public static AppComponent getComponent() { return component; } public AppComponent createComponent() { return DaggerAppComponent.builder() .networkModule(new NetworkModule(this)) .ordersModule(new OrdersModule()) .build(); } @Override public void onCreate() { super.onCreate(); component = createComponent(); } } DaggerAppComponent contains the generated code to Configure Modules
47. How does Injection Take Place public class MyApplication extends Application { private static AppComponent component; public static AppComponent getComponent() { return component; } public AppComponent createComponent() { return DaggerAppComponent.builder() .networkModule(new NetworkModule(this)) .ordersModule(new OrdersModule()) .build(); } @Override public void onCreate() { super.onCreate(); component = createComponent(); } } Modules getting configured
48. How does Injection Take Place While Testing public class TestMyApplication extends Application { @Override public AppComponent createComponent() { return DaggerAppComponent.builder() .networkModule(new NetworkModule(this)) .ordersModule(new OrdersTestModule()) .build(); } Notice Test Module Getting Configured
49. @Inject Annotation public class AllOrdersActivity extends AppCompatActivity { @Inject OrdersRepository ordersRepository; Injection Taking Place private AllOrdersViewModel allOrdersViewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getComponent().inject(this); activityAllOrdersBinding = DataBindingUtil.setContentView(this, R.layout.activity_all_orders); } private void setViewModel() { allOrdersViewModel = findOrCreateViewModel(); activityAllOrdersBinding.setAllOrdersViewModel(allOrdersViewModel); } @Override public void onResume() { super.onResume(); allOrdersViewModel.loadOrders(); }
50. View Model public class AllOrdersViewModel { public AllOrdersViewModel( OrdersRepository repository) { ordersRepository = repository; } Accepting a Repository private void loadOrders(final boolean showLoadingUI) { if (showLoadingUI) { dataLoading.set(true); } ordersRepository.getOrdersResponse(new OrdersDataSource.LoadOrdersCallback() { Orders Are being fetched from the Repository
51. ordersResponseObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<AllOrdersResponse>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { dataLoading.set(false); snackbarText.set(exceptionErrorText); e.printStackTrace(); } @Override public void onNext(AllOrdersResponse allOrdersResponse) { dataLoading.set(false); if (allOrdersResponse.isSuccess()) { ordersList.clear(); ordersList.addAll(allOrdersResponse.getOrders()); } else { snackbarText.set(allOrdersResponse.getError_message()); } Handling Exceptions Orders Are being fetched from the Repository
52. Now That We Have The Tools Ready Let’s Start Writing Test Cases
53. Three Approaches To Testing Unit Instrumentation And Integration Testing Using Espresso Unit Testing Using Robolectric Pure JVM Testing Using MVVM Which to Choose ? Performance Analysis
54. So, Why Espresso ? Closely Integrated With Android Studio No External Dependency eg. Selenium Server in case of Appium
55. So, Why Espresso ? Can be used both for Unit and Integration Testing Removes Flakiness By Mocking Intents
56. Hypnotic Effect of Espresso Intents Let’s Mock’em
57. Instrumentation Tests Source Set
58. Setting Up An Instrumentation Runner In build.gradle defaultConfig { Test Runner Class applicationId "com.sinca.shoppy" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "com.sinca.shoppy.MyTestRunner" }
59. What does the Test Runner Do ?? public class MyTestRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader classLoader, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(classLoader, TestMyApplication.class.getName(), context); } } Replacing the application class With a Test Application Class
60. What does the Test Application Do ?? public class TestMyApplication extends MyApplication { @Override public AppComponent createComponent() { return DaggerAppComponent.builder() .networkModule(new NetworkModule(this)) .ordersModule(new OrdersTestModule()) .build(); } } Setting Up Mock Modules
61. Espresso Commands At a Glance
62. Espresso Commands At a Glance
63. Espresso Testing In Action Provides functional testing of a single Activity public class AllOrdersTest { @Rule public ActivityTestRule<AllOrdersActivity> mActivityTestRule = new ActivityTestRule<AllOrdersActivity>(AllOrdersActivity.class, true, false); @BeforeClass public static void setUp() { FakeOrderDataSource.createALL_ORDER_RESPONSE_OBSERVABLE(); } Creating Order Observable With An Exception @Test public void onExceptionError_checkIfSnacBarIsDispalyed() { FakeOrderDataSource.getInstance().create_Exception_Error_Observable("Internet Security Exception"); reloadOrdersActivity(); String text = mActivityTestRule.getActivity().getString(R.string.some_error_ocurred); onView(allOf(withId(android.support.design.R.id.snackbar_text), withText(text))) .check(matches(isDisplayed())); }
64. Espresso Testing In Action public class AllOrdersTest { @Rule public ActivityTestRule<AllOrdersActivity> mActivityTestRule = new ActivityTestRule<AllOrdersActivity>(AllOrdersActivity.class, true, false); @BeforeClass public static void setUp() { FakeOrderDataSource.createALL_ORDER_RESPONSE_OBSERVABLE(); } @Test public void onExceptionError_checkIfSnacBarIsDispalyed() { FakeOrderDataSource.getInstance().create_Exception_Error_Observable("Internet Security Exception"); reloadOrdersActivity(); String text = mActivityTestRule.getActivity().getString(R.string.some_error_ocurred); onView(allOf(withId(android.support.design.R.id.snackbar_text), withText(text))) .check(matches(isDisplayed())); } Checking If a SnackBar gets displayed with an appropriate text
65. Espresso Testing In Action ViewMatcher onView(allOf(withId(android.support.design.R.id.snackbar_text), withText(text))) .check(matches(isDisplayed())); } ViewAssertion
66. Clicking on A Cancelled Order @Test public void onCancelledOrderClick_checkIfCancelledOrderPageIsOpened() { Creating Orders List Observable FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES _ARRAY); reloadOrdersActivity(); onView(withText(OrderLifeCycleConstants.STATUS_ORDER_CANCELLED)).perform(click()); onView(withId(R.id.order_cancelled_text_view)).check(matches(isDisplayed())); }
67. Clicking on A Cancelled Order @Test public void onCancelledOrderClick_checkIfCancelledOrderPageIsOpened() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); reloadOrdersActivity(); onView(withText(OrderLifeCycleConstants.STATUS_ORDER_CANCELLED)).perform(click()); onView(withId(R.id.order_cancelled_text_view)).check(matches(isDisplayed())); } View Action
68. Clicking on A Cancelled Order @Test public void onCancelledOrderClick_checkIfCancelledOrderPageIsOpened() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); reloadOrdersActivity(); onView(withText(OrderLifeCycleConstants.STATUS_ORDER_CANCELLED)).perform(click()); onView(withId(R.id.order_cancelled_text_view)).check(matches(isDisplayed())); } Checking if the correct page has opened Clicking on a Cancelled Order
69. Testing Server Error @Test public void onServerError_checkIfSnackBarIsDisplayedWithCorrectMessage() { FakeOrderDataSource.getInstance().createAllOrderResponseWithServerErrorObservable(SERVER_BU SY_MESSAGE); reloadOrdersActivity(); onView(allOf(withId(android.support.design.R.id.snackbar_text),withText(SERVER_BUSY_MESSAG E))) .check(matches(isDisplayed())); }
70. It took 45 secs to build & Install the app = + 4 secs to run the 6 test cases 49 secs
71. When to Use Espresso For Integration Testing To Test On Multiple Devices To Test With Actual Data Sources
72. Not Required Mocks Android SDK To Run Tests Directly On JVM
73. Unit Tests Source Set
74. Robolectric Test Class Setting Up Roblectric Config @RunWith(RobolectricTestRunner.class) @org.robolectric.annotation.Config(constants = BuildConfig.class, sdk = 21, shadows = {ShadowSnackbar.class},application = UnitTestingApplication.class) public class AllOrdersActivityTest { } Setting Up Test Application to Inject Mocked Modules
75. Initialisation Before Every Test @RunWith(RobolectricTestRunner.class) public class AllOrdersActivityTest { Creating activity private void reloadOrdersActivity() { activity = Robolectric.setupActivity(AllOrdersActivity.class); ordersRecyclerView = (RecyclerView) activity.findViewById(R.id.orders_recycler_view); allOrdersViewModel = activity.getAllOrdersViewModel(); } } Referencing Views and View Model
76. Testing Exception @Test public void onExceptionErrorWhileFetchingOrders_checkIfSnacBarIsDisplayed() { Creating Order Observable with Exception FakeOrderDataSource.getInstance().create_Exception_Error_Observable("Internet Security Exception"); reloadOrdersActivity(); assertThat(activity.getString(R.string.some_error_ocurred),equalTo( ShadowSnackbar.getTextOfLatestSnackbar())); } Checking If Snackbar displays the correct text or not
77. Testing labelling Of Order Statuses @Test public void onOrdersLoaded_checkIfStatusLabellingOfOrderItemsIsCorrect() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRA Y); reloadOrdersActivity(); for(int i = 0; i < OrderLifeCycleConstants.ORDER_STATUSES_ARRAY.length; i++) { View itemView = ordersRecyclerView.getChildAt(i); Referencing Views TextView statusTextView = (TextView) itemView.findViewById(R.id.order_status_text_view); assertTrue(statusTextView.getText().toString().equals(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY[i])); } }
78. Testing labelling Of Order Statuses @Test public void onOrdersLoaded_checkIfStatusLabellingOfOrderItemsIsCorrect() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRA Y); reloadOrdersActivity(); for(int i = 0; i < OrderLifeCycleConstants.ORDER_STATUSES_ARRAY.length; i++) { View itemView = ordersRecyclerView.getChildAt(i); TextView statusTextView = (TextView) itemView.findViewById(R.id.order_status_text_view); assertTrue(statusTextView.getText().toString().equals(OrderLifeCycleConstants.ORDER_STAT USES_ARRAY[i])); } } Checking If Every Order Displays The Correct Status Or Not
79. Testing Clicking Of Orders @Test public void onDeliveryOrderClick_checkIfDeliveryOrderPageIsOpened() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRA Y); reloadOrdersActivity(); ordersRecyclerView.getChildAt(0).performClick(); Clicking on an Order Item assertNextActivity(activity, DeliveryActivity.class); } Checking If correct Activity has Opened or Not
80. It took 18 secs to Shadow Android Code To JVM = + 5 secs to run the 6 test cases 23 secs
81. When to Use Robolectric For Testing Directly On JVM Very Useful When App Is Not Well Architectured Also very Helpful for testing view properties like colour, style etc.
82. Testing The View Model
83. Any Guesses How much time it took ???
84. It took 180 milli secs To Run The Same Test Cases
85. How Did It Happen??? Lets Seee…!!!
86. View Model public class AllOrdersViewModel { public AllOrdersViewModel( OrdersRepository repository) { ordersRepository = repository; } private void loadOrders(final boolean showLoadingUI) { if (showLoadingUI) { dataLoading.set(true); } ordersRepository.getOrdersResponse(new OrdersDataSource.LoadOrdersCallback() { Accepting a Repository Orders Are being fetched from the Repository
87. ordersResponseObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<AllOrdersResponse>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { dataLoading.set(false); snackbarText.set( exceptionErrorText ); } @Override public void onNext(AllOrdersResponse allOrdersResponse) { dataLoading.set(false); if (allOrdersResponse.isSuccess()) { Observables Which Would directly Update Views In The Activity ordersList.clear(); ordersList.addAll(allOrdersResponse.getOrders()); } else { snackbarText.set(allOrdersResponse.getError_message()); }
88. The Magic Of MVVM + DataBinding Layout Files ViewModel <ProgressBar app:layout_constraintTop_toTopOf="@+id/cont_all_orders" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:elevation="2dp" android:visibility="@ { allOrdersViewModel.dataLoading ? View.VISIBLE : View.GONE }" /> private final ObservableBoolean dataLoading = new ObservableBoolean(false); dataLoading.set(true); //After getting Response dataLoading.set(false); Visibility Of This ProgressBar would depend on ObservableBoolean variable
89. The Magic Of MVVM + DataBinding ViewModel private final ObservableField<String> snackbarText = new ObservableField<>(); @Override public void onError(Throwable e) { dataLoading.set(false); snackbarText.set(exceptionErrorText); e.printStackTrace(); } Whenever ObservableField<String> changes, a Snackbar is shown in the Activity with the updated value Activity private Observable.OnPropertyChangedCallback snackbarCallback; private void setupSnackBar() { snackbarCallback = new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { SnackbarUtils.showSnackbar(activityAllOrdersBinding.mai nCord, allOrdersViewModel.getSnackbarText()); } }; allOrdersViewModel.snackbarText.addOnPropertyChangedCallback(sn ackbarCallback); }
90. That Means I can Directly Test The View Model And See Whether The Business Logic Works Fine Or Not
91. YeSsssss And Since The View Model is Simply a Java Class Without Any Android Specific Code The Tests Run Very Fast On
92. Testing ProgressBar @Test public void afterSuccessFullOrdersLoading_CheckIfProgressBarIsNotDisplayed() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); AllOrdersViewModel allOrdersViewModel = constructAndGetAllOrdersViewModel (EXCEPTION_ERROR_SNACKBAR_TEXT); allOrdersViewModel.loadOrders(); assertFalse(allOrdersViewModel.getDataLoading().get()); } Instantiating The View Model private AllOrdersViewModel constructAndGetAllOrdersViewModel(String errorText) { return new AllOrdersViewModel(OrdersRepository.getInstance(FakeOrderDataSource.getInstance()), errorText); }
93. Testing ProgressBar @Test public void afterSuccessFullOrdersLoading_CheckIfProgressBarIsNotDisplayed() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); AllOrdersViewModel allOrdersViewModel = constructAndGetAllOrdersViewModel (EXCEPTION_ERROR_SNACKBAR_TEXT); allOrdersViewModel.loadOrders(); assertFalse( allOrdersViewModel.getDataLoading().get() )L; oading the orders and checking } that dataloading ObservableBoolean is false or not
94. Testing Order Count @Test public void onOrdersFetched_CheckIfOrdreCountIsCorrect() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); AllOrdersViewModel allOrdersViewModel = constructAndGetAllOrdersViewModel (EXCEPTION_ERROR_SNACKBAR_TEXT); allOrdersViewModel.loadOrders(); assertEquals(3, allOrdersViewModel.getOrdersList().size()); } Instantiating The View Model
95. Testing Order Count @Test public void onOrdersFetched_CheckIfOrdreCountIsCorrect() { FakeOrderDataSource.getInstance().createOrdersObservable(OrderLifeCycleConstants.ORDER_STATUSES_ARRAY); AllOrdersViewModel allOrdersViewModel = constructAndGetAllOrdersViewModel (EXCEPTION_ERROR_SNACKBAR_TEXT); allOrdersViewModel.loadOrders(); assertEquals( 3, allOrdersViewModel.getOrdersList().size() ); } Loading the orders and checking that orderList ObservableList<Order> Count is 3 or not
96. Testing On Multiple Devices [Bonus]
97. Testing On Multiple Devices Happy and Relaxed After An Important Release Testing On Stage Testing On PreProd Testing On 3-4 Devices
98. Testing On Multiple Devices
99. Testing On Multiple Devices Fire Base Test Labs
100. Robo Tests Randomly Tests App’s UI Can Supply Inputs for EditTexts Can Choose Maximum Depth of Test Traversal
101. Run From Android Studio
102. Get Very Detailed Reports
103. Cons 1. Less No. Of Devices 1. Supports Only Android Instrumentation Tests And Robo Tests 1. Network Speed Throttling Not Supported Not Supported
104. AWS Device Farm Supports Different Types Of Tests
105. AWS Device Farm Testing At Different Network Speeds
106. AWS Device Farm More Devices To Test On
107. The Only Con Not Able To Run Specific TestNG Test Suites
108. Sauce Labs Supports Different Testing Frameworks
109. Sauce Labs Sauce Labs Acquired TestObject to enable testing on Real Devices
110. Sauce Labs DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("deviceName", deviceName); capabilities.setCapability("platformName", AppConfig.INSTANCE.get("platformName")); capabilities.setCapability("platformVersion", androidVersion); capabilities.setCapability("appPackage", appPackage); capabilities.setCapability("resetKeyboard", true); capabilities.setCapability("testobject_api_key", "89HG598ZXSD6YH78BEF9E5796C108A0F"); MobileDriver mobileDriver = new AndroidDriver(new URL("https://eu1.appium.testobject.com/wd/hub"), capabilities); Just have to change The Url To Appium Hosted On TestObject
111. Cons Network Speed Throttling Is Not Supported Not Supported
112. Oh Lord Of Test Driven Development Cast Your Light Upon Us
113. For The Release Is Critical And Prone To Bugs