Sunday, November 23, 2008

JUnit in Android

Now that we have the instrumentation framework in our basket and we are able to connect instrumentation objects with a test runner, it is time to put a bit of structure into out tests. The number of test cases grows quicky. If there is no way to organize them or they depend on each other in a subtle way, test case maintenance soon becomes a nightmare. Experience shows that in case of a resource-constrained project, test effort is the first to suffer cutbacks and if the test cases are not maintained properly, the team or the developer may soon feel that the test set is too much hassle compared to the value the tests provide. The result is a fallback to the default, "chaotic" way of development with the predictable drop of quality.

The popular JUnit test framework is integrated into Android. JUnit, if used properly, brings two major benefits to the test implementation.
  • JUnit enforces the hierarchical organization of the test cases
  • The pattern JUnit is based on guarantees the independence of the test cases and minimizes their interference.
Entire books have been written about JUnit and unit testing therefore I will restrict myself to the minimum in explaining the test framework. JUnit's main building block is the TestCase class. Actual test case classes are inherited from TestCase. One TestCase child class contains one or more test methods, these test methods are the real test cases and the TestCase descendants are really already test groups. By convention, the test method names are prefixed with "test" (like testThis(), testThat(), etc.). JUnit can discover the test methods by itself if this convention is respected, otherwise the test method names have to be declared one by one.

The most important impact of using JUnit is the way one test method is executed. For each test method, the TestCase descendant class is instantiated, its setUp() method is invoked, then the test method in question is invoked, then the tearDown() method is invoked then the instance becomes garbage. This is repeated for each test method. This guarantees that test methods are independent of each other, their interference is minimized. Of course, "clever" programmers can work around this pattern and implement test cases that depend on each other but this will bring us back to the mess that was to be eliminated in the first place. JUnit also provides ways to collect test classes into test suites and organize these suites hierarchically.

JUnit has been included into Android and Android-specific extensions were provided in the android.test package. Android test cases classes should inherit from android.test.AndroidTestCase instead of junit.framework.TestCase. The main difference between the two is that AndroidTestCase knows about the Context object and provides method to obtain the current Context. As many Android API methods need the Context object, this makes coding of Android test cases much easier. AndroidTestCase already contains one test method that tests the correct setup of the test case class, the testAndroidTestCaseSetupProperly method. This adds one test method to every AndroidTestCase descendant but this fact rarely disturbs the programmer.

Those familiar with the "big", PC-based JUnit implementations will miss a UI-oriented TestRunner. The android.test.AndroidTestRunner does not have any user interface (not even console-based UI). If you want to get any readable feedback out of it, you have to handle callbacks from the test runner.

You can download the example program from here.

Our example program contains a very simple test runner UI, implemented as an Activity. The UI provides just the main progress indicators, details are written into the log so this implementation is nowhere near to the flexible test runners of the "big" JUnit. The test suite contains two test case classes. One of them (the MathTest) is really trivial. It demonstrates, however, the hidden testAndroidTestCaseSetupProperly() test method that explains the two extra test cases (the test runner will display 5 test cases when we have really only 3). The main lesson from the other test case class (ContactTest) is that individual test cases must be made independent from each other. This test case class inserts and deletes the test contact for each test method execution, cleaning up its garbage after each test execution. This is the effort that must be made to minimize interference. ExampleSuite organizes these two test classes into a suite. As we respected the JUnit test method naming conventions, we can leave the discovery of test methods to JUnit.



At this point, we have everything to write structured, easy to maintain tests for Android, using the Android test instrumentation mechanism to drive the UI. This will be my next step.

0 comments:

Post a Comment

Hot App Todays

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Best Buy Coupons