# Depuración y pruebas

## Depuración en Xcode

### Depuración “clásica” con NSLog y Asserts

* La depuración clásica consiste en alterar el código fuente de forma temporal añadiendo una serie de directivas con el fin de mostrar mensajes informativos en la consola. Como ya hemos visto, la función habitual para enviar mensajes a la consola de XCode es `NSLog()`

> Antes de generar el binario de distribución para la App Store deberíamos eliminar todas las llamadas a `NSLog` que tengamos en el código para así dejar "limpia" la aplicación y evitar una posible relentización de la ejecución debido a la escritura en la consola.

* Otro tipo de función con la cual podemos depurar nuestro código por consola es el llamado **Assert**. Los Asserts (o *aserciones*) son condiciones que se deben de cumplir para que continue la ejecución en un determinado momento. Estas deben de devolver `true`. En el caso de que algún assert no se cumpla se producirá una excepción en fase de ejecución que detendrá la aplicación en ese mismo instante almacenando todo el "log" en XCode para su posterior depuración.
* La directiva assert en Objective-C viene en forma de macro `assert()` a la cual se le pasa una condición que será que se tenga que evaluar a verdadero o falso. Si se evalúa a falso la aplicación se detendrá mostrando en la consola un log y si se evalúa a verdadero la aplicación continuará con su ejecución. Los asserts son usados frecuementes en APIs así como en códigos en fases de testeo.

```
assert(valor < maximoValor);
```

* `assert` hace que el programa aborte inmediatamente cuando falla la aserción
* De modo alternativo podemos usar `NSAssert`, que a diferencia de `asssert`:
  * Cuando falla la aserción no aborta directamente sino que genera una excepción de tipo `NSInternalInconsistencyException` (que por supuesto también causará un *crash* si no la capturamos).
  * Nos permite especificar un mensaje de error con argumentos

```
NSAssert(valor < maximoValor, @"El valor %i es demasiado grande (max.:%i)!", valor, maximoValor);
```

### El depurador integrado de Xcode

El depurador visual integrado en Xcode es muy similar a los que hay en otros IDES. Podemos fijar *breakpoints* clicando en el margen izquierdo del código, y una vez parada la ejecución podemos ver el valor de las variables, continuar con la ejecución hasta el siguiente *breakpoint*, ir paso a paso, etc.

> A diferencia de otros IDEs no hay un modo especial de ejecución para depurar el código, se parará en los *breakpoints* cuando ejecutamos la aplicación normalmente con `Product > Run` (o con el botón de “play” de la barra de herramientas). No obstante, en el “Breakpoint Navigator” podemos deshabilitar todos los *breakpoints* si queremos ignorarlos en un momento dado.

Una funcionalidad muy interesante es la de **Quick Look**, que nos permite ver el contenido de ciertas variables de forma “gráfica”. Por ejemplo si se trata de un `UIImage` podemos ver una versión en pequeño de la imagen para comprobar rápidamente que corresponde con lo que esperamos

![](https://281066622-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LDGNOAA3qbruZohis0s%2F-LDGNRE8cORflvZ8P6iY%2F-LDGNbDZDnAkDyOHWgkP%2Fquick_look_con_imagen.png?generation=1527152540400306\&alt=media)

* Como se ve en la figura anterior, cuando estamos depurando el código y la ejecución está parada en un punto, pasando el ratón por encima de una variable aparece un *tooltip* con información sobre ella. Pulsando sobre el pequeño icono en forma de “ojo” que aparece en el *tooltip* activamos el *quick look*. También podemos hacerlo pulsando el mismo icono en el “área de depuración” (parte inferior de la pantalla), donde aparecen las variables actuales

![](https://281066622-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LDGNOAA3qbruZohis0s%2F-LDGNRE8cORflvZ8P6iY%2F-LDGNbDcJbmO8y6Y57EB%2Fquick_look_debug_area.png?generation=1527152540313075\&alt=media)

* *Quick look* funciona con muchos tipos de variables: vistas (componentes de la interfaz de usuario, que como hemos visto aparecen “en pequeño”), localizaciones geográficas (aparece un mapa centrado en la localización), *frames* de vistas (aparece un rectángulo con las proporciones adecuadas y una anotación con el tamaño actual),curvas dibujadas con el API de gráficos, …

## Testing

* XCode tiene desde la versión 5 un *framework* de *testing* llamado `XCTest` (en las versiones anteriores se usaba otro distinto, denominado `OCUnit`).
* Desde Xcode 5, cuando creamos un proyecto se crea automáticamente un conjunto de pruebas unitarias vacío y un *target* para ejecutar esas pruebas con el mismo nombre del proyecto pero acabado en `"Test"`. Si tenemos un proyecto ya creado sin pruebas unitarias, podemos crearlas con `File > New > Target > Cocoa touch unit test`.
* En `XCTest` hay varios tipos distintos de pruebas unitarias:
  * **Tests de “lógica”**: las clásicas pruebas en las que comprobamos si determinado método funciona o no correctamente.
  * **Tests de “lógica” en modo asíncrono**:
  * **Tests de tiempo de respuesta**: en los que podemos ver estadísticas del tiempo que tarda en ejecutarse determinado bloque de código. Podemos fijar un *baseline* de tiempo de modo que el test se considerará que no pasa si está por encima del *baseline*

### El navegador de Tests

* Para moverse por los tests, lo más sencillo es usar el *Test Navigator*, que aparece en el área de Navegadores, a la izquierda de la pantalla. Su icono es el quinto por la izquierda, un rombo ![](https://281066622-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LDGNOAA3qbruZohis0s%2F-LDGNRE8cORflvZ8P6iY%2F-LDGNbDkGIuqFqoBb_Ro%2Frombo_navigator.png?generation=1527152552736494\&alt=media). En este navegador podemos ver todos los tests, ir al fuente clicando sobre el nombre del test y ejecutarlos pulsando sobre el pequeño botón de “play” que aparece a la derecha cuando pasamos el ratón por encima.

![](https://281066622-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LDGNOAA3qbruZohis0s%2F-LDGNRE8cORflvZ8P6iY%2F-LDGNbDmujlpBAZmFvhJ%2Ftest_navigator.png?generation=1527152559870506\&alt=media)

### Escribir y ejecutar pruebas unitarias

* Como ya hemos dicho, `XCTest` es muy similar a otros *frameworks* de pruebas unitarias como `JUnit`, así que no es muy complicado de usar para alguien que ya haya usado los otros.
* Por ejemplo, supongamos que tenemos un juego de tres en raya con el siguiente interfaz (simplificado)

```
typedef enum {
  Casilla_Vacia,Casilla_X,Casilla_O
} Casilla;

@interface TresEnRayaModelo : NSObject
- (id) init;
- (Casilla) getCasillaFila:(int)fila Columna:(int)columna;
- (void) setCasilla:(Casilla)valor Fila:(int)fila Columna:(int)columna;
@end
```

Vamos a comprobar varias cosas

* Que cuando se inicializa el tablero todas las casillas están vacías
* Que las casillas se pueden obtener/fijar correctamente a un valor dado
* Que cuando se intenta obtener/fijar una casilla que no existe se produce una excepción

#### Estructura de una *suite* de pruebas

* Iríamos a la plantilla de clase de pruebas que ha creado Xcode e introduciríamos el siguiente código

```
@interface TresEnRayaTests : XCTestCase
@end

@implementation TresEnRayaTests {
    TresEnRayaModelo *ter;
}

- (void)setUp {
    [super setUp];
    ter = [[TresEnRayaModelo alloc] init];
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testInitDevuelveTableroVacio {
    for (int fila=0;fila<3;fila++)
        for(int col=0;col<3;col++) {
            XCTAssertEqual(Casilla_Vacia, [ter getCasillaFila:fila Columna:col]);
        }
}

-(void)testCasillaIncorrectaGeneraExcepcion {
    XCTAssertThrows([ter getCasillaFila:10 Columna:10], @"Debería lanzar excepción");
    XCTAssertThrows([ter setCasilla:Casilla_X Fila:10 Columna:10], @"Debería lanzar excepción");
}

@end
```

* Hay varios puntos a destacar:
  * Por defecto **la plantilla coloca el** `@interface` **y la** `@implementation` de la clase de la *suite* de tests **en el mismo archivo** `.m`. Esto es adecuado en la mayoría de los casos, ya que no vamos a referenciar la *suite* desde otras clases y no necesitamos por tanto un `@interface` separado
  * Al igual que en `JUnit` hay dos métodos: `setup` y `teardown` que se ejecutan al inicio y al final de cada test, respectivamente.
  * Las pruebas unitarias se implementan en métodos que deben devolver `void` y cuyo nombre debe comenzar por `test`.
  * Comprobamos el correcto funcionamiento de la lógica con los métodos `XCTAssert`

#### Aserciones

En todas las aserciones podemos poner como parámetro final un mensaje (Un `NSString`) que aparecerá si falla el test

```
XCTAssertTrue(NO, @"Esta prueba va a fallar seguro");
```

Tenemos varios tipos de aserciones, por ejemplo:

* `XCTAssertTrue` y  `XCTAssertFalse` comprueban que algo es cierto o falso, respectivamente
* `XCTAssertEqual` sirve para comprobar la igualdad de valores escalares. En el ejemplo lo hemos usado para comprobar el contenido de las casillas al ser este un `enum`. Tenemos también el contrario, `XCTAssertNotEqual`
* `XCTAssertEqualObjects` es como el anterior, pero para comparar igualdad entre objetos. Internamente llama al `isEqual`.
* `XCTAssertThrows`comprueba que una llamada a un método genera una excepción. Opcionalmente, podemos comprobar que además la excepción es de una clase específica.

  Se recomienda consultar la documentación de Apple para [más detalles sobre los distintos tipos de aserciones](https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/testing_with_xcode/testing_3_writing_test_classes/testing_3_writing_test_classes.html#//apple_ref/doc/uid/TP40014132-CH4-SW34).

#### Ejecutar las pruebas y ver el resultado

* En el *Test navigator* podemos pulsar el pequeño botón de “play” que aparece al pasar el ratón por encima de cada test, para ejecutarlo individualmente o bien el que aparece en la *suite* completa para ejecutar todas sus pruebas.
* Se pondrá en marcha el simulador de iOS con la aplicación, ejecutará las pruebas y terminará. En el *test navigator* y en el código fuente de la *suite* aparecerá un icono al lado de cada test indicando si ha pasado o no. Si no ha pasado aparecerá además un mensaje en el fuente indicándolo. También podemos ver los mensajes de error en el *Log Navigator*.

#### Pruebas de tiempo de respuesta

Con Xcode 6 se introduce un nuevo tipo de tests: los de tiempo de respuesta. Podemos ver estadísticas sobre el tiempo que tarda en ejecutarse un determinado bloque de código. Esto lo hacemos con el método `measureBlock`, que acepta el bloque a ejecutar como parámetro

```
- (void) testInitTableroPerformance {
    [self measureBlock:^{
        for(int i=1;i<=100000;i++) {
            TresEnRayaModelo *modelo = [[TresEnRayaModelo alloc] init];
        }
    }];
}
```

Cuando ejecutamos el test el bloque se ejecutará automáticamente 10 veces y nos aparecerán estadísticas sobre el tiempo medio y la desviación típica. Podemos usar estas estadísticas para fijar un *baseline*, un tiempo de referencia para la ejecución (se puede fijar un *baseline* por separado para cada hardware:iPhone 6/5, iPad Air, …) ![](https://281066622-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LDGNOAA3qbruZohis0s%2F-LDGNRE8cORflvZ8P6iY%2F-LDGNbE5JCI7LByv6nQw%2Fperformance_test.png?generation=1527152540392466\&alt=media)

## Referencias

* [Guía de apple](https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/testing_with_xcode/Introduction/Introduction.html#//apple_ref/doc/uid/TP40014132) sobre pruebas unitarias en Xcode 5 y posterior
