How do you test an Android application across multiple Activities?

Solution 1:

I feel a bit awkward about answering my own bounty question, but here it is...

I've searched high and low on this and can't believe there is no answer published anywhere. I have come very close. I can definitely run tests that span activities now, but my implementation seems to have some timing issues where the tests don't always pass reliably. This is the only example that I know of that tests across multiple activities successfully. Hopefully my extraction and anonymizing of it did not introduce errors. This is a simplistic test where I type a username and password into a login activity, and then observe a proper welcome message is shown on a different "welcome" activity:

package com.mycompany;

import android.app.*;
import android.content.*;
import android.test.*;
import android.test.suitebuilder.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;

import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.*;
import static com.mycompany.R.id.*;

public class LoginTests extends InstrumentationTestCase {

   @MediumTest
   public void testAValidUserCanLogIn() {

      Instrumentation instrumentation = getInstrumentation();

      // Register we are interested in the authentication activiry...
      Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(AuthenticateActivity.class.getName(), null, false);

      // Start the authentication activity as the first activity...
      Intent intent = new Intent(Intent.ACTION_MAIN);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      intent.setClassName(instrumentation.getTargetContext(), AuthenticateActivity.class.getName());
      instrumentation.startActivitySync(intent);

      // Wait for it to start...
      Activity currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Type into the username field...
      View currentView = currentActivity.findViewById(username_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyUsername");

      // Type into the password field...
      currentView = currentActivity.findViewById(password_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyPassword");

      // Register we are interested in the welcome activity...
      // this has to be done before we do something that will send us to that
      // activity...
      instrumentation.removeMonitor(monitor);
      monitor = instrumentation.addMonitor(WelcomeActivity.class.getName(), null, false);

      // Click the login button...
      currentView = currentActivity.findViewById(login_button;
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(Button.class));
      TouchUtils.clickView(this, currentView);

      // Wait for the welcome page to start...
      currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Make sure we are logged in...
      currentView = currentActivity.findViewById(welcome_message);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(TextView.class));
      assertThat(((TextView)currentView).getText().toString(), is("Welcome, MyUsername!"));
   }
}

This code is obviously not very readable. I have actually extracted it into a simple library with an English-like API so I can just say things like this:

type("myUsername").intoThe(username_field);
click(login_button);

I've tested to a depth of about 4 activities and am satisfied that the approach works though as I said, there appears to be an occasional timing issue I have not completely figured out. I am still interested in hearing of any other ways of testing across activities.

Solution 2:

Take a look at Robotium
'a open-source test framework created to make automatic black-box testing of Android applications significantly faster and easier than what is possible with Android instrumentation tests out-of-the-box.'

Homepage: http://www.robotium.org/
Source: http://github.com/jayway/robotium

Please note that the Robotium project is maintained by the company I work for

Solution 3:

You could always use Robotium. It supports blackbox testing just like Selenium but for Android. You will find it at Robotium.org

Solution 4:

I'm surprised no one has mentioned some of the leading automated functional testing tools. Compared with Robotium, these don't require writing Java code.

MonkeyTalk: an open-source tool backed by the company Gorilla Logic. Pros: provides recording as well as a higher-level scripting language easier for non-technical users, and is cross-platform (includes iOS). Given those benefits as requirements, we've found this to be the best solution. It also allows customization beyond what can be done in their scripting language using Javascript.

Calabash-Android: an open-source tool for Cucumber-style features. Pros: write features in the Gherkin language which is Business Readable, Domain Specific Language that lets you describe software’s behavior without detailing how that behavior is implemented. Similar but not exact support is available for iOS in cucumber-ios. Recording capabilities are not as good, as they produce a binary output.

A couple of other references:

  • Here are some additional comparisons between Robotium, Monkeytalk and Calabash. It mentions TestDroid as another possibility.
  • This blog mentions the above plus NativeDriver and Bot-bot.