[Xamarin] Integración continua con Team City

Bug -01Introducción

La calidad en el software es algo innegociable. Un buen proceso en el desarrollo y gestión del proceso es fundamental para conseguir ese objetivo. Debemos entregar Apps móviles perfectamente adaptadas a cada plataforma, ofreciendo la mejor experiencia de usuario posible pero sobretodo, funcional. Una App funcional debe cubrir y cumplir unos mínimos exigentes de calidad.

Como desarrolladores, somo humanos y el código no estara libre de errores. Sin embargo, el proceso que apliquemos para la detección y corrección a los mismos, es vital.

A veces, ocurre...

A veces, ocurre…

Realizar un proceso automático en cada nuevo checkin o de manera programada donde:

  • Compilar proyectos.
  • Pasar pruebas unitarias.
  • Pasar pruebas de interfaz de usuario.
  • Incluso publicar automáticamente paquetes y resultados.

Nos permitirá detectar problemas en el código de la forma más prematura posible, pudiendo ofrecer mayor calidad. En este artículos vamos a repasar todo lo necesario para realizar

Tests en Xamarin

Para centrarnos en su totalidad en la parte de integración continua vamos a centrarnos en uno de los ejemplos realizados con Xamarin más conocidos por los desarrolladores Xamarin, TipCalc:

TipCalc

TipCalc

Aplicación multiplataforma que nos permite calcular la cantidad de propina a entregar en función del precio. Tenéis el código e incluso tutoriales de como crearla paso a paso en este enlace.

Nos centramos a continuación en pruebas de nuestra App. Pensar que nuestro código estara siempre absolutamente libre de errores es una quimera. Sin embargo, si cada vez que compilamos el proyecto tenemos una forma de pasar pruebas a nuestra aplicación tanto de lógica como de interfaz de usuario sin duda reduciremos la cantidad de errores y aumentaremos la calidad de la App.

Podemos crear dos tipos de pruebas diferenciadas:

  • Pruebas unitarias: Pruebas de pequeñas unidades funcionales de nuestra App. Utilizaremos NUnit para realizar estas pruebas unitarias generalmente de ViewModels, Helpers y Servicios.
  • Pruebas de interfaz de usuario: Pruebas sobre la interfaz de usuario, escritura en cajas de texto, pulsaciones de botones, etc. Utilizaremos Xamarin UITest para estas pruebas.

NOTA: Xamarin UITest es un framework de Testing de UI basado en Calabash que nos permite escribir y ejecutar tests con C# y NUnit para validar Apps Android e iOS.

Veamos una prueba unitaria de nuestro ejemplo:

[TestFixture]
public class TipCalcViewModelTests
{
     [Test]
     public void TipPercent_Updated_TipAmountAndTotalAreUpdated()
     {
         var model = new TipCalcViewModel
         {
             SubTotal = 10,
             PostTaxTotal = 12,
             TipPercent = 20
         };

         Assert.AreEqual(14, model.Total);
         Assert.AreEqual(2, model.TipAmount);
     }
}

Es una calculadora, verificamos que los cálculos de la misma sean correctos. Asignamos una serie de valores y verificamos que los resultados dados por la App son los esperados.

Pasamos a pruebas de interfaz de usuario:

[TestFixture]
public class TipCalculationTests
{
     private IApp _app;

     [SetUp]
     public void SetUp()
     {
         switch (TestEnvironment.Platform)
         {
             case TestPlatform.Local:
                 var appFile =
                     new DirectoryInfo(Path.Combine("..", "..", "testapps"))
                         .GetFileSystemInfos()
                         .OrderByDescending(file => file.LastWriteTimeUtc)
                         .First(file => file.Name.EndsWith(".app") || file.Name.EndsWith(".apk"));

                 _app = appFile.Name.EndsWith(".app")
                     ? ConfigureApp.iOS.AppBundle(appFile.FullName).StartApp() as IApp
                     : ConfigureApp.Android.ApkFile(appFile.FullName).StartApp();
                 break;
             case TestPlatform.TestCloudiOS:
                 _app = ConfigureApp.iOS.StartApp();
                 break;
             case TestPlatform.TestCloudAndroid:
                 _app = ConfigureApp.Android.StartApp();
                 break;
          }
      }

      [Test]
      public void CalculateTip()
      {
         const decimal subTotal = 10M;
         const decimal postTaxTotal = 12M;

         _app.EnterText(e => e.Marked("SubTotal"), subTotal.ToString(CultureInfo.InvariantCulture));
         _app.Screenshot("When I enter a subtotal");

         _app.EnterText(e => e.Marked("PostTaxTotal"), postTaxTotal.ToString(CultureInfo.InvariantCulture));
         _app.Screenshot("And I enter the post-tax total");

         var tipPercent = decimal.Parse(_app.Query(e => e.Marked("TipPercent")).Single().Text) / 100;
         var tipAmount = decimal.Parse(_app.Query(e => e.Marked("TipAmount")).Single().Text.Substring(1));
         var total = decimal.Parse(_app.Query(e => e.Marked("Total")).Single().Text.Substring(1));

         var expectedTipAmount = subTotal * tipPercent;
         Assert.AreEqual(expectedTipAmount, tipAmount);

         var expectedTotal = postTaxTotal + expectedTipAmount;
         Assert.AreEqual(expectedTotal, total);

         _app.Screenshot("Then the tip and total are calculated correctly");
     }
}

Accedemos a la App y accedemos a los distintos elementos de la interfaz para interactuar con ellos, recuperar valores y hacer verificaciones. Durante cualquier prueba podremos realizar acciones extras como por ejemplo tomar capturas.

NOTA: Resaltamos que el objetivo fundamental del artículo es la explicación paso a paso de conceptos y necesidades para preparar TeamCity como servidor de integración continua para Apps Xamarin. Hemos pasado de manera muy superficial por conceptos básicos de pruebas (Arrange (Preparar), Act (Actuar), Assert (Afirmar)), conceptos básicos de NUnit o de Xamarin UITest. En otros artículos profundizaremos en pruebas e incluso en uso e integración con Xamarin Test Cloud.

Integración Continua

La integración continua consiste en hacer integraciones automáticas de un Proyecto lo más a menudo possible para así detector fallos cuantos antes.

Entendemos por integración la compilación y ejecución de pruebas.

WorkFlow

WorkFlow

El flujo del proceso sería:

  1. El desarrollador trabaja en su equipo de desarrollo subiendo cambios al repositorio de código donde estaría el código además de las pruebas unitarias.
  2. De una forma automática, el repositorio de código envia el mismo al servidor de BUILD.
  3. El servidor de BUILD realizará la compilación de la solución o proyectos, ejecutará pruebas y recopilará la información de los resultados.
  4. Los resultados los obtiene el desarrollador para poder analizarlos y actuar en consecuencia.

“La integración continua no evitará que se produzcan bugs, pero si nos permite encontrarlos y solucionarlos de una forma dramáticamente más fácil”

Martin Flowler

Los beneficios de utilizar integración continua son:

  • Detectar errores con mayor rapidez y antelación. Esto provocará que sea más sencillo de corregir y por lo tanto más barato.
  • Consistencia. O lo que es lo mismo, tener Builds reproducibles. La App funcionará en cualquier entorno con las mismas condiciones. Evitamos la “contaminación de la Build” o lo que es lo mismo, tener la Build con parámetros “a fuego” y condiciones específicas de la máquina de Build.
  • Poder automatizar también la entrega consiguiendo una entrega continua.

CI Xamarin 03Team City

Dentro de las opciones de software de integración continua disponible, en este artículos vamos a centrarnos en Team City. Es:

  • Fácil de instalar y configurar.
  • Está disponible para Windows y OSX.
  • Cuenta con versión gratuita.

NOTA: Tenemos otras opciones que podemos utilizar para realizar integración continua con Apps Xamarin. Como por ejemplo, TFS (mucho más que un servidor de integracion continua) o Jenkins.

Instalación

Tras descargar la última versión de TeamCity procedemos a su instalación en el servidor de integración continua. En este ejemplo, la instalación y pruebas las realizare en mi propio equipo de desarrollo:

Inicio

Inicio

La instalación se realizará de forma muy sencilla de forma similar a cualquier otra instalación:

Similar al resto de instalaciones...

Similar al resto de instalaciones…

Al terminar comenzamos con las primeras configuraciones importantes. Podemos dejar todos lo valores que nos facilitan por defecto y aceptar pero tened en cuenta que:

  • serverUrl: Ruta de acceso al portal de administración web de Team City.
  • name: Nombre de la máquina correspondiente a nuestro servidor de integración continua.
  • systemDir, workDir y tempDir: Rutas del sistema, de trabajo y temporal correspondientes a nuestro Build Agent. Un Build Agent es una pieza configurable en Team City con capacidad para recibir diferentes comandos y lanzar Builds.
Configuración básica

Configuración básica

Llegados a este punto si accedemos a localhost con el puerto asignado a Team City veremos:

Todo listo!

Todo listo!

Configuración inicial

Los siguientes pasos en la configuración consisten en:

  • Creación de Base de Datos a utilizar.
  • Creación de usuario/s a utilizar.

La creación de la Base de datos es muy simple:

CI Xamarin 08

Elegimos BBDD a usar

Tras elegir el proveedor de Base de Datos y aceptar condiciones:

CI Xamarin 09

EULA

Pasamos a la creación de la cuenta de administrador:

Creamos usuario

Creamos usuario

Tras este paso podemos:

  • Crear nuevos usuarios.
  • Crear grupos.
  • Configurar diferentes notificaciones.
  • Integrar Team City con diferentes herramientas e IDEs.
  • Etc.
Configuración

Configuración

Llegados a este punto podemos crear un nuevo proyecto y dentro del mismo diferentes Builds con múltiples pasos. Sin embargo, no lo haremos así…

FAKE

A la hora de realizar integración continua tenemos varias opciones. Por un lado, podemos hacer por ejemplo la Build del proyecto mediante sencillas configuraciones en TeamCity y por otro lado podemos crear un script. Entre ambas opciones, siempre que sea posible es mejor contar con la segunda.

¿Por qué debemos crear un script?

Un script es:

  • Traceable.
  • Los desarrolladores pueden usarlo también!
  • Añaden más documentación extra al proyecto.
  • Mayor facilidad a la hora de mantenerlo.
  • Simplifica mucho la configuración del servidor de integración.

Para crear y gestionar nuestro script una opción muy recomendada es utilizar F# Make o FAKE. Es un Sistema de automatización de Builds muy similar a Make o Rake. Es un DSL sin necesidad de F# que podemos utilizar en Windows y OSX. Si necesitamos más funcionalidad que la disponible por defecto escribiremos código F# o referencias a librerías .NET.

Veamos un ejemplo básico de script con FAKE:

#r "tools/FAKE/tools/FakeLib.dll" // include Fake lib
open Fake
 
 
Target "Test" (fun _ ->
    trace "Testing stuff..."
)
 
Target "Deploy" (fun _ ->
    trace "Deploy stuff..."
)
 
"Test"            // define the dependencies
   ==> "Deploy"
 
Run "Deploy"

En el script de Build anterior definimos dos Targets, “Test” y “Deploy” donde “Deploy” depende de “Test”. En la parte superior incluimos y utilizamos la librería FakeLib (en otros scripts incluiremos otras librerías y helpers).

Para ejecutar el script normalmente crearemos un archivo BAT donde descargaremos la útima versión de FAKE vía NuGet y lo ejecutaremos.

@echo off
.nuget\NuGet.exe install FAKE -Version 3.5.4
packages\FAKE.3.5.4\tools\FAKE.exe build.fsx %1

Sencillo, ¿cierto?

Integración Continua de Apps Xamarin con Team City

Pasamos a realizar (si lo sé, por fin) la integración continua con nuestra App TipCalc. Comenzamos creando un script que nos permita realizar un paso básica, la compilación de la solución:

// include Fake lib
#r @"packages/FAKE.3.5.4/tools/FakeLib.dll"
#load "build-helpers.fsx"

open Fake
open System
open System.IO
open System.Linq
open BuildHelpers
open Fake.XamarinHelper
open HockeyAppHelper

// *** Define Targets ***
Target "common-build" (fun () ->

    RestorePackages "TipCalc.sln"

    MSBuild "src/TipCalc/bin/Debug" "Build" [ ("Configuration", "Debug"); ("Platform", "Any CPU") ] [ "TipCalc.sln" ] |> ignore
)
 
// *** Start Build ***
RunTarget()

Utilizamos MSBuild para compilar la solución habiendo restaurado previamente paquetes.

NOTA: En nuestro ejemplo compilamos en modo Debug. Normalmente, interesará en modo Release e incluso realizar los dos modos.

Target "core-build" (fun () ->

    RestorePackages "TipCalc.Core.sln"

    MSBuild "src/TipCalc/bin/Debug" "Build" [ ("Configuration", "Debug"); ("Platform", "Any CPU") ] [ "TipCalc.Core.sln" ] |> ignore
)

Una vez creado el script, ¿cómo hacemos que se ejecute en TeamCity?, ¿cúando?. Vamos a ello!

Tras crear un nuevo proyecto en TeamCity, crearemos una nueva Build:

CI Xamarin 14

Conectamos con el repositorio

Conectamos con el repositorio de código utilizado. Se soportan todos los repositorios habituales, VSO, TFS, Git, etc. Al crear la Build, definiremos N triggers. Con ellos podemos ejecutar una acción, en nuestro caso el script en función de una determinada condición. Podemos:

  • Lanzar cuando se hagan cambios en el código.
  • Lanzar de manera programada a una hora o fecha concreta.
  • Lanzar ante cambio de rama.
  • Etc.

Normalmente se programarán Builds “pesadas”, es decir, con varias compilaciones, gran cantidad de tests y se pasaran Builds más ligeras cada vez que se suban cambios en el código:

 

CI Xamarin 15

Añadimos Triggers

CI Xamarin 16

Añadimos pasos

Llegamos al paso más importante. En Team City tenemos gran variedad de pasos de Build a nuestra disposición:

  • Compilar un proyecto
  • Restautar paquetes
  • Copiar ficheros
  • Ejecutar scripts
  • etc

En nuestro caso nos interesa ejecutar un script:

Ejecutamos script

Ejecutamos script

Elegimos la opción Command Line, seleccionamos el archivo del script e importante, en el campo Step name, indicaremos el target que deseamos ejecutar del script. En nuestro ejemplo, core-build que realizará la descarga de paquetes y la compilación de la solución en modo Debug.

Sencillo, ¿verdad?. Pues el resto de acciones a realizar serán similares pero con los consecuentes cambios en el script correspondiente.

Pasamos a otra parte importante en la integración continua, pasar pruebas:

Target "core-tests" (fun () ->
    RunNUnitTests "src/TipCalc/bin/Debug/TipCalc.Tests.dll" "src/TipCalc/bin/Debug/testresults.xml" |> ignore
)

Contamos con helpers para pasar con facilidad tests con NUnit. Indicamos la librería de pruebas además de una ruta con resultados. En Team City, para pasar estas pruebas bastara con crear un nuevo paso de Build de forma similar a la anterior.

NOTA: Podemos acceder con facilidad a los resultados. Dependiendo de necesidades puede interesar añadir paso para enviar estos resultados por correo, subirlo a otra web o herramienta.

Pasamos a ver la compilación del proyecto Windows Phone:

Target "windows-phone-build" (fun () ->
    RestorePackages "TipCalc.WindowsPhone.sln"

    MSBuild "src/TipCalc.WindowsPhone/TipCalc.WindowsPhone/bin/Debug" "Build" [ ("Configuration", "Debug") ] [ "TipCalc.WindowsPhone.sln" ] |> ignore
)

Android:

Target "android-build" (fun () ->
    RestorePackages "TipCalc.Android.sln"

    MSBuild "src/TipCalc.Android/bin/Debug" "Build" [ ("Configuration", "Debug") ] [ "TipCalc.Android.sln" ] |> ignore
)

Creación de paquete:

Target "android-package" (fun () ->
    AndroidPackage (fun defaults ->
        {defaults with
            ProjectPath = "src/TipCalc.Android/TipCalc.Android.csproj"
            Configuration = "Debug"
            OutputPath = "src/TipCalc.Android/bin/Debug"
        })
    |> fun file -> TeamCityHelper.PublishArtifact file.FullName
)

Recordad que podemos indicar las dependencias entre diferentes Targets. De este modo un Target no se lanzará si el anterior no se ha completado con éxito:

// *** Define Dependencies ***
"core-build"
  ==> "core-tests"

"android-build"
  ==> "android-package"

Llegados a este punto, vemos como podemos crear diferentes Targets en nuestro script creando pasos de Build en nuestro proyecto para cubrir todas nuestras necesidades. FAKE cuenta con una enorme cantidad de helpers para realizar tareas como:

  • Restauración de paquetes.
  • Utilizar FXCop.
  • Publicar un paquete Android.
  • Realizar entrega continua con HockeyApp (lo veremos con detalles en otro artículo).
  • Etc.

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Para más información recomiendo el video “Using Continuous Integration with Xamarin Apps” del Evolve 2014 por Greg Shackles.

Espero que lo visto en la entrada os sea útil. Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s