Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lovely Testing of Android Apps

Lovely Testing of Android Apps

Effective and safe auto testing of Android apps

Alex Korovyansky

October 28, 2014
Tweet

More Decks by Alex Korovyansky

Other Decks in Programming

Transcript

  1. Lovely Testing of Android Apps Effective and safe auto testing

    of Android apps. Alex Korovyansky #{MBLT}DEV October 28, 2014
  2. Yes, we do! If we want to be successful in

    long term. But, it should be safe (no flaky tests and buggy tools) and effective (easy to extend, fast to write, fast to run)!
  3. • Complexity of mobile UI testing • Mobile artefacts (GPS,

    sensors, etc) • Huge number of different devices Problems
  4. • Complexity of mobile UI testing • Mobile artefacts (GPS,

    sensors, etc) • Huge number of different devices • Big number of different approaches Problems
  5. • Complexity of mobile UI testing • Mobile artefacts (GPS,

    sensors, etc) • Huge number of different devices • Big number of different approaches • Lack of official documentation Problems
  6. • Complexity of mobile UI testing • Mobile artefacts (GPS,

    sensors, etc) • Huge number of different devices • Big number of different approaches • Lack of official documentation • Official solution is friend of slow and flaky tests Problems
  7. • Complexity of mobile UI testing • Mobile artefacts (GPS,

    sensors, etc) • Huge number of different devices • Big number of different approaches • Lack of official documentation • Official solution is friend of slow and flaky tests • … Problems
  8. • Instrumentation Framework (by Google) • Robotium (by Jayway) •

    UIAutomator (by Google) • Robolectric (by ExtremeLabs) Solutions
  9. • Instrumentation Framework (by Google) • Robotium (by Jayway) •

    UIAutomator (by Google) • Robolectric (by ExtremeLabs) • Espresso (by Google) Solutions
  10. • Instrumentation Framework (by Google) • Robotium (by Jayway) •

    UIAutomator (by Google) • Robolectric (by ExtremeLabs) • Espresso (by Google) • Double-Espresso (by Jake Wharton) Solutions
  11. • Instrumentation Framework (by Google) • Robotium (by Jayway) •

    UIAutomator (by Google) • Robolectric (by ExtremeLabs) • Espresso (by Google) • Double-Espresso (by Jake Wharton) • … AssertJ-Android, Burst, Spoon … Solutions
  12. • Battery • Location • Mobile network • Multitouch •

    Sensors • … How test Android artefacts?
  13. • Dagger <- currently best one solution for DI @

    Android • Dagger 2 * • RoboGuice • Guice • PicoContainer • Spring • … Use Dependency Injection! Component <<interface>> Service ServiceImpl Assembler <<create>> Watch Introduction to Dagger — http://youtu.be/tPs1e3dQ6FU
  14. • Simple app for registration to #MBLTDev • RESTful communication

    with backend • Location helper for city field • Open Source code • End-to-end auto tests • Just demo app, fork it on GitHub Meet Demo App
  15. • Form fields behaviour • Behaviour of auto-locate button •

    Transformation of form data to request • End-to-end user scenarios:
 1. Successful registration
 2. Registration is closed error
 3. Network error • Test on different devices What we can test?
  16. Setup Double Espresso ! androidTestCompile 'com.jakewharton.espresso:espresso:1.1-r3'' { exclude group: 'com.squareup.dagger'

    } ! defaultConfig { testInstrumentationRunner "com.google.android.apps.common.testing" + ".testrunner.GoogleInstrumentationTestRunner" } ! build.gradle packagingOptions { exclude ‘LICENSE.txt' }
  17. public class MBLTDevDemoApp extends Application { ! @Override public void

    onCreate() { super.onCreate(); setModules(getStartModules()); } ! public void inject(Object component) { this.objectGraph.inject(component); } ! public Object[] getModules() { return modules.toArray(new Object[modules.size()]); } ! public void plusModule(Object module) { this.modules.add(module); this.objectGraph = objectGraph.plus(module); } ! public void setModules(Object[] modules) { this.modules = new ArrayList<Object>(Arrays.asList(modules)); this.objectGraph = ObjectGraph.create(modules); } ! protected Object[] getStartModules() { return Modules.list(this); } } Daggerized app
  18. Daggerize test app public class MBLTDevDemoTestApp extends MBLTDevDemoApp { @Override

    protected Object[] getStartModules() { return new Object[]{new MBLTDevDemoModule(this), new MBLTDevDemoTestModule()}; } } @Module( injects = { GetTicketActivity.class, … } ) public class MBLTDevDemoTestModule { ! @Provides @Singleton public LocationService provideLocationService() { return new TestLocationService(); } ! @Provides @Singleton public TestLocationService provideLocationService( LocationService locationService) { return locationService; } ! … } @Module( complete = false, library = true, overrides = true, injects = { DemoTests.class} ) public class MBLTDevDemoModule { ! @Provides @Singleton public LocationService provideLocationService( Context context) { return new RealLocationService(context); } ! … }
  19. Customize TestRunner package com.medlert.android.transport.env; ! … ! public class MBLTDevDemoTestRunner

    extends GoogleInstrumentationTestRunner { ! @Override public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return Instrumentation.newApplication(MBLTDevDemoTestApp.class, context); } ! } ! defaultConfig { testInstrumentationRunner “com.mbltdev.lovelyandroid.env.testrunner” } ! build.gradle
  20. DI in super test public class MBLTDevDemoInstrumentationTestCase2<T extends Activity> extends

    ActivityInstrumentationTestCase2<T> { ! private Object[] modules; ! public MBLTDevDemoInstrumentationTestCase2(Class<T> activityClass) { super(activityClass); } ! @Override protected void setUp() throws Exception { super.setUp(); this.modules = getApp().getModules(); getApp().inject(this); } ! @Override protected void tearDown() throws Exception { getApp().setModules(modules); super.tearDown(); } ! protected MBLTDevDemoApp getApp() { return (MBLTDevDemoApp) (getInstrumentation().getTargetContext().getApplicationContext()); } }
  21. First cup of coffee public class InitialStateTests extends MBLTDevDemoInstrumentationTestCase2<GetTicketActivity> {

    ! public InitialStateTests() { super(GetTicketActivity.class); } ! @Override protected void setUp() throws Exception { super.setUp(); getActivity(); } ! @SmallTest public void testFormInitialState() throws Exception { onView(withId(R.id.first_name_field)).check(matches(isDisplayed())); onView(withId(R.id.last_name_field)).check(matches(isDisplayed())); onView(withId(R.id.city_field)).check(matches(isDisplayed())); onView(withId(R.id.company_field)).check(matches(isDisplayed())); onView(withId(R.id.position_field)).check(matches(isDisplayed())); onView(withId(R.id.get_ticket)).check(matches(isDisplayed())); } ! }
  22. Coffee with Dependency Injection public class BehaviourTests extends MBLTDevDemoInstrumentationTestCase2<GetTicketActivity> {

    ! @Inject TestLocationService testLocationService; @Inject TestBackendService testBackendService; @Inject @TestOperationLengthMs long testOperationLengthMs; … ! @SmallTest public void testAutoLocateButton() throws Exception { testLocationService.setLocation(TestLocations.moscow()); getActivity(); onView(withId(R.id.auto_locate)).perform(click()); Thread.sleep(testOperationLengthMs); onView(withId(R.id.field_city)).check(matches(withText(“Moscow”))); } ! @SmallTest public void testFormNetworkRequest() throws Exception { Request chuckNorrisRequest = TestRequests.chuckNorris(); onFillForm(chuckNorrisRequest); onView(withId(R.id.get_ticket)).perform(click()); assertEquals(chuckNorrisRequest, testBackendService.getLastRequest()); } ! }
  23. Cup with end-to-end user scenarios public class EndToEndTests extends MBLTDevDemoInstrumentationTestCase2<GetTicketActivity>

    { @Inject TestBackendService testBackendService; @Inject @TestOperationLengthMs testOperationLengthMs; … ! @SmallTest public void testSuccessfulRegistration() throws Exception { testBackendService.setGetTicketResponse(Response.success()); getActivity(); onFillForm(TestRequests.chuckNorris()); onView(withId(R.id.get_ticket)).perform(click()); Thread.sleep(testOperationLengthMs); onView(withText(“Congrats! You are registered!”)).check(isDisplayed()); } ! @SmallTest public void testClosedRegistration() throws Exception { testBackendService.setGetTicketResponse(Response.alreadyClosed()); getActivity(); onFillForm(TestRequests.chuckNorris()); onView(withId(R.id.get_ticket)).perform(click()); Thread.sleep(testOperationLengthMs); onView(withText(“Oops… Registration is already closed!”)).check(isDisplayed()); } ! }
  24. • Spoon — https://github.com/square/spoon • SauceLabs — https://saucelabs.com/ • TestObject

    — https://testobject.com/ • Appurify — http://appurify.com/ • Appthwack — http://appthwack.com/ • TestDroid — http://testdroid.com • … Testing on different real devices
  25. Conclusions • It’s good time to start testing of your

    apps • Hope, you will love it • Select Double Espresso or Robolectric • Dependency Injection and Test Automation are big friends • More things is coming • Keep your hand on a pulse!
  26. Links 101 • Espresso
 https://code.google.com/p/android-test-kit/wiki/Espresso • Double Espresso
 https://github.com/JakeWharton/double-espresso •

    Robolectric
 https://github.com/robolectric/robolectric • Dagger
 https://github.com/square/dagger • Demo App
 https://github.com/AlexKorovyansky/mbltdev-demo
  27. Watch and talk • Introduction to Espresso
 http://youtu.be/T7ugmCuNxDU • Introduction

    to Dagger
 http://youtu.be/tPs1e3dQ6FU • GTAC 2013
 http://bit.ly/gtac2013 • GTAC 2014
 http://bit.ly/gtac2013 • Make a talk in GDG RU
 http://bit.ly/gdgru_online_talk