My favorites | Sign in
Project Logo
                
Search
for
Updated Nov 29, 2008 by phil.h.smith
Labels: Featured
Positron  
An Android story runner.

1. Get it!

Grab positron-VERSION.jar from the front page and save it in myproject/lib.

Add it to eclipse's build path:

2. Turn it on!

In your AndroidManifest.xml file, paste this: (example)

<!-- Required for positron to open it's server socket. -->
<uses-permission android:name="android.permission.INTERNET"/>

<!-- Required for positron to disable the keyguard, should it be on during tests. -->
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>

<instrumentation android:label="Positron"
	android:name="com.googlecode.autoandroid.positron.Positron"
	android:targetPackage="com.example.android.notepad"/> <!-- YOUR PACKAGE GOES HERE -->

Now change the android:targetPackage to your package name

Make sure the JRE System Library is on your build path before the Android Library:

3. Use it!

Create a new source folder named stories:

Add a class inside extending com.googlecode.autoandroid.positron.junit4.TestCase (be sure junit is included in your project):

public class BasicUsageStories extends TestCase {
	
	@Test
	public void shouldSeeEggTimerActivity() throws InterruptedException {
//		              Your package            Your activity
		startActivity("com.example.eggtimer", "com.example.eggtimer.EggTimer");
		pause();
		assertEquals("EggTimer", stringAt("class.simpleName"));
	}
	
}

Fire up your app in the emulator.

Run the test as a junit suite in eclipse:

Beware!

It's unfortunate that raw English isn't executable, so beware! This page may get stale. The best reference is of course the source. In particular, a complete copy of the notepad project is available with working sample stories. The javadoc jar available on the front page may also be useful.

General Architecture

Early in its life, positron bundled stories with the runner in the application. The stories were then run on-device. To reduce the development cycle and ease IDE integration, a client-server model is now used.

The server is a special instrumentation which is still bundled with the application and run on-device. Using adb's port-forwarding capabilities, it exposes a small http service to communicate with the client.

The client runs on the host OS and exposes an api that stories may use. For convenience junit 3 and junit 4 test superclasses using the client are also provided.

One consequence of this arrangement is the impossibility of handling native android instances (such as an Activity) directly in the story. Thus the api restricts itself to primitives and collection types.

Inspecting Views

If a story cannot directly manipulate an Activity, how is it supposed to ask about its state?

It asks the story runner to do it. Unfortunately this implies either a pile of proxy objects or a small DSL. Positron has opted to take the DSL route.

A simple syntax of dot-separated properties is used with reflection to call getters for each named property. If the 'property' is an integer, then we try to index the receiving object in a reasonable way. Arrays use literal indexing, and ViewGroups (such as ListViews) use getChildAt.

Suppose the activity is a ListActivity with customized rows, and the story is interested in seeing the contents of one of the text views in the 2nd row.

To address this, positron provides the stringAt method: stringAt("listView.1.0.text") will fetch the listView property of the current activity, ask it for its second child, ask that for its child, and return the text property.

The variations of *At tweak the return type (yielding intAt, doubleAt, etc), or allow inspection to start at an activity further down the stack.

Pausing and Thread Safety

Android stories can be similar to selenium or watir web stories, but there is a striking difference: at any moment asynchronous code may affect the interface your story is interacting with. This opens up the possibility of race conditions between the story and application.

To address this android itself provides the runOnMainSync method. It executes the given runnable on the UI thread, which usually means the target application can't do anything else while it is running. As an example, a block of assertions inside the Runnable could examine the UI safely.

Suppose we pass runOnMainSync a Runnable that just blocked execution until some signal was received. If the runOnMainSync call was made on a child thread then the positron server wouldn't block. This functionality could then be wrapped in a pair of api calls: one could pause() execution of the target application so it may be inspected. Later another call could resume() it, allowing it to process events. Positron provides these calls just so.

A paused application cannot process events. This is good for nailing it under a magnifying glass, but a story will want to generate events too. It would be cumbersome to resume(), make a call and wait for the event queue to clear only to pause() once more. So the story runner does it for you. Whenever an api call generates an event for a paused application, that application is 'momentarily' resumed so that pending events (including the new one) clear out. The application is then re-paused.

When the events generated this way completely finish processing before returning, this works great. However, some events (such as motion events) continue to have side-effects on the UI after their handlers return. Since positron pauses the application upon return, these side-effects might not ever finish processing. If the story manually resumes the app, however, it has no way of knowing when they've cleared out to pause again.

Well, the story could resume the app and repeatedly call stringAt to watch some part of the (live) UI to see when an expected change occurs, and then pause again. Or until some amount of time goes by and the story gives up. But nobody wants to write that loop, so the runner does it for you. boolean waitFor(String path, String value, long timeout) will resume the app and poll the given path until it evaluates to value or timeout milliseconds go by. The app is then paused again either way. waitFor itself retuns true if the value was observed, false if the timeout was reached. The SelectANote notepad story has an example:

@Test
public void shouldSelectByTouchingAnEntry() {
	touch("listView.1");
	assertTrue(waitFor("class.simpleName", "NoteEditor", 500));
	assertEquals("Nap in the hammock", stringAt("#note.text"));
}

Pressing Keys

Stories may send key events.

All the key event calls available on the instrumentation superclass are also available (though slightly renamed to take the Sync off the end.)

Several KeyEvent.KEYCODE_* keycodes have aliases within the api, most just stripping off the KEYCODE_ prefix. See the javadoc for a list.

Databases

It's hard to write meaningful stories without making assumptions about the application's state. Android applications will probably make use of SQLite3 databases for most persistent state. To allow a story to prepare a fixture, positron will run arbitrary sql against an android database.

Entire scripts may be run at a time: statements are terminated with a semicolon, comments begin with -- and continue to end of line.

Positron will try to backup and restore all databases in the application when the instrumentation starts and stops, by copying the relevant *.db files. However, if the server crashes in a particularly awesome way then restoration might fail, so watch out.

In previous versions, positron would allow a story to run sql kept in a 'raw' resource inside the application. This is no longer the case.


Comment by jackcholt, Nov 19, 2008

Under Databases it says "Entire scripts may be run at a time: statements are terminated with a semicolon, comments begin with -- and continue to end of line."

What method do I use?

Comment by the.edouard.mercier, Nov 27, 2008

Thank you so much for making Positron available on Android SDK 1r1! This is a wonderful tool, especially when it comes to industrializing and automating the profiling!

Comment by phil.h.smith, Nov 29, 2008

jackcholt,

The sql() call takes sql statements to run, but it can take many at a time as well. I tweaked the SelectANote story in the notepad sample to demonstrate: take a peek at it to see what I mean.

The sample story just embeds the script as one long string constant, but you could imagine loading it from a file, for example. The api doesn't yet provide a signature that takes a Reader, since it would need to buffer it all internally first anyway. One could be added later if there's demand.

Comment by chris.boulter, Feb 09, 2009

A few hints from my experience of setting this up:

1) When creating the example story (BasicUsageStories?) you'll need to static import a JUnit method:

import static org.junit.Assert.assertEquals;

2) you'll need to set your ANDROID_HOME or ANDROID_SDK environment variable (in your Eclipse Run Configuration) to the location of your SDK

3) You'll have to import org.junit.Test and add

@Test
to the test method in your test class, i.e. add it before shouldSeeEggTimerActivity() above. If you fail to do this, 'Run As' won't show 'JUnit test'.

4) If you get 'java.net.SocketException?: Connection reset' exceptions when you run your JUnit test, I think this happens if the Activity you specify doesn't have any UI (doesn't call setContentView)

Comment by stan.berka, Feb 11, 2009

Hey, it's great! Thanks! And thanks for the comments above. They were very helpful. I have two questions/issues:

1. When I show a List View with two items and then call "press(DOWN,ENTER);", the app acts upon the second item in the list. If I do the same action manually, whether in the emulator or on a real device, it acts upon the first item. Why is it different? I think, the autoAndroid is wrong here.

2. I had a following test:

    @Test
    public void shouldViewExistingWikiNote() throws InterruptedException {
        sql("data", "DELETE FROM notes");
        ...
    }

When I run it under jUnit, it complains that there is no table "notes". But, if I run the following test first, it works fine:

    @Test
    public void shouldSeeListActivity() throws InterruptedException {

            startActivity("org.sjb.droidwiki", "org.sjb.droidwiki.DroidWiki");
            pause();
            assertEquals("DroidWiki", stringAt("class.simpleName"));
    }

How come? Is there something mysterious going on with the DB at the beginning/end of the series of tests?

Comment by stan.berka, Feb 11, 2009

Question! Do I need to remove from the manifest file sections used only by the positron before deploying the app onto a real device?

Comment by phil.h.smith, Feb 16, 2009

stan.berka:

1. Unfortunately I don't have a T-Mobile G1 handy, so I'm keenly interested in differences between the instrumented emulator and real hardware. If you find more issues like this, please open an issue.

2. The missing table is due the underlying database being missing! Android sqlite3 databases are created on-demand when they are first (properly) opened. Opening an activity (with the UI or startActivity) that uses your provider will do this setup, for example. Positron uses a cheaty-face low-level api to directly talk to the DB, because it doesn't know where your SQLiteOpenHelper is (notepad's is a private static inner class inside the provider. Not easy to get to.) The big consequence of that is any initialization done in your SQLiteOpenHelper doesn't happen if positron is the first thing to access the db: positron will only get it's hands on a table-less schema.

The cheap way out is to open your application manually one time after you modify your db schema or wipe the emulator to make it initialize the db. Then you should be able to run your tests with impunity. Or, you could look at rearranging your SQLiteOpenHelper code to make it more accessible to the tests (and maybe share any emergent patterns you find.)

(3) Yes, you should remove the lines declaring positron (and it's permissions) from your manifest before shipping production. You could perhaps use a scheme with two manifest files, or a snippet of xslt to inject the needed positron tags into an otherwise clean manifest. If you come up with something cool, let us know!

Comment by phil.h.smith, Feb 16, 2009

stan.berka:

re: (3): To be clear, the "should" in "Yes, you should" is the same should as in "You should not ship production with a open socket letting arbitrary code ravage your application." Your program will still function if positron is included, it's just a giant security hole.

Comment by stan.berka, Feb 17, 2009

Phil, The item (1) from my comment from Feb 11 was about a difference between the Positron API and the G1, whether on emulator or the real device. There are really two questions: 1. which item is selected if I open a list and press DOWN: the first or the second item? Emulator/phone: 1st one. Positron: 2nd one. 2. which item, if any is selected after opening a list: none or the first one? Emulator/phone: none. Positron: 1st one. Is there a way to fix the Positron to behave just like the emulator and the phone?

Another comment: It seems, every time before running Positron tests, I need to redeploy the whole application. It is pretty much obvious, now, that I think of it. But it's different than running the jUnit tests. For jUnit tests, I don't need to repeat "Run as Android App" every time. I just rerun the tests. I guess that's because the jUnit tests run not on the emulator but in the standard jdk.

Comment by rajendran.bits, Feb 19, 2009

Hi,

I have doubt regarding the positron, can you tel me is it possible to test a third part app with out source code.....

Comment by rhysawil...@yahoo.co.uk, Feb 24, 2009

Hi,

Is it possible to simulate a button click using positron? I've tried doing the following with no luck: touch("myButton"); click();

None of my onClick or onTouch event handlers are being called.

Thanks

Comment by haemmerlech, Feb 26, 2009

Hello,

looks like an interesting project to me, but I wonder what the benefit of Positron is, compared to the built in junit-framework? Seems like I can do all sorts of testing with that too, including invoking GUI events...

Comment by j.steinblock, Apr 21, 2009

Is it possible to use some android.jar functions with positron? I have setup positron correctly and the default example startActivity... works fine.

No I try to invoke a class in my project that uses android.util.Log to dump some output. If I run the class from junit I get a "java.lang.NoClassDefFoundError?: android/util/Log" error. even if I added the Android Library to my classpath (of course after junit and positron)

If I understand the architecture of positron correctly, I can "remotly" invoke commands on the emulator. I have a startActivity("com.example.android.notepad", "com.example.android.notepad.NotesList?"); command and the At(...) functions.

What I need is to invoke a static function in my class. In android I would do: String result = MyClass?.MyFunction?("value1", "value2");

Is there something like: String result = stringAt("MyClass?.MyFunction?(\"value1\", \"value2\")"); ?

Comment by stan.berka, Apr 22, 2009

Phil, Positron/Autoandroid is great. I use jUnit for classes not referring to Android explicitly, but the meat of my app (DroidWiki?) is not a candidate for jUnit, at least I don't know how to do it. However, having a growing set of acceptance test, for most features of the app, makes a BIG difference! Thanks! Also, thanks for support!

Comment by azadbol...@bolour.com, Apr 30, 2009

Hi,

Thanks for a great tool.

I just upgraded to SDK 1.5_r1 and I am having trouble getting the positron tests in the sample notepad application (http:/autoandroid.googlecode.com/svn/trunk/samples/notepad) working in the new environment. After building the application and installing it on a 1.1 target, I tried to run the stories. But the ant run-stories task just hangs in my environment.

Is positron tested on 1.5_r1? Anything special I need to be aware of?

Part of my problem may be that ant support for SDK 1.5 has changed, so I had to merge the project's original build.xml file with the build.xml created by the 1.5 android tool. If there is a sample ant build.xml file that is compatible with the ant standard for SDK 1.5, and that will build and install an application and build and run positron tests against that application , I would really appreciate a copy.

Many thanks.

Azad

Comment by plaacebokid, Sep 02, 2009

Seems to be nice tool, but doesn't work for SDK 1.5. Could you fix that? :)

Comment by stan.berka, Sep 02, 2009

I'm using the Autoandroid with SDK 1.5 without problems. However, I don't use ant. Just eclipse with ADT.

On another note, I'd love to be able to place the autoandroid stuff into a separate project that depends on my application project. Anyone knows if it is possible and how to do it? Pleeeaaase.


Sign in to add a comment
Hosted by Google Code