Android Test Driven Development

Test Driven Development on android turned out to be much more frustrating than I expected. My hopes were high as I had seen that Android provides ActivityUnitTestCase for “isolated testing of a single activity”. However these tests must be run on a emulator or device making them rather slow. In order to do TDD the time taken to run the relevant unit test needs to be on par with saving a file i.e. in the order of milliseconds. In fact when doing TDD on an IDE I prefer to run a test (and automatically save the file) rather than saving directly. This way I get two bits of feedback every time I make a change 1. Does it compile? 2. Does the junit pass? This mode of work proved impossible using ActivityUnitTestCase as each run of a test involved a project being built and transferred to the device/emulator before being run taking ~10 secs each time.

I hope Google has plans for a speedier way to do this on Android but in the mean time I looked for an alternative. Robolectric objectives seemed to match what I was looking for so I gave it a go.  It intercepts the loading of Android classes so you can run unit tests on your local JVM. It seems like a hacky solution to me but until google tackles the core problem I can’t see any alternatives.

Below is my test.


package test.ie.shizzle;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import ie.shizzle.R;
import ie.shizzle.media.PlayerActivity;
import ie.shizzle.ref.StaticStrings;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URISyntaxException;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.VideoView;

import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
public class PlayerTest {
	PlayerActivity activityUnderTest;
	Bundle bundle;
	String[] videoIDs = {"tG7cM5Yvhz4", "v_UyVmITiYQ"};
	String[] videoNames = {"song 1", "song 2"};
	String artistName = "bojo";
	String gdataResponse;
	/**
	 * 
	 * Creates an initial bundle to pass the activityUnderTests onCreate method
	 * Creates a mock of a response from gdata from gdataResponse.xml
	 *  
	 * @throws IOException
	 */
	@Before
	public void setup() throws IOException{
		activityUnderTest = new PlayerActivity();
		bundle = new Bundle();
		bundle.putStringArray(StaticStrings.KEY_VIDEO_IDS, videoIDs);
		bundle.putStringArray(StaticStrings.KEY_VIDEO_NAME, videoNames);
		bundle.putString(StaticStrings.KEY_ARTIST, artistName);
		File file =  new File(getClass().getResource("gdataResponse.xml").getPath());
		FileReader fileReader =  new FileReader(file);
		BufferedReader bufferedReader = new BufferedReader(fileReader);
		StringBuilder builder = new StringBuilder();
		while(bufferedReader.ready()){
			builder.append(bufferedReader.readLine());
		}
		gdataResponse = builder.toString();
		TestHttpResponse response = new TestHttpResponse(200, gdataResponse);
		Robolectric.addPendingHttpResponse(response);
		activityUnderTest.onCreate(bundle);
	}
	
	/**
	 * Tests that the artist and song are displayed correctly
	 * Tests all the views are attached to the activity
	 * 
	 * @throws IOException
	 * @throws URISyntaxException
	 */
	@Test
	public void testPlayingFirstSong() throws IOException, URISyntaxException{
		assertNotNull(activityUnderTest);
		assertEquals(((TextView)activityUnderTest.findViewById(R.id.artist)).getText(),"bojo - song 1");
		assertNotNull(((VideoView)activityUnderTest.findViewById(R.id.video)));
		assertNotNull(((ImageView)activityUnderTest.findViewById(R.id.echoNextLink)));
	}
	
}

Its not very complex but I immediately ran into problems (see stack trace below) because my activity makes a google api call and the robolectric TestHttpResponse hasn’t implemented HttpEntityStub.getContentEncoding.



java.lang.UnsupportedOperationException
	at com.xtremelabs.robolectric.tester.org.apache.http.HttpEntityStub.getContentEncoding(HttpEntityStub.java:31)
	at com.google.api.client.http.apache.ApacheHttpResponse.getContentEncoding(ApacheHttpResponse.java:56)
	at com.google.api.client.http.HttpResponse.(HttpResponse.java:109)
	at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:361)
	at ie.shizzle.media.PlayerActivity.onFillUI(PlayerActivity.java:62)
        ....

Not to worry I simply extended TestHttpResponse myself and implemented the required methods:



package test.ie.shizzle.support;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;

import com.xtremelabs.robolectric.tester.org.apache.http.HttpEntityStub;

public class TestHttpResponse extends
		com.xtremelabs.robolectric.tester.org.apache.http.TestHttpResponse {
    private TestHttpEntity httpEntity = new TestHttpEntity();
    private String responseBody;
    
	public TestHttpResponse(int statusCode, String responseBody) {
		super(statusCode, responseBody);
		this.responseBody = responseBody;
	}
	
    @Override public HttpEntity getEntity() {
        return httpEntity;
    }

    public class TestHttpEntity extends HttpEntityStub {
        @Override public long getContentLength() {
            return responseBody.length();
        }

        @Override public InputStream getContent() throws IOException, IllegalStateException {
            return new ByteArrayInputStream(responseBody.getBytes());
        }

        @Override public void writeTo(OutputStream outputStream) throws IOException {
            outputStream.write(responseBody.getBytes());
        }

        @Override public void consumeContent() throws IOException {
        }
        
        @Override
        public Header getContentType() {
        	return new Header() {
				
				@Override
				public String getValue() {
					// TODO Auto-generated method stub
					return "application/atom+xml";
				}
				
				@Override
				public String getName() {
					// TODO Auto-generated method stub
					return null;
				}
				
				@Override
				public HeaderElement[] getElements() throws ParseException {
					// TODO Auto-generated method stub
					return null;
				}
			};
        }
        @Override
        public Header getContentEncoding() {
        	return new Header() {
				
				@Override
				public String getValue() {
					// TODO Auto-generated method stub
					return "gzip";
				}
				
				@Override
				public String getName() {
					// TODO Auto-generated method stub
					return null;
				}
				
				@Override
				public HeaderElement[] getElements() throws ParseException {
					// TODO Auto-generated method stub
					return null;
				}
			};
        }
    }

}

That didn’t take me long but it does make me worry about this approach. I get the feeling I will end up spending a lot of time implemented mock classes that have only been stubbed out by robolectric. It reminds me a lot of trying to do TDD in Enterprise Java before Spring came along and made unit testing really simple, fast and effective.

Advertisements

About bebblebrox

I am an experienced software developer with over five year’s commercial experience with strong software design and development skills. I am searching for an ambitious start-up company with a shared passion for delivering quality software products.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s