Un Beagle y Yo

Todo es posible. Lo imposible simplemente lleva más tiempo.

Empezando con Android, Maven, Robolectric y Roboguice.

con un comentario

Siempre es mejor montar la estructura de un proyecto al principio y luego dedicarte a programar que andar luego moviendolo todo y arriesgarte a perder tiempo y tu pelo por el estres.

— Aviso de que es bastante largo y esta escrito mas como recordatorio que otra cosa. De todas formas seguro que a alguien le sirve de algo. –

¿Por que usar Maven?, ¿y Robolectric?, ¿WTF es Roboguice?

Si estas leyendo esto seguramente tengas repuesta a estas preguntas, pero para los que no.

No voy a explicar aquí lo que es maven, hay millones de post ahí fuera que lo explican mucho mejor de lo que yo lo haría , pero si programas en java y no sabes lo que es maven deberías hacerte el harakiri, si no programas en java deberías hacerte el harakiri también.

Suponiendo que no  has muerto, si has trabajado con maven tienes bastante posibilidades de saber lo que es Spring, osea que seguramente sabes lo que es la inyección de dependencias. Roboguice es una extensión de google-guice para android que básicamente lo que hace es eso.

Por ultimo Robolectric, lo que ha liado la gente de google con Android es una pasada. El emulador que es un fork de Qemu funciona bastante bien. El plugin adt para eclipse es impresionante,  pero hacer TDD contra el framework oficial es demasiado lento. Y ahí es cuando entra robolectric, estoy literariamente enamorado del proyecto. Incluir esta librería te permite hacer TDD en android con simples test Junit tan rápidos como siempre. Los chicos de Robolectric han montando un entorno de mocks que emulan todo el conjunto de clases necesario para dejar de ver ese odiado mensaje Stub! en tu cuadro de tests.

Una vez echas las presentaciones vamos con los prerequisitos.

Prerequisitorios.

Se presupone que tienes maven funcionando en tu sistema.

[sorack@Cerberos ~]$ mvn -v
Apache Maven 3.0.2 (rNON-CANONICAL_2011-01-14_09-38_root; 2011-01-14 09:38:12+0000)
Java version: 1.6.0_22, vendor: Sun Microsystems Inc.
Java home: /usr/lib/jvm/java-6-openjdk/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "2.6.37-arch", arch: "amd64", family: "unix"

Que tienes el sdk de Android con el PATH del sistema apuntando a las tools.

[sorack@Cerberos ~]$ which android
/opt/android-sdk/tools/android

Tienes un eclipse instalado con los plugins adt, m2eclipse y M2Eclipse Android Integration plugin

Y ahora si que si, al tajo. El codigo fuente lo teneis en http://code.google.com/p/android-maven-robolectric-roboguice-example/

Empezamos creando un proyecto desde la linea de comandos

android create project
	--target 1
	--name MyAndroidApp
	--path ./MyAndroidAppProject
	--activity MyAndroidAppActivity
	--package com.example.myandroid

Una vez ejecutado el comando tenemos un proyecto android con toda su estructura de carpetas.

Todas las aplicaciones android tienen un AndroidManifest.xml donde se definen las propiedades de la aplicación.Le añadimos esta linea al nuestro para especificar la versión del api que vamos a utilizar.

<uses-sdk android:minSdkVersion="8" />

Importamos el proyecto a eclipse y así vamos jugando con los dos, linea de comandos e IDE.

File->New->Android Project, pulsamos “project from existing source”, seleccionamos lacarpeta que nos creo el comando y vemos que eclipse nos rellena todo los campos.

Le ponemos el nombre que queramos al proyecto.

Elegimos el api que queremos usar, en este caso el 8

Ya tenemos creado nuestro proyecto en eclipse, si ahora pulsamos Run As-> Android Aplicación veremos como se levanta el emulador y arrancara nuestra activity con el “Hello World” de facto.

Solo por curiosidad, vamos a ver los builders que tiene el proyecto ahora mismo. (Luego hablaremos mas sobre los builders)

Ahora a por la integración con maven.

Creamos el pom.xml en la raíz del proyecto y le añadimos unas cuantas dependencias y el maven-android-plugin.

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
	<groupId>com.example.myandroid</groupId>
	<artifactId>MyAndroidApp</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>apk</packaging>
    <name>MyAndroidApp</name>

    <dependencies>

  		<dependency>
        	<groupId>com.google.android</groupId>
            <artifactId>android</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>

  		<dependency>
            <groupId>com.pivotallabs</groupId>
            <artifactId>robolectric</artifactId>
            <version>0.9.8</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.roboguice</groupId>
            <artifactId>roboguice</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>2.0-no_aop</version>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.8.5</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
        	<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-source-plugin</artifactId>
				<version>2.1.2</version>
				<executions>
					<execution>
						<id>attach-sources</id>
						<phase>verify</phase>
						<goals>
							<goal>jar-no-fork</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
			<plugin>
                <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                <artifactId>maven-android-plugin</artifactId>
                <version>2.8.3</version>
                <configuration>
                    <sdk>
                        <path>/opt/android-sdk</path>
                        <platform>8</platform>
                    </sdk>
                    <emulator>
                        <avd>Android_2.2</avd>
                    </emulator>
                    <deleteConflictingFiles>true</deleteConflictingFiles>
                    <undeployBeforeDeploy>true</undeployBeforeDeploy>
                </configuration>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    </build>
</project>

Por comentar algunas cosas, tenemos la librería de android, robolectric, google-guice, roboguice, Mockito y Hamcrest. En el plugin de android tenemos la ruta a nuestro sdk y la avd en el que queremos hacer el deploy.

Desde la consola probamos que maven puede compilar y hacer deploy sin problemas.

[sorack@Cerberos MyAndroidAppProject]$ mvn clean package android:deploy
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building MyAndroidApp 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-android-plugin:2.8.3:deploy (default-cli) @ MyAndroidApp ---
[INFO] /opt/android-sdk/platform-tools/adb [install, -r, /home/sorack/IDE/workspaces/MyAndroidAppProject/target/MyAndroidApp.apk]
[INFO] 978 KB/s (172096 bytes in 0.171s)
	pkg: /data/local/tmp/MyAndroidApp.apk
Success
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.709s
[INFO] Finished at: Sat Mar 12 12:08:41 GMT 2011
[INFO] Final Memory: 14M/170M
[INFO] ------------------------------------------------------------------------

Perfecto, siguiente paso añadir la gestión de dependencias con maven al proyecto de eclipse. Botón derecho en el proyecto y Maven-> Enable dependency management.

Ahora si vamos a las propiedades del proyecto->Builders deberíamos ver los 5 builders activados.

Si por lo que sea no vierais los 5 en el código fuente teneis una copia del .project, solo copiar y pegar lo que os haga falta.

En la lista de builders, subir el maven projet builder y dejar el ultimo el Android package builder. En cuanto os pongáis a trabajar un poquito veréis que maven tarda mucho en hacer el build, así que yo trabajo con este builder desactivado salvo que quiera depurar con eclipse, pero de momento lo dejamos así. Otra opción es quitar build automatically, pero yo personalmente prefiero lo primero.

Creamos en eclipse las típicas source folders /src/main/java y /src/test/java. Las añadimos al buildpath y dejamos las output folder como en la imagen.

Movemos nuestra activity “MyAndroidAppActivity.java” a /src/main/java.

Ahora mismo deberíamos de poder hacer Run en eclipse y deploy desde la linea de comandos y ser capaces de ver el “hello world”.

Roboelectric

Voy a ir poniendo como van quedando las clases sin extenderme mucho, todos sabemos como funciona TDD.

Botón derecho a nuestra source folder src/test/java -> new -> Junit test case y creamos nuestro primer test.

@RunWith(RobolectricTestRunner.class)
public class MyAndroidAppActivityTest {

	private Button pressMeButton;
	private MyAndroidAppActivity activity;

	@Before
	public void setUp() throws Exception {
		activity = new MyAndroidAppActivity();
		activity.onCreate(null);
		pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
	}

	@Test
	public void shouldHaveAButtonThatSaysPressMe() throws Exception {
		assertEquals("Press me",pressMeButton.getText().toString());
	}

El botoncito que hemos creado en el layout

<Button
     	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:id="@+id/press_me_button"
    	android:text="Press me"
    />

 

Se pueden hacer maravillas con Robolectric pero este tutorial es solo para ponerlo en marcha. Nos podríamos quedar aquí y empezar a testear nuestra aplicación sin problemas pero lo vamos a complicar un poquito mas añadiendo y testeando con roboguice.

Roboguice

Siguiendo las instrucciones de instalación de la pagina oficial creamos una clase que extienda de RoboApplication y una clase que extienda de AbstractAndroidModule.

package com.example.myandroid;
public class MyModule extends AbstractAndroidModule {

	@Override
	protected void configure() {
		// TODO Auto-generated method stub
	}
}

package com.example.myandroid;
public class MyApplication extends RoboApplication {

	private Module module = new MyModule();

	@Override
	protected void addApplicationModules(List modules) {
		modules.add(module);
	}

	public void setModule(Module module) {
		this.module = module;
	}
}

Registramos nuestra aplicación en el AndroidManifest.xml

 <application
    	android:label="@string/app_name"
    	android:name=".MyApplication">

Ahora podemos probar a volver a correr el test y a hacer deploy de la aplicación a ver si todo va bien.

Vamos a inyectar el TextView(al que le he puesto un id), el botón y una clase Date para mostrar el “hello world” + la fecha.

Fijaros que la Activity ahora extiende de RoboActivity.

package com.example.myandroid;
public class MyAndroidAppActivity extends RoboActivity
{
	@InjectView(R.id.press_me_button) Button pressMeButton;
	@InjectView(R.id.hello_world_text) TextView helloWorld;
	@Inject Date date;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        String text = helloWorld.getText().toString();
        text += "  " +date.toString();
        helloWorld.setText(text);
    }
}

Si hacéis un run del proyecto en eclipse sin pasar por los test la aplicación funciona perfecta, pero si lo hacéis con maven el test no pasa. Salta un NullPointerException

1) Error injecting constructor, java.lang.NullPointerException
at roboguice.util.Ln$BaseConfig.(Unknown Source)
while locating roboguice.util.Ln$BaseConfig
for field at roboguice.util.Ln.config(Unknown Source)

Este error se ha comentado aquí, pero solo es necesario hacer unos ajustes en Robolectric para que todo funcione.

Básicamente necesitamos dos cosas, un nuevo testRunner que extienda de RobolectricTestRunner y un nuevo modulo para registrar en nuestra aplicación.

El nuevo Modulo, el mock del date es solo para ilustrar el ejemplo, normalmente utilizaríamos un provider que seteariamos luego en el test.

package com.example.myandroid;

public class MyTestModule extends AbstractAndroidModule {

    @Override protected void configure() {
    	Date date = Mockito.mock(Date.class);
    	Mockito.when(date.toString()).thenReturn("Mock!!!");
        bind(Date.class).toInstance(date);
        bind(Ln.BaseConfig.class).toInstance(new MyAppliactionLoggerConfig());
    }

    static class MyAppliactionLoggerConfig extends Ln.BaseConfig {
        public MyAppliactionLoggerConfig() {
            super();
            this.packageName = "MyApplication";
            this.minimumLogLevel = Log.VERBOSE;
            this.scope = "APP";
        }
    }
}

El nuevo testRunner

package com.example.myandroid;

public class InjectedTestRunner extends RobolectricTestRunner {

    public InjectedTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected Application createApplication() {
        MyApplication application = (MyApplication)super.createApplication();
        application.setModule(new MyTestModule());
        return application;
    }

    @Override
    public void prepareTest(Object test) {
    	MyApplication application = (MyApplication) Robolectric.application;

        Injector injector = application.getInjector();
        ContextScope scope = injector.getInstance(ContextScope.class);
        scope.enter(application);

        injector.injectMembers(test);
    }
}

Y nuestro nuevo test, fijaros que ahora corre con InjectedTestRunner.

package com.example.myandroid;

import static org.junit.Assert.*;

@RunWith(InjectedTestRunner.class)
public class MyAndroidAppActivityTest {

	@Inject Context context;
	@Inject MyAndroidAppActivity activity;

	private Button pressMeButton;
	private TextView helloWorld;

	@Before
	public void setUp() throws Exception {
		activity.onCreate(null);
		pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
		helloWorld = (TextView)activity.findViewById(R.id.hello_world_text);
	}

	@Test
	public void shouldHaveAButtonThatSaysPressMe() throws Exception {
		assertEquals("Press me",pressMeButton.getText().toString());
	}

	@Test
	public void shouldHaveATextViewThatSaysMock() throws Exception {
		assertTrue(helloWorld.getText().toString().contains("Mock!!!"));
	}

}

Tenéis el código fuente en http://code.google.com/p/android-maven-robolectric-roboguice-example/

Builder

Como he dicho antes yo trabajo en el builder de maven desactivado. Hago los deploys de la aplicación con maven desde linea de comandos y no suelo depurar mucho. Cuando toco algún recurso el cual requiere recompilar la clase R llamo a maven por linea de comandos también.

Advertisement

Escrito por jnavarretesanchez

marzo 12, 2011 a 8:08 pm

Escrito en Uncategorized

Etiquetado con , , , ,

Una respuesta

Suscríbete a los comentarios mediante RSS.

  1. Fabuloso… el pom.xml del proyecto de ejemplo me va a servir un montón. Gracias por compartir tu experiencia.

    Cristian

    junio 11, 2011 a 3:01 am


Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s

Seguir

Get every new post delivered to your Inbox.