Tuesday, July 07, 2009

Android: Testing on the Android platform - Is Toast leaking ?









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_194s36s83fz), cut and paste link if you have problems accessing it.






A couple of days ago I've found an interesting post by skink on Android Developers group titled **never ever** use Toasts with Activity context. The post speaking about NotifyWithText in ApiDemos, states something like:


"...try to show any Toast, then exit NotifyWithText

demo. run it again - you will see getInstanceCount() increases leaking

Activities. repeat running demo couple of times. counter still

increases."


So the questions are:



  • Is Toast leaking Context objects ?


  • Is it a bug in Toast, in ApiDemos, in the documentation ?


  • Should we use Application Context instead ?




Let's try to find the answers using some Unit Tests as we have been investigating in previous articles in this blog.


ToastActivity


Let's recreate a oversimplified version of the Activity to display just the Toast depending on an extra parameter DISPLAY_TOAST in the Intent starting the Activity.










package com.codtech.android.training.toast;



import android.app.Activity;

import android.app.Application;

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

import android.widget.Toast;



public class ToastActivity extends Activity {

    private static final String TAG = "ToastActivity";

    public static final String USE_ACTIVITY_CONTEXT =

        "com.codtech.android.training.toast.useActivityContext";

    public static final String DISPLAY_TOAST =

        "com.codtech.android.training.toast.displayToast";

    private Context toastContext;

    private boolean useActivityContext;

    private boolean displayToast;

    

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        final Intent intent = getIntent();

        useActivityContext =

            intent.getBooleanExtra(USE_ACTIVITY_CONTEXT, true);

        displayToast =

            intent.getBooleanExtra(DISPLAY_TOAST, true);

        

        if ( useActivityContext ) {

            toastContext = this;

        }

        else if ( displayToast ) {

            toastContext =

                getApplication().getApplicationContext();

        }

    }

      

    

    /* (non-Javadoc)

     * @see android.app.Activity#onPause()

     */

    @Override

    protected void onResume() {

        super.onResume();

        if ( displayToast ) {

            Toast.makeText(toastContext,

                "Sample Toast: " + getInstanceCount(),

                Toast.LENGTH_SHORT).show();

        }

        else {

            Log.d(TAG, "No toast displayed");

        }

        finish();

    }





    /* (non-Javadoc)

     * @see android.app.Activity#onDestroy()

     */

    @Override

    protected void onDestroy() {

        super.onDestroy();

        Log.d(TAG, "Activity destroyed: " + this);

    }



}




ToastActivity Tests



As usual, let's create our test to.



In this very particular case we want it to run several times specified by ToastActivityTests.N, 50 actually but you can change it if you like, to see if displaying a Toast or not change things in some way.



RepeatedTestSuite



This class implements repeated tests.











package com.codtech.android.training.toast.tests;



import junit.framework.Test;

import junit.framework.TestSuite;



public class ReapeatedTestSuite extends TestSuite {

    

    public static Test repeatedSuite(Test test, int count) {

        TestSuite suite = new TestSuite("Repeated");

        

        // there's no RepeatedTest in android's junit

        for (int i=0; i<count; i++) {

            suite.addTest(test);

        }

    

        return suite;

    }

}




RepeatedActivityContextWithToast


This test will run testSingleActivityContextWithToast starting the activity displaying the Toast several times










/**

 *

 */

package com.codtech.android.training.toast.tests;



import junit.framework.Test;





/**

 * @author diego

 *

 */

public class RepeatedActivityContextWithToast extends ReapeatedTestSuite {

    public static Test suite() {

        return repeatedSuite(

            new ToastActivityTests("testSingleActivityContextWithToast"),

            ToastActivityTests.N);

    }

}





RepeatedActivityContextWithoutToast


This test will run testSingleActivityContextWithoutToast starting the activity, without displaying any Toast, several times










/**

 *

 */

package com.codtech.android.training.toast.tests;



import junit.framework.Test;





/**

 * @author diego

 *

 */

public class RepeatedActivityContextWithoutToast extends ReapeatedTestSuite {

    public static Test suite() {

        return repeatedSuite(

            new ToastActivityTests("testSingleActivityContextWithoutToast"),

            ToastActivityTests.N);


    }

}







ToastActivityTests


We decided to fail the test if the Activity instance count reaches N/4.












/**

 *

 */

package com.codtech.android.training.toast.tests;





import java.lang.reflect.Method;



import android.app.Instrumentation;

import android.content.Intent;

import android.os.Bundle;

import android.test.ActivityInstrumentationTestCase2;

import android.test.FlakyTest;

import android.test.suitebuilder.annotation.MediumTest;

import android.util.Log;



import com.codtech.android.training.toast.ToastActivity;



/**

 * @author diego

 *

 */

public class ToastActivityTests

    extends ActivityInstrumentationTestCase2<ToastActivity> {




    private static final String TAG = "ToastActivityTests";

    public static final int N = 50;



    /**

     * @param name

     */

    public ToastActivityTests(String name) {

        super("com.codtech.android.training.toast",

            ToastActivity.class);


        setName(name);

    }





    /* (non-Javadoc)

     * @see junit.framework.TestCase#setUp()

     */

    protected void setUp() throws Exception {

        super.setUp();

    }



    /* (non-Javadoc)

     * @see android.test.InstrumentationTestCase#tearDown()

     */

    protected void tearDown() throws Exception {

        super.tearDown();

    }





    @MediumTest

    public void testSingleActivityContextWithToast() {

        exersiseActivityLifecycle(intentFactory(true, true),

            "testSingleActivityContextWithToast");


    }

    

    @MediumTest

    public void testSingleApplicationContextWithToast() {

        exersiseActivityLifecycle(intentFactory(false, true),

            "testSingleApplicationContextWithToast");


    }

    

    @MediumTest

    public void testSingleActivityContextWithoutToast() {

        exersiseActivityLifecycle(intentFactory(true, false),

            "testSingleActivityContextWithoutToast");


    }

    

    @MediumTest

    public void testSingleApplicationContextWithoutToast() {

        exersiseActivityLifecycle(intentFactory(false, false),

            "testSingleApplicationContextWithoutToast");


    }

   



    /**

     * @param intent

     */

    private void exersiseActivityLifecycle(final Intent intent, final String name) {

        setActivityIntent(intent);

        final ToastActivity activity = getActivity();

        final Instrumentation instrumentation = getInstrumentation();



        // At this point, onCreate() has been called, but nothing else

        // Complete the startup of the activity

        instrumentation.callActivityOnStart(activity);

        instrumentation.callActivityOnResume(activity);

        // At this point you could test for various configuration aspects, or you could

        // use a Mock Context to confirm that your activity has made certain calls to the system

        // and set itself up properly.

        instrumentation.callActivityOnPause(activity);

        // At this point you could confirm that the activity has paused properly, as if it is

        // no longer the topmost activity on screen.

        instrumentation.callActivityOnStop(activity);



        Runtime.getRuntime().gc();

        Runtime.getRuntime().runFinalization();

        Runtime.getRuntime().gc();

      

        long aic = ToastActivity.getInstanceCount();

        assertTrue("instance count reached " + aic, aic < N/4);



        // At this point we are invoking onDestroy explicitly because we are iterating

        // and tearDown() will not be called

        instrumentation.callActivityOnDestroy(activity);

        

        // run N times, requires FlakyTest

        try {

            Method method = this.getClass().getMethod(name, new Class[] {});

            FlakyTest flakyTest = method.getAnnotation(FlakyTest.class);

            if ( flakyTest != null ) {

                assertTrue(count >= flakyTest.tolerance());

            }

        } catch (SecurityException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (NoSuchMethodException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

    

    private static Intent intentFactory(boolean useActivityContext,

            boolean displayToast) {


        final Intent intent = new Intent();

        intent.setAction(Intent.ACTION_MAIN);

        intent.setClassName("com.codtect.android.training.toast",

            "com.codtect.android.training.toast.ToastActivity");


        intent.putExtra(ToastActivity.USE_ACTIVITY_CONTEXT, useActivityContext);

        intent.putExtra(ToastActivity.DISPLAY_TOAST, displayToast);

        return intent;

    }

}







Running the tests



Running RepeatedActivityContextWithoutToast


Running the tests we can verify that everything is fine.













diego@bruce:~$ adb shell am instrument -w -e class com.codtech.android.training.toast.tests.RepeatedActivityContextWithoutToast com.codtech.android.training.toast.tests/android.test.InstrumentationTestRunner



com.codtech.android.training.toast.tests.ToastActivityTests:...................

...............................


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

.........

Time: 42.473



OK (50 tests)






Running RepeatedActivityContextWithToast


Running the test that displays the Toast we find a different result. Activity instance count reaches the maximum allowed and the test fails












diego@bruce:~$ adb shell am instrument -w -e class com.codtech.android.training.toast.tests.RepeatedActivityContextWithToast com.codtech.android.training.toast.tests/android.test.InstrumentationTestRunner



com.codtech.android.training.toast.tests.ToastActivityTests:...........

Failure in testSingleActivityContextWithToast:

junit.framework.AssertionFailedError: instance count reached 12

    at com.codtech.android.training.toast.tests.ToastActivityTests.

       exersiseActivityLifecycle(ToastActivityTests.java:195)


    at com.codtech.android.training.toast.tests.ToastActivityTests.

       testSingleActivityContextWithToast(ToastActivityTests.java:138)




...





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

F.F.F.F.F.F.F.F.F.F

Time: 56.101



FAILURES!!!

Tests run: 50,  Failures: 39,  Errors: 0







Conclusion


We can see that the only difference between both tests is the Toast being displayed, and the results are completely different.

While the demonstration given in the Android Developer's thread seems correct, this seems correct too !

Comments, suggestions and corrections are gladly welcome.

If you are interested in the source code or APK just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.




































Friday, July 03, 2009

Android: Testing on the Android platform - Hamcrest









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_193f2v698g9), cut and paste link if you have problems accessing it.







We used a custom Comparator in the previous post to be able to compare Strings content in our mock object's method invocations.






While this method is valid a more generic approach would be to introduce hamcrest, a library of matcher objects (also known as constraints or predicates) allowing 'match' rules to be defined declaratively, to be used in other frameworks. Hamcrest also provides adaptors for EasyMock 2.



We will be revisiting our previous example introducing hamcrest for our matchers.


Hamcrest matchers



Hamcrest comes with a library of useful matchers. Here are some of the most important ones.

    · Core
          o anything - always matches, useful if you don't care what the object under test is
          o describedAs - decorator to adding custom failure description
          o is - decorator to improve readability - see "Sugar", below
    · Logical
          o allOf - matches if all matchers match, short circuits (like Java &&)
          o anyOf - matches if any matchers match, short circuits (like Java ||)
          o not - matches if the wrapped matcher doesn't match and vice versa
    · Object
          o equalTo - test object equality using Object.equals
          o hasToString - test Object.toString
          o instanceOf, isCompatibleType - test type
          o notNullValue, nullValue - test for null
          o sameInstance - test object identity
    · Beans
          o hasProperty - test JavaBeans properties
    · Collections
          o array - test an array's elements against an array of matchers
          o hasEntry, hasKey, hasValue - test a map contains an entry, key or value
          o hasItem, hasItems - test a collection contains elements
          o hasItemInArray - test an array contains an element
    · Number
          o closeTo - test floating point values are close to a given value
          o greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - test ordering
    · Text
          o equalToIgnoringCase - test string equality ignoring case
          o equalToIgnoringWhiteSpace - test string equality ignoring differences in runs of whitespace
          o containsString, endsWith, startsWith - test string matching



hasToString matcher


Let's create a matcher to replace stringCmp. EasyMock2Adapter is an adapter class provided by hamcrest.










import org.hamcrest.integration.EasyMock2Adapter;

import org.hamcrest.object.HasToString;   





    /**


     * Create an {@link EasyMock2Adapter} using a
     * {@link HasToString.hasToString}


     *

     * @param <T> The original class of the arguments

     * @param o The argument to the comparison

     * @return o

     */

    public static <T> T hasToString(T o) {

        EasyMock2Adapter.adapt(
            HasToString.hasToString(o.toString()));


        return o;

    }




testTextChanged

Now, the watcher mock object checks will include this matcher:










    watcher.beforeTextChanged(hasToString(sar[i-1]),

       eq(0), eq(sar[i-1].length()), eq(sar[i].length()));

    watcher.onTextChanged(hasToString(sar[i]),

       eq(0), eq(sar[i-1].length()), eq(sar[i].length()));

    watcher.afterTextChanged(

       hasToString(Editable.Factory.getInstance()
       .newEditable(sar[i])));




Conclusion


We have introduced hamcrest that makes a great number of matchers and the ability to create new ones and adapt them to be used by EasyMock.




Copyright © 2009 Diego Torres Milano. All rights reserved.


















Thursday, July 02, 2009

Android: Testing on the Android platform - Mock objects











This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_186chdgdwdj), cut and paste link if you have problems accessing it.







In a previous post we created a parallel project to hold our tests. Our project is still empty and has only a TestSuite

Now we will explore how to create the actual tests and in this post we will be introducing mock objects.


If we are Test Driven Development purists we may argue about the use of mock objects and be more inclined to use real ones. Martin Fowler calls these two styles the classical and mockist Test Driven Development dichotomy in his great article Mocks aren't stubs.


Temperature Converter

We will be using our well know Temperature Converter application, the same we have used in many other examples, however we will be introducing some modifications and new tests.


 





EditNumber

Temperature Converter uses two fields where temperatures, as signed decimal numbers, can be entered. Let's extend EditText to have a specialized View with this behavior.


Test Driven Development

We are starting writing our tests for our yet inexistent class EditNumber. We decided to extend EditText and we know that EditTexts can add a listener, actually a TextWatcher, to provide methods that are called whenever EditText's text changes.
And this is precisely where we are introducing a mock TextWatcher to check method invocations while text changes.
EasyMock will help us achieve this. This is not an EsyMock tutorial, we will just be analyzing its use in Android, so if you are not familiar with it I would recommend you to take a look at the documentation available in its web site.
Add easymock-2.5.1.jar to the Tests project properties.

testTextChanged

This test will excersise EditNumber behavior checking the method calls on the TextWatcher mock and verify the results.
We are using an InstrumentationTestCase because we are interested in testing EditNumber in isolation of other components or Activities. Later on we will be introducing Functional Tests where we test the whole Activity.
sai and sar are two String arrays containing the inputs and results expected.
We will be using a special Comparator, stringCmp, because we are interested in comparing the String content for different classes used by the Android like Editable, CharSequence, String, etc.

/**
 * EditNumberTests
 */
package com.codtech.android.training.temperatureconverter.tests.view;

import static com.codtech.android.training.temperatureconverter.tests.TestUtils.stringCmp;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import android.content.Context;
import android.test.InstrumentationTestCase;
import android.text.Editable;
import android.text.TextWatcher;

import com.codtech.android.training.temperatureconverter.view.EditNumber;

/**
 * @author diego
 *
 */
public class EditNumberTests extends InstrumentationTestCase {

    private static final String TAG = "EditNumberTests";
    private EditNumber editNumber;

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

    /* (non-Javadoc)
     * @see junit.framework.TestCase#setUp()
     */
    protected void setUp() throws Exception {
        super.setUp();
        final Context context = getInstrumentation().getTargetContext();
        editNumber = new EditNumber(context);
        editNumber.setFocusable(true);
    }

    /* (non-Javadoc)
     * @see android.test.InstrumentationTestCase#tearDown()
     */
    protected void tearDown() throws Exception {
        super.tearDown();
    }
   
    /**
     * testTextChanged
     */
    public final void testTextChanged() {
        String[] sai = new String[] {null, "", "1", "123", "-123", "0",
            "1.2", "-1.2", "1-2-3", "+1", "1.2.3" };

        String[] sar = new String[] {"",   "", "1", "123", "-123", "0",
            "1.2", "-1.2", "123",   "1",  "1.23"  };

        
        // mock
        final TextWatcher watcher = createMock(TextWatcher.class);
        editNumber.addTextChangedListener(watcher);
        
        for (int i=1; i < sai.length; i++) {
            // record
            watcher.beforeTextChanged(stringCmp(sar[i-1]), eq(0),
                eq(sar[i-1].length()), eq(sar[i].length()));

            watcher.onTextChanged(stringCmp(sar[i]), eq(0),
                eq(sar[i-1].length()), eq(sar[i].length()));
                        watcher.afterTextChanged(stringCmp(
                Editable.Factory.getInstance().newEditable(sar[i])));


            // replay
            replay(watcher);

                        // exersise
            editNumber.setText(sai[i]);

            // test
            final String actual = editNumber.getText().toString();
            assertEquals(sai[i] + " => " + sar[i] + " => " + actual, sar[i], actual);

            // verify
            verify(watcher);
            
            // reset
            reset(watcher);
        }
    }
}

stringCmp

This is the Comparator we are using
    public static final class StringComparator<T> implements Comparator<T> {

        /**
         * Constructor
         */
        public StringComparator() {
            super();
        }
        
        /* (non-Javadoc)
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         *
         * Return the {@link String} comparison of the arguments.
         */
        @Override
        public int compare(T object1, T object2) {
            return object1.toString().compareTo(object2.toString());
        }       
    }
    
    /**
     * Return {@link EasyMock.cmp} using a {@link StringComparator} and
     * {@link LogicalOperator.EQUAL}

     *
     * @param <T> The original class of the arguments
     * @param o The argument to the comparison
     * @return {@link EasyMock.cmp}
     */
    public static <T> T stringCmp(T o) {
        return EasyMock.cmp(o, new StringComparator<T>(), LogicalOperator.EQUAL);
    }


Implementing EditNumber

This is what we want for our EditNumber. This is an extremely simple custom View but same techniques can be used in more complicated cases.


/**
 * EditNumber
 */
package com.codtech.android.training.temperatureconverter.view;

import android.content.Context;
import android.text.InputFilter;
import android.text.method.DigitsKeyListener;
import android.util.AttributeSet;
import android.widget.EditText;

/**
 * @author diego
 *
 */
public class EditNumber extends EditText {


    /**
     * @param context
     */
    public EditNumber(Context context) {
        super(context);
        init(null);
    }

    /**
     * @param context
     * @param attrs
     */
    public EditNumber(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    /**
     * @param context
     * @param attrs
     * @param defStyle
     */
    public EditNumber(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    
    private void init(AttributeSet attrs) {
        final InputFilter[] filters = new InputFilter[] {
            DigitsKeyListener.getInstance(true, true) };

        
        setFilters(filters);
    }
   
}



Running the tests

Run the tests. Surprisingly we obtain an error
java.lang.AssertionError:
Unexpected method call onTextChanged(12.3, 0, 1, 4):

onTextChanged(StringComparator<com.codtech.android.training.temperatureconverter.tests.TestUtils.StringComparator>(1.23) == 0, 0, 1, 4): expected: 1, actual: 0
afterTextChanged(StringComparator<com.codtech.android.training.temperatureconverter.tests.TestUtils.StringComparator>(1.23) == 0): expected: 1, actual: 0

at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:72)
at $Proxy0.onTextChanged(Native Method)
at android.widget.TextView.sendOnTextChanged(TextView.java:5905)
at android.widget.TextView.setText(TextView.java:2634)
at android.widget.TextView.setText(TextView.java:2501)
at android.widget.EditText.setText(EditText.java:71)
at android.widget.TextView.setText(TextView.java:2476)
at com.codtech.android.training.temperatureconverter.tests.view.EditNumberTests.testTextChanged(EditNumberTests.java:151)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:191)
at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:181)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:164)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:151)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:418)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1520)

 When we call editNumber.setText("1.2.3") instead of  the expected "1.23" we obtain "12.3". What really give us the indication of a potential bug is that if we use the UI and type "1.2.3" we obtain the expected value but it differs if we use setText().
Is it a bug in TextView, in DigitsKeyListener, somewhere else ?
We need to find out.


Conclusion



We have seen how sometimes mock objects and particulary EasyMock is able to help us on our mockist flavor Test Driven Development and quickly find bugs that could take much longer to discover using other techniques.


Copyright © 2009 Diego Torres Milano. All rights reserved.



 








Android: Testing on the Android platform - Creating tests











This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_186chdgdwdj), cut and paste link if you have problems accessing it.







In a previous post we analyzed the alternatives we have to run our tests on the Android platform and we suggested that the best way to create our tests is into a separate parallel project. As a convention this project will be named as the original project plus the Tests suffix.



For example if we have the MyAndroidProject project tests will be in MyAndroidProjectTests.

Create the project using this name, the same Build Target as the original project, Application name can be empty, for package name use the original project's package name appending the suffix .tests, and you can opt not to create an Activity as it's not needed in most cases.


As usual we will be using a variation of our AndroidTemperatureConverter project and consequently our tests will be in a parallel project named AndroidTemperatureConverterTests.

AndroidManifest.xml

Some changes must be introduced to the default AndroidManifest.xml to run our tests.

First, under Instrumentation, select the Name of the class implementing Instrumentation, usually android.test.InstrumentationTestRunner, the Target package and optionally a Label which is the one that will be displayed in the Instrumentation list as described in Running tests.

Target package is the package containing the code you are testing not your tests.


If you try to run the tests just now, you'll receive

ERROR: Application does not specify a android.test.InstrumentationTestRunner instrumentation or does not declare uses-library android.test.runner
To solve this, let's add android.test.runner as an uses library Application node.

Creating the TestSuite

We need a TestSuite, a Composite of Tests, to simply run all of our tests. Let's name this class AllTests.
Using TestSuiteBuilder we can automatically include all of the tests under the specified package. Obviously, we still don't have any test yet and the TestSuite will be initally empty.

/**
 * Test suite
 */
package com.example.tests;

import android.test.suitebuilder.TestSuiteBuilder;

import junit.framework.Test;
import junit.framework.TestSuite;


public class AllTests extends TestSuite {

    public static Test suite() {
        return new TestSuiteBuilder(AllTests.class)
        .includeAllPackagesUnderHere()
        .build();
    }

}


Running the tests

Using Run as... -> Android JUnit Test will run our, still empty, list of tests.



Conclusion



Organizing the tests into a seprate project gives you the advantage of isolating them and its dependencies in a different APK. Shortly we will be introducing mock objects for our tests, using libraries like EasyMock, and in such case we will be adding it to the build path of the tests project only keeping the tidiness of the target untouched.



 

Tuesday, June 16, 2009

Android: Testing on the Android platform - Running tests









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


This post is about Running Tests on the Android platform. We have analyzed how to create tests before and we will be updating the subject soon covering android SDK 1.5_r2 but now how to run these tests is our target. In future installments of this series of articles we will be analyzing unit and functional tests and the tools introduced here will be helpful depending on the case.


There are at least three different complementary ways of running tests, each with its pros and cons. Latest Android ADT plugin supports running tests from inside Eclipse with no additional changes or requirements simplifying the matter a lot compared with previous versions.


Test can be run:


  • from inside Eclipse

  • from the android shell, using am

  • from inside the emulator using Development Tools -> Instrumentation


There's also different methods of including your tests, one method can be including tests inside the same project and the other is creating a parallel project comprising only the tests. The latter gives the advantage of being easily removable from the final APK, as they are part of a different project. Thus this is the one we will be using in future examples.




Running tests from Eclipse

Latest Android ADT plugin (0.9.1) supports running tests from Eclipse. Once you have your tests created inside a separate project select the root project folder and Run as... -> Android JUnit Test.

This video shows this option.


In this case, the obvious advantage is how clearly you can analyze tests success or failure.


Running tests from command line

Tests can be run from the command line using the android shell and am instrumentation command. This has the clear disadvantage of having to type a quite complex command line and if you are already inside android shell, its poor command line editing and history capabilities won't help you a lot. On the other hand, this is scriptable, so probably you have to type it once and then use some kind of script to run your tests.



You will need something like this:








# am instrument -w -e class com.codtech.android.training.sample.project.tests.SampleTest\#testSimpleCreate com.codtech.android.training.sample.project.tests/android.test.InstrumentationTestRunner

com.codtech.android.training.sample.project.tests.SampleTest:.
Test results for InstrumentationTestRunner=.
Time: 1.14

OK (1 test)




This video illustrates this method:


Running from Instrumentation

On the emulator, Dev Tools -> Instrumentation provides yet another alternative of running your tests.

This video illustrates this method:


To see the results you need to go to DDMS Logcat view in Eclipse.

Conclusion

Depending on the case each particular tool has its own pros and cons. If you are running or re-running a test that has just failed perhaps running from Eclipse is more adequate, if you are running a big test suite and want in some way to automate it, the command line and some scripts could be better. And finally if you are running some functional tests and want to see them while they perform perhaps Dev Tools -> Instrumentation is better.
What's important to notice is that you can use any one on the same set of test suites depending on the case.

Tuesday, May 26, 2009

Ooops!..., they did it again

Once again, the new android sdk 1.5 r2 for Linux is distributed as a zip file and if you intend to use it in a multi-user development environment and install it as root to protect it from unintended changes you'll find some problems.

But again, here you are a shell script to fix the permission problems, also useful if you are building packages for your platform. Set the SDK_DIR variable accordingly to your installation.


#! /bin/bash

SDK_DIR=${SDK_DIR:-/opt/android-sdk-linux_x86-1.5_r2/}

sudo find $SDK_DIR/ -type d -exec chmod a+rx {} \;
sudo find $SDK_DIR/ ! -perm -044 -exec chmod g+r,o+r {} \;

Hope it helps.

Thursday, April 23, 2009

Android Training at Skills Matter in London

May is a big month as we will be launching my first Android course on the international stage!


I've worked for quite some time now with my partner Ricston to develop this course for developers. Ricston have years of experience developing and delivering courses for open source and in 2008 they invited me to work with them to draw on my extensive Android knowledge and experience to create a series of courses for engineers that want to build applications on this exciting platform.


I will be in London to deliver the first course on May 27 at Skills Matter - for more information you can download a course description and find further information on the course on the Ricston or Skills Matter sites. The launch is being offered at specially reduced rates - 50% off! - for this first course so take advantage of this great offer!


I hope to see you in the classroom in May.

Tuesday, April 14, 2009

Android Top animations

You may have read a previous post about "Android Top", an my experiments about custom widget creations.
Well, here in this sample video you can find a rather funny animation when you long touch on the VU Meter face.



This is shown in the emulator and that's why CPU usage increases so high when the animation takes place.

PS: I need to find a better image for VU Meter's back. If you have one to contribute it's gladly welcome.

Monday, April 13, 2009

Android frame by frame animations

Frame by frame animations are traditional animations in the sense that they are created with a sequence of different images, played in order, like a roll of film. The AnimationDrawable class is the basis for frame animations.

This is a sample video of the Android Earth Animation application:


The animations is presented as a Button background and every time you touch it the animation stops and next time the rotational direction is changed (prograde and retrograde motion).

Usually, these animations are defined in XML resource files in the res/anim folder, using the tag and including the drawables composing th e animation.

Some information can be found at Android developers: Frame Animation, however if you want to automatically start the animation when the Activity is just started you'll find a hard time.
You may think that you can start the animation in onCreate or onResume, but it's not going to work.
The simplest solution is to delay the animation start using a timer in onResume:


/* (non-Javadoc) * @see android.app.Activity#onResume() */ @Override protected void onResume() { super.onResume(); (new Timer(false)).schedule(new AnimationTimer(earthButtonAnimation), 100); }



Where AnimationTimer is defined as


private static class AnimationTimer extends TimerTask { AnimationDrawable animation; public AnimationTimer(AnimationDrawable animation) { this.animation = animation; } @Override public void run() { animation.start(); this.cancel(); } }


As usual you can install or download the application or its source code from http://codtech.com/downloads/android.
Hope this helps and save you some time.

UPDATE:


Latest code, including some of the contributions, can be found at github.
Fell free to send me patches and corrections.

Android Tutorial at Linux Symposium

I will be presenting an Introduction to Android development tutorial at Linux Symposium (Canada) from July 13th to 17th 2009.
This year also marks the first year that the Symposium will be happening outside of Ottawa and Centre Mont-Royal in Montreal was selected as the 2009 venue.
Linux Symposium is celebrating 11 years of bringing togheter Linux developers, industry professionals, and enthusiasts from over 30 countries.

Don't miss this opportunity if you are interested in knowing more about Android development.

Saturday, April 11, 2009

Android Top

I'm experimenting creating some custom widgets and one is the VU meter, and I'm trying to make them behave more or less like real ones. The most interesting characteristic of VU meters is their slow measurement averaging out peaks.
In this simple example (see video) instead of measuring loudness I'm measuring CPU and memory usage.



CPU and memory consumption are obtained from kernel's /proc filesystem and top command.
Buffers and Cached memory can be excluded or included from the count long pressing on the memory VU meter.
While the application was running a background process was launched and thus why the CPU usage increases.

I've found this example interesting and wanted to share it.
I hope you find it interesting too.

Tuesday, March 31, 2009

Android Eye Contact

I'm working on Android Eye Contact, and application that complements the standard Android Contacts application and provides alternative presentations.
The application, in its Lite version, can be freely downloaded from Android Market.
There will be also a Pro version, someday when people from other countries other than USA and UK be able to charge from their applications. In the meantime there's a very good opportunity to explore the possibilities analyzing what should be included and what shouldn't.
The main idea behind Android Eye Contact is to provide a seamless user experience easing access to contacts ideally using only one hand and with the phone closed, if we think about the available models only.
Some users requested features like Contacts Flow, and I'm now experimenting on this front, though I'm not completely convinced that this is a good way to glimpse through hundreds of contacts which usually don't have a picture associated.
Anyway, this is highly based on great pictureflow, a Qt2/Qt3/Qt4/Qtopia4 widget to display images with animated transition effect.

Comments, suggestions and ideas you may want to share are gladly welcome.


Friday, March 13, 2009

android web applications seamlessly integrate native widgets

I'm writing some android sample code to be included in training courses and I found this example so interesting that I decided to publish it here to share it with you. I hope you find it interesting too.

This example is extremely simple, just a few lines of code, but demonstrates how powerful the idea of mixing native widgets and web applications can be.

The application consists of an oversimplified web browser based on WebView and a super imposed SeekBar slider that when is moved changes the background color of the page using javascript. The default page is http://google.com to demonstrate that no special javascript code is needed in the page.




Views hierarchy
This is how we defined the layout.



Basically, the idea is to have a RelativeLayout holding the WebView and the SeekBar over it.

Changing the background color

This is the most interesting part, in the SeekBar change listener we will be invoking the javascript needed to change the page background color using the DOM. Also we will be using a simple conversion from the progress value to HSV colors.
To invoke the javascript we use webView.loadUrl.







// change color depending on seek bar position
colorBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
float p100 = progress / 100.0f;
webView.loadUrl(String.format(
"javascript:{document.body.style.backgroundColor='#%06x'}",
Color.HSVToColor(new float[] {360 * p100, p100, 1 - p100 }) &
OPAQUE));

}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub

}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub

}

});



Seamless applications
One of the main objectives of the android platform is to provide a seamless experience to the user. In this case we have seen how to seamlessly integrate native and web applications.

Installing the application
To install Android Web Page Background Color Bridge into your phone or emulator visit http://codtech.com/downloads/android directly from the device providing you have an SD card to download it and the required permission enable to install application from Unknown Sources in Settings.

Source code
Source code can also be downloaded from http://codtech.com/downloads/android

Monday, March 09, 2009

Android Meenie early preview: more features described

In my previous post we've seen some basic features of Meenie, like adding placemarks to the map while you are on the road, but there's a more common use case when you are planning your trip in advance and want to add some placemarks, let's say the airport, hotel, office, restaurants, etc.

While it's perfectly possible to use the same use case as being on the road, it's much more confortable to add those placemarks and search for restaurants, hotels or whatever you are looking for in the comfort of your desktop computer. In those cases we will be using Google Maps to help us.
Google Maps has a feature called My Maps, where you can define map placemarks, lines and areas.


Then using a conversion utility which is running in Google Appengine to off-load the processing of the map in the android phone, we will be able to download this map and allow Meenie to show the same content. To locate the map we will be using the URL that appears in Link, in Google Maps top right.

This is the result.



Some details about Google Appengine being access from Android and using the Google Maps Link URL in Meenie and other Android applications were ommited for clarity and will be discussed on upcoming posts.



As always, comments are gladly welcome.

Wednesday, March 04, 2009

Android Meenie early preview

I was working on an Android application that scratchs a personal itch. As I'm traveling very frequently and visiting new cities all the time I always need some information readily available.
For example, the airport, station, hotel, office, venue, places to visit, restaurants, etc.
You need these information quickly accessible, while you travel.

This is an early preview of Android Meenie maps front end. I'm working on the server side now, so more information is coming soon.

Monday, March 02, 2009

Android emulator small bug

I wonder why Android SDK for Linux is distributed as a ZIP file, but anyway if you try to install the SDK in a multiuser environment you'll face some problems.
As usual, in a multiuser environment to avoid undesired changes to the software it should be installed as root.
Unpacking the SDK as root and then running as a normal user you receive this error

### WARNING: Cannot write user data file '/home/diego/.android/SDK-1.1/userdata-qemu.img'

if you check file permissions you can find that you can write to ~/.andrid/SDK-1.1 and userdata-qemu.img has been actually written.

So, what's the problem. Using strace to find out, we can see this

open("/home/diego/.android/SDK-1.1/userdata-qemu.img", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0600) = 3
open("/opt/android-sdk-linux_x86-1.1_r1/tools/lib/images//userdata.img", O_RDONLY|O_NOCTTY|O_LARGEFILE) = -1 EACCES (Permission denied)
close(3) = 0
write(2, "### WARNING: Cannot write user d"..., 109### WARNING: Cannot write user data file '/home/diego/.android/SDK-1.1/userdata-qemu.img': Permission denied ) = 109 unlink("/home/diego/.android/SDK-1.1/userdata-qemu.img.lock") = 0
exit_group(3) = ?

qemu is reporting the wrong error, and the root of the problem is openning /opt/android-sdk-linux_x86-1.1_r1/tools/lib/images//userdata.img for reading.


Here is how to fix it

$ sudo chmod a+r /opt/android-sdk-linux_x86-1.1_r1/tools/lib/images//userdata.img
$ sudo chmod a+r /opt/android-sdk-linux_x86-1.1_r1/tools/lib/images//system.img

and everything will run smoothly.

Friday, February 27, 2009

Blacklisting kernels modules from command line

Sometimes the need to blacklist a module from the Linux kernel command line arise, though it's not very common, and it seems there's no a simple solution.

Well, most distributions support/etc/modprobe.d/blacklist kind of files stored in the filesystem, where you can list the modules to blacklist. But, what if this is not possible, mainly because you don't have a filesystem at all to change and reboot like happened to me while working in the new cult 3.1 thin client OS version ?

I'm listing te change here because it's very useful and can be used in other situations as well, like the one described here using Knoppix.

In the init file, while processing command line options, this snippet should be added

blacklist=*)
for m in $(echo ${x#blacklist=} | sed 's/+/ /g')
do
echo -e "# added by cult\nblacklist $m\n" >> \
/etc/modprobe.d/blacklist
done
;;
Here, the cult convention of separating multiple values in kernel command line options by '+' is used, but you can use ',' as well if you feel more comfortable.

Next time your kernel panics and you need to blacklist a module from the kernel command line you know.