main activity of the application under test mActivity = getActivity(); // Get a handle to the Activity object's main UI widget, a Spinner mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner); // Set the Spinner to a known position mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION); // Stop the activity - The onDestroy() method should save the state of the Spinner mActivity.finish(); // Re-start the Activity - the onResume() method should restore the state of the Spinner mActivity = getActivity(); // Get the Spinner's current position int currentPosition = mActivity.getSpinnerPosition(); // Assert that the current position is the same as the starting position assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
power Ŷ TestRunner // assert that textview does not start with with "XYZ" and contains "ABC" anywhere onView(withId(R.id.celsius_textview)) .check(matches(allOf(withText(not(startsWith("XYZ"))), withText(containsString("ABC"))))); // assert that textview ends with "XYZ" or contains "ABC" anywhere onView(withId(R.id.celsius_textview)) .check(matches(anyOf(withText(endsWith("XYZ")), withText(containsString("ABC")))));
if a double has the value NaN (not a number) */ public class IsNotANumber extends TypeSafeMatcher<Double> { @Override public boolean matchesSafely(Double number) { return number.isNaN(); } public void describeTo(Description description) { description.appendText("not a number"); } @Factory public static <T> Matcher<Double> notANumber() { return new IsNotANumber(); } }
Actions. */ public class CustomViewActions { private CustomViewActions() { } /** * Returns an action that clears text on the view. Extra thoroughly! */ public static ViewAction clearTextExtraThoroughly() { return new ClearTextAction(); } }
@Override public String getName() { return IdleMonitor.class.getSimpleName(); } @Override public boolean isIdleNow() { // return true if resource is idle return false; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { // store a reference to the resourceCallback // notify resourceCallback when idle } } Espresso.registerIdlingResources(idlingResource);
implementations of Android core libraries) too slow / hard for unit testing without the baggage of a device Replaces the behavior of code Ŷ Write JUnit tests Ŷ When? Ŷ Does this by using Shadow classes Ŷ InstrumentationTestCase Ŷ vs real device / speedy emulator Ŷ http://robolectric.org/
restricting the test to a single package Event types and frequencies command-line tool Ŷ basic configuration Ŷ $ adb shell monkey -p your.package.name -v 500 Ŷ
rate limit exceeded? HTTP calls should be asynchronous … Hit a real server? Ŷ Brittle tests! Ŷ Slow tests Ŷ Network or server is down Ŷ Incomplete tests Ŷ Further complicated in Android Ŷ
required in your app codebase • can be shared across multiple platforms (Android, iOS, web, …) • another dependency (that can fail) • something new to figure out • not always easy to trigger errors or edge cases • what if ‘state’ is needed? • ‘slow’ tests execution, still HTTP calls needed PROs Ŷ CONs Ŷ
… • easy to generate errors / edge cases • easy to control server-side state • write your dummy data in Java • extra test code needed • you don’t test the JSON deserialization (but you can add unit tests) • mock a part of your application ➪ not a true integration test PROs Ŷ CONs Ŷ You trust the test suites of Retrofit, Gson, … and mock out the data
REST adapter which points to the GitHub API endpoint. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(API_URL) .build(); // Wrap our REST adapter to allow mock implementations and fake network delay. MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter); // Instantiate a mock object so we can interact with it later. MockGitHub mockGitHub = new MockGitHub(); // Use the mock REST adapter and our mock object to create the API interface. GitHub gitHub = mockRestAdapter.create(GitHub.class, mockGitHub); // Query for some contributors for a few repositories. printContributors(gitHub, "square", "retrofit"); printContributors(gitHub, "square", "picasso");
Specify responses Ŷ Test HTTP clients Ŷ Verify requests Ŷ Like Mockito (for HTTP stack) Ŷ • script your mocks • run app code • verify that expected requests were made
that you can create // a new instance for every unit test. MockWebServer mockWebServer = new MockWebServer(); mockWebServer.play(); // Setup the MockWebServer & schedule a response MockResponse mockResponse = new MockResponse(); mockResponse.setResponseCode(500); mockWebServer().enqueue(mockResponse); // Setup your networking lib. Ask the server for its url. … mockWebServer.getUrl(“/“); DO YOUR TEST
your app do requests to http://localhost:8080 // setup MockWebServer onView(withId(R.id.submit_button)).perform(click()); onView(withText(“error message”)).check(matches(isDisplayed())); verify(postRequestedFor(urlMatching(“/my/resource/[a-z0-9]+”))); }
Robotium Automated Testing for Android Hrushikesh Zadgaonkar (9781782168010) The Busy Coder’s Guide to Android Development Mark Murphy - http://commonsware.com/Android/
Continuous Integration: Improving Software Quality and Reducing Risk Duvall, Paul M. et al. (978-0321336385) Working Effectively with Legacy Code Feathers, Michael (978-0131177055)