Showing posts with label eclipse. Show all posts
Showing posts with label eclipse. Show all posts

Tuesday, December 22, 2015

AndroidViewClient/culebra vs. MonkeyRunner

More than 2 years ago I took a crucial decision in AndroidViewClient/culebra development plan and that was to free it from `monkeyrunner`, Jython and Chimpchat.

AndroidViewClient/culebra was liberated and starting with version 4.0.0 it does not require any other runtime environment than python 2.x (read announcement). It can be installed and upgraded using the corresponding platform tools like easy_install or pip and can be easily integrated into IDEs like Eclipse PyDev or Pycharm. It also improves speed, solves chimpchat bugs, and even provides a GUI whre you can automatically create tests or scripts without writing a single line of code.

Nonetheless, from time to time, I receive some questions or reports about problems with scripts created with `culebra` that are attempted to run with `monkeyrunner` or some other combinations. I take the blame for it. I failed at communicating that AndroidViewClient/culebra is a complete replacement and should not be used together.

In order to improve the situation I gave a very detailed, easy to follow, step-by-step answer to
Error of Script with MonkeyRunner and AndroidViewClient (Touch) on Stackoverflow, showing how you can create a test case that automatically starts.and Activity (Duolingo) , checks if some Views are on the screen, touches them and finally take the screenshot. All from the GUI.



I hope you find this explanation useful.

Tuesday, September 11, 2012

monkeyrunner: importing from PYTHONPATH

In previous post we analyzed what is needed to develop, run and debug monkeyrunner scripts using Eclipse and PyDev.


#! /usr/bin/env monkeyrunner
'''
Created on Sep 10, 2012

@author: diego
'''

import re
import sys
import os
import java

# This must be imported before MonkeyRunner and MonkeyDevice,
# otherwise the import fails.
# PyDev sets PYTHONPATH, use it
try:
    for p in os.environ['PYTHONPATH'].split(':'):
       if not p in sys.path:
          sys.path.append(p)
except:
    pass

try:
    sys.path.append(os.path.join(os.environ['ANDROID_VIEW_CLIENT_HOME'], 'src'))
except:
    pass

from com.dtmilano.android.viewclient import ViewClient, View
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

# usage: script [serialno]
serialno = sys.argv[1] if len(sys.argv) > 1 else 'emulator-5554'
device = MonkeyRunner.waitForConnection(30, serialno)
try:
    device.wake()
except java.lang.NullPointerException, e:
    print "ERROR: Couldn't connect to %s: %s" % (serialno, e)

These are the lines you should add to every monkeyrunner script. Here you are a brief explanation of the snippet.

  1. The shebang line to invoke monkeyrunner  interpreter if you are using Linux or Mac OS X. Unfortunately this is not available on Windows. Eclipse does not use this line but is needed if you want to simplify the way you are running the scripts from the command line.
  2. Some standard imports
  3. PyDev uses PYTHONPATH while monkeyrunner ignores it. This snippet adds the components present in PYTHONPATH to sys.path and makes them visible to monkeyrunner.
  4. Following, we need to locate AndroidViewClient which you should have added to the environment. This can be also added in Eclipse in Run Configurations -> Environment.
    ANDROID_VIEW_CLIENT_HOME should point to your AndroidViewClient installation to the parent folder of src. That is, if you have downloaded AndroidViewClient in /opt/AndroidViewClient and kept the same structure as the distribution, you should set ANDROID_VIEW_CLIENT_HOME=/opt/AndroidViewClient/AndroidViewClient
  5. The imports, which will now succeed because sys.path contains the right components
  6. Gets the device's serial number from the command line or default to emulator-5554.
  7. Connect to the device
  8. Check if the connection was successful. Because MonkeyRunner.waitForConnection() returns a MonkeyDevice even when the connection fails we need to go to this extra step to verify it.



Tuesday, April 10, 2012

android: testing library projects

The latest Android SDK Tools (Revision 17 or greater) features several fixes related with library projects and the way the R class is now generated, and the way custom attributes for custom views are handled. This opens a greater number of possibilities using library projects and sooner or later you will be facing the need of testing the library.


Following there is a quick reference of how to do it for a sample library project called AndroidLibrary and its corresponding test project AndroidLibraryTest.

Create library project

Set properties to indicate it is a Library

Create test project and set the properties to reference the library

Set the Target package to this same test project

Run the tests

Friday, March 16, 2012

Eclipse: working monkeyrunner configuration

This post is intended to help you if you have problems running monkeyrunner from Eclipse.
There are tons of messages floating around describing a variety of problems. It seems that the most problematic platform in this respect is Microsoft Windows, and Linux or Mac OSX are both much less tricky.


Using Android monkeyrunner from Eclipse is one of the all-time most popular post in this blog. Clearly, this indicates that the setup is not as straightforward as it should be, so I decided to post a detailed configuration that has been tested and is the one I mostly use to develop tools like AndroidViewClient, which has been described in latests posts like monkeyrunner: interacting with the Views.


After this brief introduction we are ready to start, firstly my Eclipse Helios configuration:

  •   Android DDMS 16.0.1.v201112150204-238534
  •   Android Development Tools 16.0.1.v201112150204-238534
  •   Android Hierarchy Viewer 16.0.1.v201112150204-238534
  •   Android Traceview 16.0.1.v201112150204-238534
  •   AspectJ Development Tools 2.1.3.e36x-20110622-1300
  •   Cross References tool (XRef) 2.1.3.e36x-20110622-1300
  •   EclEmma Java Code Coverage 2.0.1.201112281951
  •   Eclipse EGit 1.2.0.201112221803-r
  •   Eclipse IDE for Java Developers 1.3.2.20110301-1807
  •   Eclipse JGit 1.2.0.201112221803-r
  •   Eclipse Weaving Service Feature 2.1.3.e36x-20110622-1300
  •   Equinox Weaving SDK 1.0.0.v20100421-79--EVVFNFFsFc
  •   m2e - Maven Integration for Eclipse 1.0.100.20110804-1717
  •   m2e - slf4j over logback logging (Optional) 1.0.100.20110804-1717
  •   PyDev for Eclipse 2.2.0.2011062419
  •   Pydev Mylyn Integration 0.3.0


Following this configuration we will be using one of the AndroidViewClient's example: browser-open-url.py. This is showing its run configuration.

main

arguments

interpreter

refresh

environment

common





Tuesday, November 15, 2011

Obtaining code coverage of a running Android application

How can we obtain the code coverage of a running application, not just its tests ?
I have been asked this question many times. Recently, Jonas posted a similar question as comment to Eclipse, Android and EMMA code coverage. So we will elaborate the solution to this problem.
But firstly, let's do a brief introduction of the concepts.

EMMA: a free Java code coverage tool
EMMA is an open-source toolkit for measuring and reporting Java code coverage. EMMA distinguishes itself from other tools by going after a unique feature combination: support for large-scale enterprise software development while keeping individual developer's work fast and iterative.

Android includes EMMA v2.0, build 5312, which includes some minor changes introduced by Android to adapt it for the platform specifics.

Android Instrumentation
The instrumentation framework is the foundation of the testing framework. Instrumentation controls the application under test and permits the injection of mock components required by the application to run.
Usually, an InstrumentationTestRunner, a special class the extends Instrumentation, is used to run various types of TestCases, against an android application.
Typically, this Instrumentation is declared in the test project's AndroidManifest.xml and then run from Eclipse or from the command line using am instrument.
Also, to generate EMMA code coverage -e coverage true option is added to the command line.
Basically, we have all the components but in different places because we want to obtain the code coverage from the running application not from its tests.

EmmaInstrumentation
The first thing we need to do is to create a new Instrumentation that starts the Activity Under Test using EMMA instrumentation and when this Activity is finished the coverage data is saved to a file.
To be notified of this Activity finish we need a listener that we can set extending the AUT because one of our objectives is to keep it unchanged.

To illustrate this technique we will be using the Temperature Converter application that we have used many times in other posts. The source code is as usual available through github.


package com.example.instrumentation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.example.i2at.tc.TemperatureConverterActivity;
//import com.vladium.emma.rt.RT;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;

public class EmmaInstrumentation extends Instrumentation implements FinishListener {

    private static final String TAG = "EmmaInstrumentation";

    private static final boolean LOGD = true;

    private static final String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";

    private final Bundle mResults = new Bundle();

    private Intent mIntent;

    private boolean mCoverage = true;

    private String mCoverageFilePath;

    /**
     * Extends the AUT to provide the necessary behavior to invoke the
     * {@link FinishListener} that may have been provided using
     * {@link #setFinishListener(FinishListener)}.
     * 
     * It's important to note that the original Activity has not been modified.
     * Also, the Activity must be declared in the
     * <code>AndroidManifest.xml</code> because it is started by an intent in
     * {@link EmmaInstrumentation#onStart()}. This turns more difficult to use
     * other methods like using template classes. This latter method could be
     * viable, but all Activity methods should be re-written to invoke the
     * template parameter class corresponding methods.
     * 
     * @author diego
     * 
     */
    public static class InstrumentedActivity extends
    TemperatureConverterActivity {
        private FinishListener mListener;

        public void setFinishListener(FinishListener listener) {
            mListener = listener;
        }

        @Override
        public void finish() {
            if (LOGD)
                Log.d(TAG + ".InstrumentedActivity", "finish()");
            super.finish();
            if (mListener != null) {
                mListener.onActivityFinished();
            }
        }

    }

    /**
     * Constructor
     */
    public EmmaInstrumentation() {

    }

    @Override
    public void onCreate(Bundle arguments) {
        if (LOGD)
            Log.d(TAG, "onCreate(" + arguments + ")");
        super.onCreate(arguments);

        if (arguments != null) {
            mCoverage = getBooleanArgument(arguments, "coverage");
            mCoverageFilePath = arguments.getString("coverageFile");
        }

        mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);
        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        start();
    }

    @Override
    public void onStart() {
        if (LOGD)
            Log.d(TAG, "onStart()");
        super.onStart();

        Looper.prepare();
        InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);
        activity.setFinishListener(this);
    }

    private boolean getBooleanArgument(Bundle arguments, String tag) {
        String tagString = arguments.getString(tag);
        return tagString != null && Boolean.parseBoolean(tagString);
    }

    private void generateCoverageReport() {
        if (LOGD)
            Log.d(TAG, "generateCoverageReport()");

        java.io.File coverageFile = new java.io.File(getCoverageFilePath());

        // We may use this if we want to avoid refecltion and we include
        // emma.jar
        // RT.dumpCoverageData(coverageFile, false, false);

        // Use reflection to call emma dump coverage method, to avoid
        // always statically compiling against emma jar
        try {
            Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
            Method dumpCoverageMethod = emmaRTClass.getMethod(
                    "dumpCoverageData", coverageFile.getClass(), boolean.class,
                    boolean.class);
            dumpCoverageMethod.invoke(null, coverageFile, false, false);
        } catch (ClassNotFoundException e) {
            reportEmmaError("Is emma jar on classpath?", e);
        } catch (SecurityException e) {
            reportEmmaError(e);
        } catch (NoSuchMethodException e) {
            reportEmmaError(e);
        } catch (IllegalArgumentException e) {
            reportEmmaError(e);
        } catch (IllegalAccessException e) {
            reportEmmaError(e);
        } catch (InvocationTargetException e) {
            reportEmmaError(e);
        }
    }

    private String getCoverageFilePath() {
        if (mCoverageFilePath == null) {
            return DEFAULT_COVERAGE_FILE_PATH;
        } else {
            return mCoverageFilePath;
        }
    }

    private void reportEmmaError(Exception e) {
        reportEmmaError("", e);
    }

    private void reportEmmaError(String hint, Exception e) {
        String msg = "Failed to generate emma coverage. " + hint;
        Log.e(TAG, msg, e);
        mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: "
                + msg);
    }

    /* (non-Javadoc)
     * @see com.example.instrumentation.FinishListener#onActivityFinished()
     */
    @Override
    public void onActivityFinished() {
        if (LOGD)
            Log.d(TAG, "onActivityFinished()");
        if (mCoverage) {
            generateCoverageReport();
        }
        finish(Activity.RESULT_OK, mResults);
    }

}

We are also implementing the FinishListener interface, which is defined as


package com.example.instrumentation;

/**
 * Listen for an Activity to finish and invokes {@link #onActivityFinished()} when this happens.
 * 
 * @author diego
 *
 */
public interface FinishListener {

        /**
         * Invoked when the Activity finishes.
         */
        void onActivityFinished();

}

Running the instrumented application
Once we have the EmmaInstrumentation class in place we need a few more adjustments to be able to get the coverage report of the running application.
Firstly, we need to add the new Activity to the manifest. Secondly, we should allow our application to write to the sdcard if this is where we decided to generate the coverage report. To do it you should grant the android.permission.WRITE_EXTERNAL_STORAGE permission.
Then, it's time to build and install the instrumented apk:

$ ant clean
$ ant instrument
$ ant installi

Everything is ready to start the instrumented application

$ adb shell am instrument -e coverage true \
     -w com.example.i2at.tc/\
        com.example.instrumentation.EmmaInstrumentation

If everything went well, the Temperature Converter application will be running and we can use it for a while


when we exit by pressing the BACK button we can see that the coverage data was written to the file and reflected in the logcat

I/System.out(2453): EMMA: runtime coverage data written to [/mnt/sdcard/coverage.ec] {in 975 ms}

this file can then be moved to the host computer using adb pull.

Hope this helps you obtaining the code coverage for your application to help you understand its usage patterns. As always, comments and questions are always welcome.

Thursday, November 10, 2011

Android: Using monkey from Java


The latest version of the Android SDK and tools include chimpchat, a library that facilitates the use of monkey from Java. This is equivalent to monkeyrunner, which is the bridge between monkey and the Python scripting language.
While Python is an incredibly powerful and expressive scripting language and will permit you creating tests with just a few statements, there are some occasions when you don't want to introduce a new language to the project leaving your Java confort zone or you prefer to leverage the use of previously created libraries instead of writing new ones.
In such cases, you can now have the same access to monkey running on the device with the help of chimpchat, as we are going to demonstrate.


Creating a Java project
Our first step will be to create a new Java project and we will add the required libraries to the Java Build Path as External Jars.
We are naming the project JavaMonkey, for obvious reasons.




We are adding these libraries from Android SDK, which are used directly or indirectly by our project, to the Java Build Path:

  • chimpchat.jar
  • ddmlib.jar
  • guavalib.jar
  • sdklib.jar


JavaMonkey.java
Our intention is to create a simple class, serving the purpose of a simple example to get as started. We will be simply:

  1. Creating a JavaMonkey object
  2. initializing it, this implies creating the connection with any emulator or device found or throwing an exception is not connection was made before the timeout expires
  3. listing all the properties in the device or emulator
  4. shutting down the connection

Following, is the JavaMonkey class: 



/**
 * Copyright (C) 2011  Diego Torres Milano
 */
package com.example.javamonkey;

import java.util.TreeMap;

import com.android.chimpchat.ChimpChat;
import com.android.chimpchat.core.IChimpDevice;

/**
 * @author diego
 *
 */
public class JavaMonkey {

        private static final String ADB = "/Users/diego/opt/android-sdk/platform-tools/adb";
        private static final long TIMEOUT = 5000;
        private ChimpChat mChimpchat;
        private IChimpDevice mDevice;

        /**
         * Constructor
         */
        public JavaMonkey() {
                super();
        TreeMap<String, String> options = new TreeMap<String, String>();
        options.put("backend", "adb");
        options.put("adbLocation", ADB);
        mChimpchat = ChimpChat.getInstance(options);
        }

        /**
         * Initializes the JavaMonkey.
         */
        private void init() {
                mDevice = mChimpchat.waitForConnection(TIMEOUT, ".*");
                if ( mDevice == null ) {
                        throw new RuntimeException("Couldn't connect.");
                }
                mDevice.wake();
        }

        /**
         * List all properties.
         */
        private void listProperties() {
                if ( mDevice == null ) {
                        throw new IllegalStateException("init() must be called first.");
                }
                for (String prop: mDevice.getPropertyList()) {
                        System.out.println(prop + ": " + mDevice.getProperty(prop));
                }
        }

        /**
         * Terminates this JavaMonkey.
         */
        private void shutdown() {
                mChimpchat.shutdown();
                mDevice = null;
        }

        /**
         * @param args
         */
        public static void main(String[] args) {
                final JavaMonkey javaMonkey = new JavaMonkey();
                javaMonkey.init();
                javaMonkey.listProperties();
                javaMonkey.shutdown();
        }

}


Configuration
One of the important things you have to adapt to your environment is the location of the adb command. Otherwise if you don't set it you will receive:

E/adb: Failed to get the adb version: Cannot run program "adb": error=2, No such file or directory


Hope this helps you get started with chimpchat. As always, comments and questions are always welcome.

Thursday, September 08, 2011

Android Testing: Running tests from code


I was answering some questions at StackOverflow today and one caught my attention. It was asking, well, not directly but I understood this was the intention, how to run the tests not from another computer using adb or Eclipse but from an android application itself. Then, here we are presenting a solution to run instrumentation from code.



private void runTests() {
   final String packageName = getPackageName();
   final List<InstrumentationInfo> list = 
         getPackageManager().queryInstrumentation(packageName, 0);
   if ( list.isEmpty() ) {
      Toast.makeText(this, "Cannot find instrumentation for " + packageName,
         Toast.LENGTH_SHORT).show();
      return;
   }
   final InstrumentationInfo instrumentationInfo = list.get(0);
   final ComponentName componentName = 
         new ComponentName(instrumentationInfo.packageName,
         instrumentationInfo.name);
   if ( !startInstrumentation(componentName, null, null) ) {
      Toast.makeText(this, "Cannot run instrumentation for " + packageName,
         Toast.LENGTH_SHORT).show();
   }
}


You need a valid context to call startInstrumentation() so this is probably added to an Activity.

I can imagine only a valid use case for this, which is running the tests when you don't have the device connected to a computer.
Do you have another use case ?
Speak out.

Hope this helps.

Friday, August 26, 2011

Android Application Testing Guide: Q&A


Q:I followed your example. I set up the test project in a similar way.
But try to write a TestCase for the utility class from the original
project.
Eclipse says "Class under test does not exist in the current project."

It's kind of reasonable to me, since that class is indeed in another
project.
I only have experienced using JUnit to test normal Java Project where
the test directory is inside the project.

Also, I checked out the two example projects and found there is a
build.properties and build.xml. Is that the reason that you can import
the original class:
"import com.example.i2at.tc.TemperatureConverter;"?

Thanks

Best wishes,
Ryan
Posted by Ryan Huang.

A:If you have imported both projects (main & test) into Eclipse you should have no problems because the versions available at github have the required properties set.
However, if for some reason they were not set properly, this is what you should verify in your test project's Java Build Path -> Libraries




The other files you mentioned are used when you build with ant.

Friday, July 08, 2011

Eclipse, Android and EMMA code coverage

On of the examples I'm including in the tutorial I will be presenting at LinuxCon North America 2011 (link to presentation) is a step by step use of EMMA code coverage from Eclipse to help you navigate through the Android application source code while applying Test Driven Development techniques.


This screenshot, taken from the tutorial examples, shows:

  1. The code coverage results directly summarized and highlighted in the Activity source code inside Eclipse editor
  2. The coverage view lists coverage summaries for the Android project, allowing drill-down to method level
If you are seriously developing Android applications you shouldn't miss this tutorial.
Hope to see you there.

Friday, June 03, 2011

Android Application Testing Guide

The wait is almost over and after a year of hard work the book is finished and is expected to be published by PACKT this month (June 2011).

You can Pre-order now !

Approach
Adroid Application Testing Guide is a highly detailed book which gives step-by-step examples for a great variety of real-world cases, providing professional guidelines and recommendations that will be extremely valuable for optimizing your development time and resources. In the chapters you will find an introduction to specific testing techniques, and tools for specific situations.

Overview of Android Application Testing Guide
  • The first and only book that focuses on testing Android applications
  • Step-by-step approach clearly explaining the most efficient testing methodologies
  • Real world examples with practical test cases that you can reuse
  • eBook available as PDF and ePub downloads and also on PacktLib
More detailed information can be obtained from its web page at PACKT.

    Friday, January 14, 2011

    Alternative logcat viewer

    If you are developing Android with Eclipse and have several devices and emulators connected you may have been annoyed by the constant disconnection that the DDMS view suffers, in such cases you could try to reconnect to the desired device by clicking on it, but this is not always successful.

    I've found as a workaround that sometimes is easier to reconnect if you reach another device or emulator first and then the desired one, so as a rule of thumb is probably better to always have more than one.

    Anyway, here you are a very simple trick that might turn you life a little easier just using a text logcat from adb in a gnome-terminal that will remain open no matter what have happened to the connection.

    First, you need to create a gnome-terminal profile, logcat in this case, an set the following values as dispalyed:


    The important things here are:

    • Run a custom command instead of my shell
    • Custom command: sh -c '/opt/android-sdk/platform-tools/adb -e logcat | coloredlogcat.py'
    • When command exits: Hold the terminal open
    As a bonus, we are including coloredlogcat.py, a filter that will colorize our logcat output.
    What's nice about this simple setup is that even if you are disconnected because you closed the emulator or unplugged the device, just clicking on the Relaunch button will reconnect you again.


    Hope you enjoy it and improve your development not having to insistently click on DDMS'  device list.

    Friday, December 31, 2010

    Problems updating Android to 2.3 in Ubuntu

    If you were using Ubuntu 9.10 (karmic) because you wanted to avoid some issues with Eclipse and Android ADT plugin that happened on newer versions on Ubuntu, well now is time to upgrade to the latest Ubuntu 10.10 (maverick) if you want to update to the latest Android 2.3 (gingerbread).

    You may have discovered this after the update finished using Android SDK and AVD Manager tool and you sadly realize that your Android development environment was turned useless without a single word of warning. Latest Android SDK version depends on GLIBC_2.11 but it's no available for previous versions of Ubuntu, so unless you want to take the risk of using a different repository trying to find an update with unknown consequences to other components, upgrading is your only possible path.
    If this is not a serious bug we should reconsider the definition of the word.

    The origin of the problem is that Android SDK distribution is not package based and thus no dependencies are considered before updating the software. This is clearly a nonsense. Google has taken a completely different approach with Chrome which is cleverly distributed from a package repository as we can see in this screenshot of Ubuntu Software Center.


    Android SDK must be distributed as packages as Chrome does, otherwise there is no way to prevent this problems happening again and again in the future. We are only scaring people away of Android development with this poor policies. It's true that the requirements page was updated and GLIBC 2.11 is listed now as a requirement, but when you discover it, it's too late. You expect some verifications done from software doing an update that cannot easily be reverted.

    This is perhaps the most important bug, but there are others after upgrading to Ubuntu 10.10 (maverick) and Android SDK 2.3 (gingerbread) some other things are still broken, for example hierarchyviewer does not run out of the box and you have to manually copy some libraries,
    Android SDK and AVD Manager updates some packages again an again ignoring they are already installed and some deficiencies of the Android ADT Layout editor, to name a few.

    I complained about many things about Android in this blog and it seems that in one way or another they were fixed in following releases, so I hopefully think this would be the case.

    Wednesday, February 24, 2010

    Android: Generate javadoc for your project

    So you want to follow the best practices and generate the javadoc for your android project from Eclipse and as soon as you start you hit the first wall, messages like "package android.app does not exist" completely fill your output console.

    What's wrong ?
    And most important, how can you generate complete javadoc with links to android documentation ?




    Find out how reading the complete article in Google Docs: http://docs.google.com/View?id=ddwc44gs_240hkc84xfd

    Sunday, January 17, 2010

    Android Continuos Integration: Build with maven

    Android Continuous Integration: Build with maven









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

    If you have problems seeing the images, like the icon in this box don't blame me, it seems to be an issue when you publish to Blogspot from Google Docs.



















    introduction




    "Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible."










    We have written some tests for our project and now we would like to take continuous integration into account, mainly if we are working in an enterprise development environment and our team has several developers.



    We all are very much accustom to use Eclipse and Android ADT as our main developer environment, so it's logical that even though we are increasing our team size we don't want to left behind that experience and start using a different tool from scratch. At the same time, it's pretty clear that using maven as a build tool for the project we obtain a great level of simplification, mainly if we use hudson, cruise control or similar continuous integration tools.



    That's why the approach presented here is to let both models co-exist.










      project structure




      We are also considering here that a VCS is used and a parent project containing both the main and test project is under this VCS control.



      The structure of our project P1 will be
















      the P1 project including AP1 application's main project and its tests in AP1Test.

      To create these projects you should follow this sequence:






      1. Create a Project (File -> New... -> Project), P1 in this case
      2. Add a pom.xml file (File -> New... -> Other -> Maven -> Maven POM file). We will review its content later
      3. Create an Android Project (New... -> Android Project), AP1, using P1 folder instead of the default location, but DON'T create the test project yet or it will fail (ADT 0.9.5)
      4. Add a pom.xml file to AP1 (File -> New... -> Other -> Maven -> Maven POM file). We will review its content later
      5. Selecting AP1 project, create the corresponding test project AP1Test (Android Tools -> New Test Project) using again P1 as the location
      6. Add a pom.xml file to AP1 (File -> New... -> Other -> Maven -> Maven POM file). We will review its content later
      7. You can now import the whole project structure to your VCS





      pom files


      You need to define some things inside your pom files in order to get the Android projects correctly built.

      P1's pom.xml


      We are using maven-android-plugin here, visit its site for further details.







       1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">


       2   <modelVersion>4.0.0</modelVersion>


       3   <groupId>P1</groupId>


       4   <artifactId>P1</artifactId>


       5   <packaging>pom</packaging>


       6   <version>0.0.1-SNAPSHOT</version>


       7  


       8    <build>


       9         <sourceDirectory>src</sourceDirectory>


      10         <plugins>


      11             <plugin>


      12                 <groupId>org.apache.maven.plugins</groupId>


      13                 <artifactId>maven-compiler-plugin</artifactId>


      14                 <version>2.1</version>


      15                 <configuration>


      16                     <source>1.6</source>


      17                     <target>1.6</target>


      18                 </configuration>


      19             </plugin>


      20             <!--


      21                maven-android-plugin doesn't delete files from bin,


      22                only from target, we are deleting them here


      23             -->


      24             <plugin>


      25                <groupId>org.apache.maven.plugins</groupId>


      26                <artifactId>maven-clean-plugin</artifactId>


      27                <version>2.2</version>


      28                <configuration>


      29                   <filesets>


      30                      <fileset>


      31                         <directory>bin</directory>


      32                         <includes>


      33                            <include>**/*</include>


      34                         </includes>


      35                      </fileset>


      36                      <fileset>


      37                         <directory>gen</directory>


      38                         <includes>


      39                            <include>**/*</include>


      40                         </includes>


      41                      </fileset>


      42                   </filesets>


      43                </configuration>


      44             </plugin>


      45             <plugin>


      46                 <groupId>com.jayway.maven.plugins.android.generation2</groupId>


      47                 <artifactId>maven-android-plugin</artifactId>


      48                 <configuration>


      49                     <sdk>


      50                         <!--

                                  <path>${env.ANDROID_HOME}</path>

                                 --
      >


      51                         <path>/opt/android-sdk/</path>


      52                         <platform>2.1</platform>


      53                     </sdk>


      54                     <deleteConflictingFiles>true</deleteConflictingFiles>


      55                 </configuration>


      56                 <extensions>true</extensions>


      57             </plugin>


      58         </plugins>


      59     </build>


      60    <modules>


      61       <module>AP1</module>


      62       <module>AP1Test</module>


      63    </modules>


      64    <dependencies>


      65       <dependency>


      66          <groupId>android</groupId>


      67          <artifactId>android</artifactId>


      68          <version>2.1</version>


      69          <type>jar</type>


      70          <scope>provided</scope>


      71       </dependency>


      72    </dependencies>


      73


      74     <dependencyManagement>


      75         <dependencies>


      76             <dependency>


      77                 <groupId>android</groupId>


      78                 <artifactId>android</artifactId>


      79                 <version>2.1</version>


      80                 <scope>provided</scope>


      81             </dependency>


      82         </dependencies>


      83     </dependencyManagement>


      84 </project>





      Basically we
      1. define our source directory as src to be compatible with the Eclipse Android project structure
      2. configure the maven-compiler-plugin according to our needs
      3. clean Eclipse Android project structure too in our clean goal
      4. configure maven-android-plugin defining the location of the Android SDK. This can be defined here or read from the environment
      5. define our modules, in this case the main project AP1 and its tests AP1Test
      6. define the dependencies, android 2.1 in this case

      AP1's pom.xml





       1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">


       2   <modelVersion>4.0.0</modelVersion>


       3   <groupId>AP1</groupId>


       4   <artifactId>AP1</artifactId>


       5   <packaging>apk</packaging>


       6   <version>0.0.1-SNAPSHOT</version>


       7   <parent>


       8    <artifactId>P1</artifactId>


       9    <groupId>P1</groupId>


      10    <version>0.0.1-SNAPSHOT</version>


      11   </parent>


      12 </project>










      AP1Test's pom.xml





       1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

       2   <modelVersion>4.0.0</modelVersion>

       3   <groupId>AP1Test</groupId>

       4   <artifactId>AP1Test</artifactId>

       5   <packaging>apk</packaging>

       6   <version>0.0.1-SNAPSHOT</version>

       7   <parent>

       8    <artifactId>P1</artifactId>

       9    <groupId>P1</groupId>

      10    <version>0.0.1-SNAPSHOT</version>

      11   </parent>

      12   <dependencies>

      13    <dependency>

      14       <groupId>AP1</groupId>

      15       <artifactId>AP1</artifactId>

      16       <version>0.0.1-SANPSHOT</version>

      17    </dependency>

      18    <dependency>

      19       <groupId>AP1</groupId>

      20       <artifactId>AP1</artifactId>

      21       <version>0.0.1-SNAPSHOT</version>

      22       <type>apk</type>

      23    </dependency>

      24   </dependencies>

      25 </project>







      populating your local repository


      For maven to find android dependencies you must populate your local repository. To ease the task you can use android-mvn-install script which do all the hard work for you.
      Just run

      $ wget -qO - http://android.codtech.com/android-tools/android-mvn-install | bash -s -- --sdk-dir=/opt/android


      or download from its page and run it locally.
      There's an alternative for this step, maven-android-sdk-deployer, however I prefer the simplicity and flexibility of the android-mvn-install script.

      building the project

      Now we have several alternatives to build our project:
      • using Eclipse as usual for other Android projects using ADT plugin (i.e.: AP1 -> Run As -> Android Application)
      • using Maven from Eclipse (i.e.: P1 -> Run As -> Maven build)
      • using Maven from the command line (i.e.: mvn install)
      • using a continuous integration tool

      using hudson

      Having followed all the steps mentioned before we will now be able to create a job in hudson to build our project using maven











      conclusion

      There are still some rough edges and some things we haven't mentioned yet like running headless android emulators to be able to run the tests on the server. We will be covering these issues in future posts but I think that we have enough already to start applying continuous integration for our android projects.


      Comments are gladly welcome.






      Copyright © 2010 Diego Torres Milano. All rights reserved.