Thursday, November 20, 2008

Android workshop at Mobile Dev Camp

I was kindly invited by Bubble Foundry's Peter Robinett to give a workshop on Android at Mobile Dev Camp this 29th November 2008 in Amsterdam.
We will be introducing the Android platform and how to start developing applications keeping an eye on Test Driven Development. 
More information and registration can be found at http://www.mobiledevcamp.nl/?p=41

Looking forward to seeing you.

Sunday, November 09, 2008

Android: Testing on the Android platform - ApiDemos tests











This document can be read in Google Docs (http://docs.google.com/Doc?id=ddwc44gs_49stmqs7hb), cut and paste link.





A previous post,Android: Testing on the Android platform - Unit tests, showed how to add your tests and run them from inside Eclipse. Although these are very useful tests in our quest to Test Driven Development, they cover only a small part of our needs.

Android 1.0 introduced a whole new world of tests cases like ActivityUnitTestCase to run isolated unit tests, ServiceTestCase to tests services and even functional test of activities like ActivityInstrumentatonTestCase.



ApiDemos provides a set of tests but you will not be able to easily run them unless in a non convoluted way as the way they are structured it's not supported by current Android Development Tools (ADT) Eclipse plugin and you would need other tools, but the solution is so simple that I wonder why they are not originally provided in the way we will be changing them.

Google documentation is not helping either.



ApiDemos tests


Tests are located in the tests/src folder and they have their corresponding AndroidManifest.xml.

Inside this folder, tests like this one are found










/*

 * Copyright (C) 2008 Google Inc.

 */



package com.example.android.apis;



import android.test.ActivityInstrumentationTestCase;



/**

 * Make sure that the main launcher activity opens up properly, which will be

 * verified by {@link ActivityInstrumentationTestCase#testActivityTestCaseSetUpProperly}.

 */

public class ApiDemosTest extends ActivityInstrumentationTestCase<ApiDemos> {



    public ApiDemosTest() {

        super("com.example.android.apis", ApiDemos.class);

    }



}







but we will not be able to run them not resorting to ant or maven, unless we make the following set of changes.

ApiDemosTest Android project




Create new project


Let's create a new Android project from within Eclipse, as usual New -> Project -> Android -> Android Project






Activity and Application name are set to Dummy because they are not used as we are seeing shortly.



Copy ApiDemos tests




Copy the tests/src folder from te original ApiDemos project and turn it into a source folder.

Fix the project setup to use original ApiDemos project to resolve references.






Copy AndroidManifest.xml


Again, from the original ApiDemos project copy AndroidManifest.xml overwriting the existing one






Build and run



If necessary, remove ApiDemos.apk if it has been already installed in the emulator.

Build ApiDemos and ApiDemosTests projects and install them.

Now, if you execute Dev Tools in the emulator and go to Instrumentation, you'll find Test for Api Demos. in the list.








Select it and you will obtain the tests results. Unfortunatelly by now, the reslts will be only visible in the logcat window









11-09 22:56:35.132: INFO/instrumentation(153): Test results for InstrumentationTestRunner=......................

11-09 22:56:35.132: INFO/instrumentation(153): Time: 6.739

11-09 22:56:35.132: INFO/instrumentation(153): OK (22 tests)






or alternativelly, from the command line

$ adb shell am instrument -w com.example.android.apis.tests/android.test.InstrumentationTestRunner

com.example.android.apis.ApiDemosApplicationTests:....

com.example.android.apis.ApiDemosTest:.

com.example.android.apis.app.ForwardingTest:...

com.example.android.apis.app.LocalServiceTest:.....

com.example.android.apis.os.MorseCodeConverterTest:.

com.example.android.apis.view.Focus2ActivityTest:....

com.example.android.apis.view.Focus2AndroidTest:....

Test results for InstrumentationTestRunner=......................

Time: 7.493



OK (22 tests)


Conclusion


So simple that I can't understand why they are not provided in this way by the original Android ApiDemos project.

Next time we will try to present test results in a more concise way inside the emulator as Electro did (video1, video2, video3).









Thursday, November 06, 2008

Android: Testing on the Android platform - Unit tests

This is the first installment of a series of articles about testing on the Android platform. Things have changed with the release of the version 1.0 of the SDK and will change in the future for sure, as there are still some lose ends.
Electron and Positron frameworks are not valid anymore in this new SDK, so let's back to base and start analyzing the possibilities featured in the Android platform as it is.


As with previous version of articles covering testing for different platforms we will be using our simple Temperature Converter (also analyzed in Test Driven Development and GUI Testing) example.

For a Test Driven Development approach you should
  1. create your Android project
  2. add the tests source folder (New -> Source Folder -> Folder name: tests/src)
  3. in the newly created folder add a Java Package. Its name should be the same as the project package with .tests appended
  4. in the 'tests' package create a new JUnit Test Case using JUnit 3 (New -> JUnit Test Case). Name it TemperatureConverterTest, leave class under test blank
  5. Copy this code into TemperatureConverterTest.java 
package com.codtech.android.temperatureconverter.tests;

      import junit.framework.TestCase;

          import java.util.HashMap;
            import java.util.Map;

                import com.codtech.android.temperatureconverter.TemperatureConverter;

                    /**
                       * @author diego
                         *
                           */
                            public class TemperatureConverterTest extends TestCase {

                                private static final Map conversionTable = new HashMap();

                                    static {
                                      // initialize (c, f) pairs
                                        conversionTable.put(0, 32);
                                          conversionTable.put(100, 212);
                                            conversionTable.put(-1, 30);
                                              conversionTable.put(-100, -148); conversionTable.put(32, 90);
                                                conversionTable.put(-40, -40);
                                                  conversionTable.put(-273, -459);
                                                    }

                                                        /**
                                                          * @param name
                                                            */
                                                              public TemperatureConverterTest(String name) { super(name);
                                                                }


                                                                      /**
                                                                        * Test method for {@link com.codtech.android.temperatureconverter.TemperatureConverter#celsiusToFahrenheit(int)}.
                                                                          */
                                                                            public void testCelsiusToFahrenheit() {
                                                                              for (int c: conversionTable.keySet()) {
                                                                                int f = conversionTable.get(c);
                                                                                  String msg = "" + c + "C -> " + f + "F"; int r = TemperatureConverter.celsiusToFahrenheit(c);
                                                                                    assertEquals(msg, f, r);
                                                                                      }
                                                                                        }
                                                                                          }

                                                                                          1. Accept Eclipse hints to create TemperatureConverter class and celsiusToFahrenheit method
                                                                                          2. Run -> Run As -> Run configurations and create a new JUnit configuration and select the previously created Test class
                                                                                          3. Run the tests and instead of the expected test results you'll get
                                                                                            # An unexpected error has been detected by Java Runtime Environment:
                                                                                              #
                                                                                                #  Internal Error (classFileParser.cpp:2924), pid=5364, tid=6644
                                                                                                  #  Error: ShouldNotReachHere
                                                                                                  1. That's because we are using Android's JUnit stub implementation. Go to Run -> Run As -> Run configurations again and in the recently created JUnit configuration Classpath's Bootstrap Entries remove Android Library
                                                                                                  2. Then Add Library, using Advanced... button, and add JRE System Library and JUnit 3
                                                                                                  3. Apply and Run
                                                                                                  4. And now our tests run, failing as expected. We haven't created the conversion method anyway





                                                                                                  So far so good, our tests are running. But as you may have discovered already, mainly when we changed the libraries, those tests are not Android platform tests so they are only useful as part of our Test Driven Development strategy and we must have always in mind that real Android implementation of some functionality may vary.

                                                                                                  1. Let's finish our TemperatureConverter class
                                                                                                  2. package com.codtech.android.temperatureconverter;

                                                                                                    /**
                                                                                                       * @author diego
                                                                                                         *
                                                                                                           */
                                                                                                            package com.codtech.android.temperatureconverter;
                                                                                                              public class TemperatureConverter {
                                                                                                                public static final int ABSOLUTE_ZERO_C = -273;

                                                                                                                    public static int celsiusToFahrenheit(int c) {
                                                                                                                      if (c < ABSOLUTE_ZERO_C ) {
                                                                                                                      throw new RuntimeException("Invalid temperature: " + c + " below absolute zero");
                                                                                                                      }
                                                                                                                      return (int)Math.round(c * 1.8 + 32);
                                                                                                                      }
                                                                                                                      }

                                                                                                                      Run the test again and it will success. A Test Suite can allso be added, however in this case as we have only one test it's not necessary.

                                                                                                                      Stay tuned. Next article will explore Android Instrumentation to run tests on the real platform.