[Xamarin] Integración continua con Team City

Introducció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 03

Team 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
.nugetNuGet.exe install FAKE -Version 3.5.4
packagesFAKE.3.5.4toolsFAKE.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

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *