Testing J2ME Software

Having no test framework is no excuse for not testing.

Any non-trivial J2ME application contains large amounts of code that is completely independent of J2ME-specific APIs. We here suggest an approach for keeping tight code-compile-test cycles, in the spirit of extreme programming, while still continuously maintaining J2ME compilance.

The Problems

Suppose that, for some reason, we are implementing a SAX parser that runs on J2ME. Since virtually all of the code is J2ME-independent (at least superficially), most logic errors can be pin-pointed without time-consuming emulation. However, before checking in and leaving for home, we must ensure that all unit tests succeed when run

preferrably without any intervening redecoration, recompilation or repackaging.

The problems here are, since we have to test something that makes use of streams, the fact that the emulator does not support e.g. JUnit, and the fact that the command line does not support the GCF.

But these problems are easily overcome, as we shall see.

Yet Another Testing Framework

Since we want the very same tests to run both within and without the emulator, we design a test framework around an interface that is both J2ME- and J2SE-compliant:

public interface Test {
    public void setName(String name);

    public String getName();

    public void setProperty(String key, String value);

    public Object run() throws Exception;
}

A simple implementation is the unit test for the framework itself (the source listing of AbstractTest is left to the reader's imagination):

public class TestTest extends AbstractTest {
    public Object run() throws Exception {
	String millis = getProperty("millis");
	if (millis != null) Thread.sleep(Long.parseLong(millis));
	String message = getProperty("message");
	if (message != null) throw new Exception(message);
	return getProperty("result");
    }
}

The unit tests are enumerated and parameterized in a resource file:

<tests>
  <test name="Test" class="se.japp.j2me.testing.TestTest">
    <property key="result" value="I'm into SOFTWARE!"/>
  </test>
  <test name="Fail" class="se.japp.j2me.testing.TestTest">
    <property key="millis" value="2000"/>
    <property key="message" value="Now I am depressed..."/>
  </test>
  .
  .
  .
</tests>

The lightweight framework can be run from the command line:

$ java se.japp.j2me.testing.TestRunner
I'm into SOFTWARE!
Exception in thread "main" java.lang.Exception: Now I am depressed...
	at se.japp.j2me.testing.TestTest.run(TestTest.java:13)
	at se.japp.j2me.testing.TestRunner.main(TestRunner.java:84)

as well as within the emulator:

Yet Another GCF-on-J2SE

Since simply replacing javax.microedition.io.Connector implies adding classes to javax.* packages, we instead define a factory with the familiar implementation hook (and get the irresistible "rsrc:" URL scheme almost for free):

public class ConnectionFactory {
    public static ConnectionFactory newInstance() {
	try {
	    return (ConnectionFactory)Class.forName(System.getProperty("se.japp.j2me.io.ConnectionFactory")).newInstance();
	}
	catch (Exception e) {
	}
	return new ConnectionFactory();
    }

    public Connection open(String name) throws IOException {
	return open(name, READ_WRITE);
    }

    public Connection open(String name, int mode) throws IOException {
	return open(name, mode, false);
    }

    public Connection open(String name, int mode, boolean timeouts) throws IOException {
	if (name.startsWith("rsrc:"))
	    return new ResourceConnection(name.substring(5));

	else
	    return Connector.open(name, mode, timeouts);
    }

    public DataInputStream openDataInputStream(String name) throws IOException {
	return ((InputConnection)open(name, READ)).openDataInputStream();
    }

    public DataOutputStream openDataOutputStream(String name) throws IOException {
	return ((OutputConnection)open(name, WRITE)).openDataOutputStream();
    }

    public InputStream openInputStream(String name) throws IOException {
	return ((InputConnection)open(name, READ)).openInputStream();
    }

    public OutputStream openOutputStream(String name) throws IOException {
	return ((OutputConnection)open(name, WRITE)).openOutputStream();
    }
}


class ResourceConnection implements InputConnection {
    protected String name;

    ResourceConnection(String name) {
	this.name = name;
    }

    public void close() throws IOException {
    }

    public DataInputStream openDataInputStream() throws IOException {
	return new DataInputStream(openInputStream());
    }

    public InputStream openInputStream() throws IOException {
	return getClass().getResourceAsStream(name);
    }
}

The above obviuosly works only on J2ME, but it is a fairly straightforward matter to make use of the hook and implement the Connection interface from CLDC and its friends in terms of J2SE (HttpUrlConnection is left as an exercise for the reader):

public class URLConnectionFactory extends ConnectionFactory {
    public Connection open(String name, int mode, boolean timeouts) throws IOException {
	if (name.startsWith("rsrc:"))
	    return super.open(name, mode, timeouts);

	else
	    return new URLContentConnection(new URL(name).openConnection(), mode, timeouts);
    }
}


class URLContentConnection implements ContentConnection {
    URLConnection urlConnection;
    URLContentConnection(URLConnection urlConnection, int mode, boolean timeouts) {
	this.urlConnection = urlConnection;
    }
    public void close() throws IOException {
    }
    public InputStream openInputStream() throws IOException {
	return urlConnection.getInputStream();
    }
    public DataInputStream openDataInputStream() throws IOException {
	return new DataInputStream(openInputStream());
    }
    public OutputStream openOutputStream() throws IOException {
	return urlConnection.getOutputStream();
    }
    public DataOutputStream openDataOutputStream() throws IOException {
	return new DataOutputStream(openOutputStream());
    }
    public String getEncoding() {
	return urlConnection.getContentEncoding();
    }
    public long getLength() {
	return urlConnection.getContentLength();
    }
    public String getType() {
	return urlConnection.getContentType();
    }
}

Finally, to avoid having to see "unfamiliar" expressions like

InputStream is = ConnectionFactory.newInstance().openInputStream("rsrc:/tests.xml");
we define:

public class Connector {
    static final ConnectionFactory connectionFactory = ConnectionFactory.newInstance();

    public static Connection open(String name) throws IOException {
	return connectionFactory.open(name);
    }

    public static Connection open(String name, int mode) throws IOException {
	return connectionFactory.open(name, mode);
    }

    public static Connection open(String name, int mode, boolean timeouts) throws IOException {
	return connectionFactory.open(name, mode, timeouts);
    }

    public static DataInputStream openDataInputStream(String name) throws IOException {
	return connectionFactory.openDataInputStream(name);
    }

    public static DataOutputStream openDataOutputStream(String name) throws IOException {
	return connectionFactory.openDataOutputStream(name);
    }

    public static InputStream openInputStream(String name) throws IOException {
	return connectionFactory.openInputStream(name);
    }

    public static OutputStream openOutputStream(String name) throws IOException {
	return connectionFactory.openOutputStream(name);
    }
}

With this icing on the cake, we henceforth write the following once and run both within and without the emulator, and eventually on real devices:

import javax.microedition.iose.japp.j2me.io.Connector;

...
InputStream is = Connector.openInputStream("rsrc:/tests.xml");
To be continued...

© Copyright 2004 by Johan Appelgren AB. All rights reserved.