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

Android Testing 2.0

Android Testing 2.0

Build a toolset to write rock solid apps.

Prateek Srivastava

April 10, 2015
Tweet

More Decks by Prateek Srivastava

Other Decks in Technology

Transcript

  1. Android Testing

  2. Unit Tests small scoped and isolated

  3. public static boolean isNullOrEmpty(String text) { return TextUtils.isEmpty(text) || TextUtils.getTrimmedLength(text)

    == 0; }
  4. JUnit http://junit.org

  5. public class UtilsTest extends TestCase { public void testIsNullOrEmpty() {

    assertEquals(isNullOrEmpty(null), true); assertEquals(isNullOrEmpty(""), true); assertEquals(isNullOrEmpty(" "), true); assertEquals(isNullOrEmpty("foo"), false); } }
  6. public class UtilsTest { @Test public void isNullOrEmpty() { assertEquals(isNullOrEmpty(null),

    true); assertEquals(isNullOrEmpty(""), true); assertEquals(isNullOrEmpty(" "), true); assertEquals(isNullOrEmpty("foo"), false); } }
  7. java.lang.RuntimeException: Stub!

  8. public class TextUtils { ... public static boolean isEmpty(CharSequence cs)

    { throw new RuntimeException("Stub!"); } public static int getTrimmedLength(CharSequence cs) { throw new RuntimeException("Stub!"); } }
  9. Android Testing Framework https://developer.android.com/tools/testing/ testing_android.html

  10. public class UtilsTest extends TestCase { public void testIsNullOrEmpty() {

    assertEquals(isNullOrEmpty(null), true); assertEquals(isNullOrEmpty(""), true); assertEquals(isNullOrEmpty(" "), true); assertEquals(isNullOrEmpty("foo"), false); } } http://developer.android.com/reference/junit/framework/ TestCase.html
  11. Slow!

  12. Can’t Test Outside Device

  13. Can’t Test Outside Device

  14. Java Module

  15. public static boolean isNullOrEmpty(String text) { return text == null

    || text.trim().length() == 0; }
  16. None
  17. settings.gradle include 'library' include 'app' app/build.gradle dependencies { compile(':library') }

  18. Robolectric http://robolectric.org/

  19. @RunWith(RobolectricTestRunner.class) public class UtilsTest { @Test public void isNullOrEmpty() {

    assertEquals(isNullOrEmpty(null), true); assertEquals(isNullOrEmpty(""), true); assertEquals(isNullOrEmpty(" "), true); assertEquals(isNullOrEmpty("foo"), false); } }
  20. @RunWith(RobolectricTestRunner.class) public class UtilsTest { @Test public void isNullOrEmpty() {

    assertEquals(isNullOrEmpty(null), true); assertEquals(isNullOrEmpty(""), true); assertEquals(isNullOrEmpty(" "), true); assertEquals(isNullOrEmpty("foo"), false); } }
  21. Robolectric + Gradle https://github.com/segmentio/analytics-android/blob/2.5.1/core- tests/build.gradle#L46

  22. Instrumentation Tests larger scoped, black box testing Espresso, Spoon, Wiremock,

    Oh my! - Michael Bailey https://www.youtube.com/watch?v=-xQCNf_5NNM
  23. public class LoginActivity extends Activity { @InjectView(R.id.username) EditText username; @InjectView(R.id.password)

    EditText password; @OnClick(R.id.login) void onLoginClicked() { boolean hasError = false; if (TextUtils.isEmpty(username.getText())) { username.setError(getString(R.string.required)); hasError = true; } else { username.setError(null); } if (TextUtils.isEmpty(password.getText())) { password.setError(getString(R.string.required)); hasError = true; } else { password.setError(null); } if (!hasError) { Intent intent = new Intent(LoginActivity.this, OrderActivity.class); intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); } } }
  24. Instrumentation API https://developer.android.com/reference/android/app/ Instrumentation.html

  25. public void testEmptyPassword_ShowsError() { // Make sure the initial state

    does not show any errors. assertEquals(username.getError(), null); assertEquals(password.getError(), null); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { // Type a value into the username and password fields. username.setText("elvis"); password.setText(""); // Click the "login" button. login.performClick(); } }); instrumentation.waitForIdleSync(); // Verify error was shown only for password field. assertEquals(username.getError(), null); assertEquals(password.getError(), getString(R.string.required)); }
  26. public void testEmptyPassword_ShowsError() { // Make sure the initial state

    does not show any errors. assertEquals(username.getError(), null); assertEquals(password.getError(), null); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { // Type a value into the username and password fields. username.setText("elvis"); password.setText(""); // Click the "login" button. login.performClick(); } }); instrumentation.waitForIdleSync(); // Verify error was shown only for password field. assertEquals(username.getError(), null); assertEquals(password.getError(), getString(R.string.required)); }
  27. assertEquals(password.getError(), null); assertEquals(password.getVisibility(), VISIBLE); assertEquals(password.getText(), null); assertEquals(password.getHint(), getString(R.string.password));

  28. expected: <8> but was: <4>

  29. AssertJ-Android http://square.github.io/assertj-android/

  30. assertThat(username) .hasNoError() .isVisible() .isEmpty() .hasHint(R.string.hint);

  31. Expected visibility <gone> but was <invisible>.

  32. Robotium https://code.google.com/p/robotium/

  33. public void testEmptyPassword_ShowsError() { // Make sure the initial state

    does not show any errors. assertThat(username).hasNoError(); assertThat(password).hasNoError(); // Type a value into the username and password fields. solo.typeText(username, "elvis"); solo.typeText(password, ""); // Click the "login" button. solo.clickOnView(login); // Verify error was shown only for username field. assertThat(username).hasNoError(); assertThat(password).hasError(R.string.required); }
  34. Espresso https://code.google.com/p/android-test-kit/

  35. public void testEmptyPassword_ShowsError() { // Make sure the initial state

    does not show any errors. onView(withId(R.id.username)).check(matches(hasNoError())); onView(withId(R.id.password)).check(matches(hasNoError())); // Type a value into the username and password fields. onView(withId(R.id.username)).perform(typeText("elvis")); onView(withId(R.id.password)).perform(typeText("")); // Click the "login" button. onView(withId(R.id.login)).perform(click()); // Verify error was shown only for username field. onView(withId(R.id.password)).check(matches(hasNoError())); onView(withId(R.id.password)) .check(matches(hasError(R.string.required))); } https://code.google.com/p/android-test-kit/wiki/ EspressoV2CheatSheet
  36. Spoon https://github.com/square/spoon http://square.github.io/spoon/sample/index.html

  37. None
  38. public void testEmptyPassword_ShowsError() { Spoon.screenshot(activity, "initial_state"); // Make sure the

    initial state does not show any errors. onView(withId(R.id.username)).check(matches(hasNoError())); onView(withId(R.id.password)).check(matches(hasNoError())); // Type a value into the username and password fields. onView(withId(R.id.username)).perform(typeText("elvis")); onView(withId(R.id.password)).perform(typeText("")); Spoon.screenshot(activity, "typed_text"); // Click the "login" button. onView(withId(R.id.login)).perform(click()); Spoon.screenshot(activity, "login_clicked"); // Verify error was shown only for username field. onView(withId(R.id.password)).check(matches(hasNoError())); onView(withId(R.id.password)) .check(matches(hasError(R.string.required))); }
  39. public void testEmptyPassword_ShowsError() { Spoon.screenshot(activity, "initial_state"); // Make sure the

    initial state does not show any errors. onView(withId(R.id.username)).check(matches(hasNoError())); onView(withId(R.id.password)).check(matches(hasNoError())); // Type a value into the username and password fields. onView(withId(R.id.username)).perform(typeText("elvis")); onView(withId(R.id.password)).perform(typeText("")); Spoon.screenshot(activity, "typed_text"); // Click the "login" button. onView(withId(R.id.login)).perform(click()); Spoon.screenshot(activity, "login_clicked"); // Verify error was shown only for username field. onView(withId(R.id.password)).check(matches(hasNoError())); onView(withId(R.id.password)) .check(matches(hasError(R.string.required))); }
  40. None
  41. Fork https://github.com/shazam/fork

  42. Mocks script responses, verify interactions http://martinfowler.com/articles/mocksArentStubs.html

  43. Mockito http://mockito.org https://corner.squareup.com/2012/10/mockito-android.html

  44. analytics.identify("elvis", new Traits() .putFirstName("Elvis") .putLastName("Presley") .putEmail("[email protected]"));

  45. class MixpanelAdapter implements Adapter { final MixpanelAPI.People people; … void

    identify(IdentifyMessage message) { JSONObject json = new JSONObject(); json.put("$email", message.email()); json.put("$first_name", message.firstName()); json.put("$last_name", message.lastName()); people.identify(message.userId()); people.set(json); } }
  46. @Test public void identify() throws JSONException { // 1. Prepare

    our mocks MixpanelAPI.People people = Mockito.mock(MixpanelAPI.People.class); MixpanelAdapter adapter = mockAdapter(people); Analytics analytics = createAnalyticsWithMock(adapter); // 2. Exercise the test analytics.identify("elvis", new Traits() .putFirstName("Elvis") .putLastName("Presley") .putEmail("[email protected]")); JSONObject expected = new JSONObject(); expected.put("userId", "elvis"); expected.put("$email", "[email protected]"); expected.put("$first_name", "Elvis"); expected.put("$last_name", "Presley"); // 3. Verify that we saw what we expected Mockito.verify(people).identify("elvis"); Mockito.verify(people).set(eq(expected)); }
  47. PowerMockito https://code.google.com/p/powermock/

  48. class LocalyticsAdapter implements Adapter { void identify(IdentifyMessage message) { Localytics.setCustomerId(message.userId());

    Localytics.setIdentifier("email", message.email()); Localytics.setIdentifier("customer_name", message.name()); } }
  49. @Test public void identify() { // 1. Prepare mocks. PowerMockito.mockStatic(Localytics.class);

    Analytics analytics = createAnalytics(); // 2. Test the code of interest. analytics.identify("elvis", new Traits() .putFirstName("Elvis") .putLastName("Presley") .putEmail("[email protected]")); // 3. Validate that we saw exactly what we wanted. PowerMockito.verifyStatic(); Localytics.setCustomerId("elvis"); PowerMockito.verifyStatic(); Localytics.setIdentifier("email", "[email protected]"); PowerMockito.verifyStatic(); Localytics.setIdentifier("customer_name", "Elvis Presley"); }
  50. MockWebServer https://github.com/square/okhttp/tree/master/mockwebserver

  51. public void test() throws Exception { // Create a MockWebServer.

    MockWebServer server = new MockWebServer(); // Schedule some responses. server.enqueue(new MockResponse().setBody("hello, world!")); // Start the server. server.start(); // Ask the server for its URL. You'll need this to make HTTP requests. URL baseUrl = server.getUrl(“/v1/chat/“); // Exercise your application code, which makes those HTTP requests. // Responses are returned in the same order that they are enqueued. Chat chat = new Chat(baseUrl); chat.loadMore(); assertThat(chat.messages()).containsExactly("hello, world!"); // Confirm that your app made the HTTP requests you were expecting. RecordedRequest request1 = server.takeRequest(); assertEquals("/v1/chat/messages/", request1.getPath()); assertThat(request1.getHeader("Authorization")).isNotNull(); // Shut down the server. server.shutdown(); }
  52. WireMock http://wiremock.org

  53. Monkey http://developer.android.com/tools/help/monkey.html

  54. adb shell monkey -p your.package.name -v 500

  55. Caliper https://code.google.com/p/caliper/ https://code.google.com/p/caliper/wiki/CaliperOnAndroid

  56. public class MyBenchmark extends Benchmark { public void timeMyOperation(int reps)

    { for (int i = 0; i < reps; i++) { MyClass.myOperation(); } } }
  57. Vogar https://code.google.com/p/vogar/ https://android.googlesource.com/platform/external/vogar/

  58. Vogar JAR : https://www.dropbox.com/s/ddjv2slngzon78r/vogar.jar?dl=0 Sample Script and Benchmarks: https://github.com/segmentio/cartographer/blob/master/ run_vogar.sh

  59. java -jar vogar.jar --benchmark --verbose --mode device --sourcepath …

  60. Questions? @f2prateek