views:

4565

answers:

9

We are building a complex Android application consisting of many screens and workflows spread across many Activities. Our workflows are similar to what you might see on a Bank's ATM machine, for example, there is an Activity to login in that transitions to a main menu Activity which can transition to other activities based on the user's choices.

Since we have so many workflows we need to create automated tests that span multiple activities so we can test a workflow from end to end. For example, using the ATM example, we would want to enter a valid PIN, verify that sends us to the main menu, choose withdraw cash, verify that we are on the withdraw cash screen, etc., etc., and eventually find ourselves back on the main menu or "logged" out.

We've toyed with the test APIs that come with Android (e.g. ActivityInstrumentationTestCase2) and also with Positron, but neither seem capable of testing beyond the bounds of a single Activity, and while we can find some utility in these tools for some unit testing, they won't meet our needs for testing scenarios that cut across multiple Activities.

We are open to an xUnit framework, scripting, GUI recorders/playbacks, etc. and would appreciate any advice.

A: 

I haven't personally used it, but ApplicationTestCase looks like it might be what you're looking for.

Eric
Unfortunately there are no examples indicating that to be the case.
SingleShot
Yea, looks like you're right...was tricked by the name. I can't figure this one out. Best approach I've got so far is to use positron's ActivityUnitTestCase to verify that the next activity is started, but that doesn't help you build coherent stories.Alternatively, InstrumentationTestCase.launchActivity might allow you to start an arbitrary number of activities, but I'm still trying to figure out the Instrumentation stuff.
Eric
+5  A: 

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.

SingleShot
You might try adding the FlakyTest annotation to repeat the test automatically when timing issues cause it to fail. Not a solution, really, but a viable workaround in some situations.
Carl Manaster
A: 

Hi,

iam trying to work on a similar example where in i need to design a user login form,authenticate it and connect it to say a social networking site.I tried searching for a lot of referencees and finally found your post quite relevant to my requirement.Iam a beginner in android and would like to learn a more about the above procedure.It will be of graet help if you provide a link for your whole project so that i will be able to relate on how it actually works

Thanks in advance

Jane

jane
Hi Jane. I cannot provide a link to the project as it belongs to the company I work for. See Instrumentation and TouchUtils in the Android API. Those monitors I use are the only way for me to get the next activity after I leave the current one.
SingleShot
A: 

Hey all, There is another way to do the multiple activity using ActivityInstrumentation Class.. Its a normal automation scenario... First get the focus of what ever object you want and then send a key Simple as that sample code

button.requestFocus(); sendKeys(KeyEvent.KEYCODE_ENTER);

Only thing is understanding the every API calls will help us.

Think this helps

Sandeep

sandeep
+2  A: 

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

Renas
+6  A: 

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

Jonas Söderström
A: 

Hello,

Is there anyway to go back to a previously launched Activity while running an Instrumentation test? Currently, when I use sendKeyDownUpSync( KeyEvent.KEYCODE_BACK);, this forces my test to exit, rather than go back to the previous Activity.

Any help with this is greatly appreciated.

Jin

Jin Park
A: 

When I try the selected answer, I get the following error:

java.lang.SecurityException: Injecting to another application requires INJECT_EVENT permission at android.os.Parcel.readException(Parcel.java:1218) at android.os.Parcel.readException(Parcel.java:1206) at android.view.IWindowManager$Stub$Proxy.injectKeyEvent(IWindowManager.java:805) at android.app.Instrumentation.sendKeySync(Instrumentation.java:859) at android.app.Instrumentation.sendKeyDownUpSync(Instrumentation.java:871) at com.jayway.android.robotium.solo.Solo.pressMenuItem(Solo.java:556) at com.jayway.test.NotePadTest.testAddNote(NotePadTest.java:38) at java.lang.reflect.Method.invokeNative(Native Method) at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:205) at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:195) at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:175) at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169) at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154) at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)

I tried adding the permission to the manifest file, but that didn't help.

For a follow-up question like this it would be better to ask it as a new question ("Ask Question" button in the top right of the page), not post it here in an old thread. More people would see it and try to solve your problem.
sth
A: 

You can find new approach see the following blog: http://auto-simple.blogspot.com/2010/03/automate-google-maps-on-android.html

Guy