Tutorial de pruebas unitarias y pruebas de interfaz de usuario de iOS

Nota de actualización: Michael Katz ha actualizado este tutorial para Xcode 10.1, Swift 4.2 y iOS 12. Audrey Tam escribió el original.

Escribir pruebas no es glamuroso, pero como las pruebas evitan que tu aplicación brillante se convierta en una chatarra llena de errores, es necesario. Si estás leyendo este tutorial, ya sabes que debes escribir pruebas para tu código y la interfaz de usuario, pero puede que no sepas cómo.

Puede que tengas una aplicación que funciona, pero quieres probar los cambios que estás haciendo para ampliar la aplicación. Tal vez ya tienes pruebas escritas, pero no estás seguro de si son las pruebas correctas. O bien, has empezado a trabajar en una nueva aplicación y quieres hacer pruebas sobre la marcha.

Este tutorial le mostrará:

  • Cómo utilizar el navegador de pruebas de Xcode para probar el modelo y los métodos asíncronos de una app
  • Cómo fingir interacciones con objetos de la librería o del sistema utilizando stubs y mocks
  • Cómo probar la UI y el rendimiento
  • Cómo utilizar la herramienta de cobertura de código

A lo largo del camino, recogerás parte del vocabulario utilizado por los ninjas de las pruebas.

Descubriendo qué probar

Antes de escribir cualquier prueba, es importante conocer lo básico. Qué es lo que necesitas probar?

Si su objetivo es ampliar una aplicación existente, primero debe escribir pruebas para cualquier componente que planee cambiar.

En general, las pruebas deben cubrir:

  • Funcionalidad del núcleo: Clases y métodos del modelo y sus interacciones con el controlador
  • Los flujos de trabajo más comunes de la interfaz de usuario
  • Condiciones límite
  • Corrección de errores

Mejores prácticas para las pruebas

El acrónimo FIRST describe un conjunto conciso de criterios para realizar pruebas unitarias eficaces. Estos criterios son:

  • Rápido: Las pruebas deben ejecutarse rápidamente.
  • Independiente/Aislado: Las pruebas no deben compartir estado entre sí.
  • Repetible: Debe obtener los mismos resultados cada vez que ejecute una prueba. Los proveedores de datos externos o los problemas de concurrencia podrían causar fallos intermitentes.
  • Autovalidación: Las pruebas deben ser totalmente automatizadas. El resultado debe ser «correcto» o «incorrecto», en lugar de depender de la interpretación de un archivo de registro por parte del programador.
  • Oportuno: Idealmente, las pruebas deben ser escritas antes de escribir el código de producción que prueban (Test-Driven Development).

Seguir los principios de FIRST mantendrá sus pruebas claras y útiles, en lugar de convertirse en bloqueos para su aplicación.

Cómo empezar

Comience por descargar los materiales del proyecto utilizando el botón de descarga de materiales en la parte superior o inferior de este tutorial. Hay dos proyectos de inicio separados: BullsEye y HalfTunes.

  • BullsEye se basa en una aplicación de ejemplo en iOS Apprentice. La lógica del juego está en la clase BullsEyeGame, que probarás durante este tutorial.
  • HalfTunes es una versión actualizada de la app de ejemplo del Tutorial de URLSession. Los usuarios pueden consultar la API de iTunes para las canciones, a continuación, descargar y reproducir fragmentos de canciones.

Pruebas de unidad en Xcode

El navegador de pruebas proporciona la forma más fácil de trabajar con las pruebas; lo utilizarás para crear objetivos de prueba y ejecutar pruebas contra tu aplicación.

Creación de un objetivo de prueba unitaria

Abra el proyecto BullsEye y pulse Comando-6 para abrir el navegador de pruebas.

Haga clic en el botón + de la esquina inferior izquierda y, a continuación, seleccione Nuevo objetivo de prueba unitaria… en el menú:

Acepte el nombre predeterminado, BullsEyeTests. Cuando el paquete de pruebas aparezca en el navegador de pruebas, haga clic para abrir el paquete en el editor. Si el paquete no aparece automáticamente, solucione el problema haciendo clic en uno de los otros navegadores y, a continuación, vuelva al navegador de pruebas.

La plantilla por defecto importa el marco de pruebas, XCTest, y define una subclase BullsEyeTests de XCTestCase, con setUp(), tearDown(), y métodos de prueba de ejemplo.

Hay tres maneras de ejecutar las pruebas:

  1. Producto ▸ Prueba o Comando-U. Ambos ejecutan todas las clases de prueba.
  2. Haga clic en el botón de flecha en el navegador de pruebas.
  3. Haga clic en el botón de diamante en el canalón.

También puede ejecutar un método de prueba individual haciendo clic en su diamante, ya sea en el navegador de pruebas o en el canalón.

Pruebe las diferentes formas de ejecutar las pruebas para tener una idea de cuánto tiempo se tarda y cómo se ve. Las pruebas de ejemplo no hacen nada todavía, así que se ejecutan muy rápido.

Cuando todas las pruebas tienen éxito, los diamantes se vuelven verdes y muestran marcas de verificación. Puede hacer clic en el diamante gris al final de testPerformanceExample() para abrir el resultado de rendimiento:

No necesita testPerformanceExample() ni testExample() para este tutorial, así que bórrelos.

Usando XCTAssert para probar modelos

Primero, utilizará las funciones XCTAssert para probar una función central del modelo de BullsEye: ¿Calcula un objeto BullsEyeGame correctamente la puntuación de una ronda?

En BullsEyeTests.swift, añada esta línea justo debajo de la declaración import:

@testable import BullsEye

Esto da a las pruebas unitarias acceso a los tipos y funciones internas de BullsEye.

En la parte superior de la clase BullsEyeTests, añada esta propiedad:

var sut: BullsEyeGame!

Esto crea un marcador de posición para un BullsEyeGame, que es el Sistema Bajo Prueba (SUT), o el objeto que esta clase de caso de prueba se ocupa de probar.

A continuación, reemplaza el contenido de setup() con esto:

super.setUp()sut = BullsEyeGame()sut.startNewGame()

Esto crea un objeto BullsEyeGame a nivel de clase, por lo que todas las pruebas en esta clase de prueba pueden acceder a las propiedades y métodos del objeto SUT.

Aquí, también llamas al startNewGame() del juego, que inicializa el targetValue. Muchas de las pruebas utilizarán targetValue para comprobar que el juego calcula la puntuación correctamente.

Antes de que te olvides, libera tu objeto SUT en tearDown(). Reemplaza su contenido con:

sut = nilsuper.tearDown()
Nota: Es una buena práctica crear el SUT en setUp() y liberarlo en tearDown() para asegurar que cada prueba comienza con una pizarra limpia. Para más discusión, echa un vistazo al post de Jon Reid sobre el tema.

Escribiendo tu primera prueba

¡Ahora, estás listo para escribir tu primera prueba!

Añade el siguiente código al final de BullsEyeTests:

func testScoreIsComputed() { // 1. given let guess = sut.targetValue + 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

El nombre de un método de prueba siempre comienza con test, seguido de una descripción de lo que prueba.

Es una buena práctica formatear la prueba en secciones given, when y then:

  1. Given: Aquí, usted establece cualquier valor necesario. En este ejemplo, se crea un valor guess para que pueda especificar cuánto difiere de targetValue.
  2. Cuando: En esta sección, ejecutarás el código que se está probando: Llama a check(guess:).
  3. Entonces: Esta es la sección donde se afirmará el resultado que se espera con un mensaje que se imprime si la prueba falla. En este caso, sut.scoreRound debe ser igual a 95 (100 – 5).

Ejecuta la prueba haciendo clic en el icono del diamante en el canalón o en el navegador de pruebas. Esto construirá y ejecutará la aplicación, y el icono del diamante cambiará a una marca de verificación verde.

Nota: Para ver una lista completa de XCTestAssertions, vaya a Apple’s Assertions Listed by Category.

Debugging a Test

Hay un error construido en BullsEyeGame a propósito, y usted practicará encontrarlo ahora. Para ver el error en acción, crearás una prueba que reste 5 a targetValue en la sección dada, y deje todo lo demás igual.

Agrega la siguiente prueba:

func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = sut.targetValue - 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

La diferencia entre guess y targetValue sigue siendo 5, por lo que la puntuación debería seguir siendo 95.

En el navegador de puntos de interrupción, añada un punto de interrupción de la prueba. Esto detendrá la ejecución de la prueba cuando un método de prueba publique una afirmación de fallo.

Ejecute su prueba, y debería detenerse en la línea XCTAssertEqual con un fallo de prueba.

Inspeccione sut y guess en la consola de depuración:

guess es targetValue - 5 pero scoreRound es 105, no 95!

Para investigar más a fondo, utilice el proceso de depuración normal: Establezca un punto de interrupción en la sentencia when y también uno en BullsEyeGame.swift, dentro de check(guess:), donde se crea difference. A continuación, ejecute la prueba de nuevo, y pase por encima de la declaración let difference para inspeccionar el valor de difference en la aplicación:

El problema es que difference es negativo, por lo que la puntuación es 100 – (-5). Para solucionarlo, debes utilizar el valor absoluto de difference. En check(guess:), descomenta la línea correcta y borra la incorrecta.

Quita los dos puntos de interrupción, y ejecuta la prueba de nuevo para confirmar que ahora tiene éxito.

Using XCTestExpectation to Test Asynchronous Operations

Ahora que has aprendido a probar modelos y a depurar los fallos de las pruebas, es el momento de pasar a probar el código asíncrono.

Abre el proyecto HalfTunes. Utiliza URLSession para consultar la API de iTunes y descargar muestras de canciones. Suponga que quiere modificarlo para utilizar AlamoFire para las operaciones de red. Para ver si algo se rompe, deberías escribir pruebas para las operaciones de red y ejecutarlas antes y después de cambiar el código.

URLSession Los métodos son asíncronos: devuelven de inmediato, pero no terminan de ejecutarse hasta más tarde. Para probar los métodos asíncronos, utiliza XCTestExpectation para hacer que tu prueba espere a que la operación asíncrona se complete.

Las pruebas asíncronas suelen ser lentas, por lo que debes mantenerlas separadas de tus pruebas unitarias más rápidas.

Crea un nuevo objetivo de prueba unitaria llamado HalfTunesSlowTests. Abre la clase HalfTunesSlowTests, e importa el módulo de la app HalfTunes justo debajo de la declaración import existente:

@testable import HalfTunes

Todas las pruebas de esta clase utilizan el URLSession por defecto para enviar las peticiones a los servidores de Apple, así que declara un objeto sut, créalo en setUp() y libéralo en tearDown().

Reemplaza el contenido de la clase HalfTunesSlowTests con:

var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}

A continuación, añade esta prueba asíncrona:

// Asynchronous test: success fast, failure slowfunc testValidCallToiTunesGetsHTTPStatusCode200() { // given let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") // 1 let promise = expectation(description: "Status code: 200") // when let dataTask = sut.dataTask(with: url!) { data, response, error in // then if let error = error { XCTFail("Error: \(error.localizedDescription)") return } else if let statusCode = (response as? HTTPURLResponse)?.statusCode { if statusCode == 200 { // 2 promise.fulfill() } else { XCTFail("Status code: \(statusCode)") } } } dataTask.resume() // 3 wait(for: , timeout: 5)}

Esta prueba comprueba que el envío de una consulta válida a iTunes devuelve un código de estado 200. La mayor parte del código es el mismo que escribirías en la aplicación, con estas líneas adicionales:

  1. expectación(descripción:): Devuelve un objeto XCTestExpectation, almacenado en promise. El parámetro description describe lo que espera que ocurra.
  2. promise.fulfill(): Llama a esto en el cierre de la condición de éxito del manejador de finalización del método asíncrono para marcar que la expectativa se ha cumplido.
  3. wait(for:timeout:): Mantiene la prueba en ejecución hasta que se cumplan todas las expectativas, o hasta que termine el intervalo timeout, lo que ocurra primero.

Ejecuta la prueba. Si estás conectado a Internet, la prueba debería tardar aproximadamente un segundo en tener éxito después de que la aplicación se cargue en el simulador.

Fallar rápidamente

El fallo duele, pero no tiene por qué ser eterno.

Para experimentar el fallo, simplemente elimina la ‘s’ de «itunes» en la URL:

let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

Ejecuta la prueba. Falla, pero toma el intervalo de tiempo de espera completo. Esto se debe a que asumió que la solicitud siempre tendría éxito, y ahí es donde llamó a promise.fulfill(). Como la petición falló, sólo terminó cuando el tiempo de espera expiró.

Puedes mejorar esto y hacer que la prueba falle más rápido cambiando la suposición: En lugar de esperar a que la solicitud tenga éxito, espere sólo hasta que se invoque el manejador de finalización del método asíncrono. Esto sucede tan pronto como la aplicación recibe una respuesta -ya sea OK o error- del servidor, que cumple con la expectativa. Su prueba puede entonces comprobar si la solicitud tuvo éxito.

Para ver cómo funciona esto, cree una nueva prueba.

Pero primero, arregla la prueba anterior deshaciendo el cambio que hiciste en el url.
Entonces, añade la siguiente prueba a tu clase:

func testCallToiTunesCompletes() { // given let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba") let promise = expectation(description: "Completion handler invoked") var statusCode: Int? var responseError: Error? // when let dataTask = sut.dataTask(with: url!) { data, response, error in statusCode = (response as? HTTPURLResponse)?.statusCode responseError = error promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertNil(responseError) XCTAssertEqual(statusCode, 200)}

La diferencia clave es que simplemente entrando en el manejador de finalización se cumple la expectativa, y esto sólo tarda un segundo en suceder. Si la solicitud falla, las aserciones then fallan.

Ejecuta la prueba. Ahora debería tardar aproximadamente un segundo en fallar. Falla porque la solicitud falló, no porque la ejecución de la prueba excedió timeout.

Corregir el url, y luego ejecutar la prueba de nuevo para confirmar que ahora tiene éxito.

Fingir Objetos e Interacciones

Las pruebas asíncronas le dan la confianza de que su código genera la entrada correcta a una API asíncrona. También es posible que quieras probar que tu código funciona correctamente cuando recibe la entrada de un URLSession, o que actualiza correctamente la base de datos por defecto del usuario o un contenedor de iCloud.

La mayoría de las apps interactúan con objetos del sistema o de la biblioteca -objetos que no controlas- y las pruebas que interactúan con estos objetos pueden ser lentas e irrepetibles, violando dos de los principios de FIRST. En su lugar, puedes fingir las interacciones obteniendo la entrada de los stubs o actualizando los objetos mock.

Emplea la falsificación cuando tu código tenga una dependencia de un objeto del sistema o de la biblioteca. Puedes hacerlo creando un objeto falso que desempeñe ese papel e inyectando esta falsificación en tu código. Dependency Injection de Jon Reid describe varias maneras de hacer esto.

Fake Input From Stub

En esta prueba, comprobarás que el updateSearchResults(_:) de la aplicación analiza correctamente los datos descargados por la sesión, comprobando que searchResults.count es correcto. El SUT es el controlador de la vista, y falsificarás la sesión con stubs y algunos datos pre-descargados.

Ve al navegador de pruebas y añade un nuevo objetivo de prueba de unidad. Llámelo HalfTunesFakeTests. Abra HalfTunesFakeTests.swift e importa el módulo de la aplicación HalfTunes justo debajo de la declaración import:

@testable import HalfTunes

Ahora, reemplaza el contenido de la clase HalfTunesFakeTests con esto:

var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}

Esto declara el SUT, que es un SearchViewController, lo crea en setUp() y lo libera en tearDown():

Nota: El SUT es el controlador de vista, porque HalfTunes tiene un problema masivo de controlador de vista – todo el trabajo se hace en SearchViewController.rápido. Mover el código de red en un módulo separado reduciría este problema y, además, haría las pruebas más fáciles.

A continuación, necesitarás algunos datos JSON de muestra que tu sesión falsa proporcionará a tu prueba. Sólo unos pocos elementos serán suficientes, así que para limitar los resultados de la descarga en iTunes anexa &limit=3 a la cadena de URL:

https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

Copia esta URL y pégala en un navegador. Esto descarga un archivo llamado 1.txt, 1.txt.js o similar. Previsualízalo para confirmar que es un archivo JSON, luego renómbralo abbaData.json.

Ahora, vuelve a Xcode y ve al navegador de proyectos. Añade el archivo al grupo HalfTunesFakeTests.

El proyecto HalfTunes contiene el archivo de soporte DHURLSessionMock.swift. Esto define un protocolo simple llamado DHURLSession, con métodos (stubs) para crear una tarea de datos con un URL o un URLRequest. También define URLSessionMock, que se ajusta a este protocolo con inicializadores que le permiten crear un objeto mock URLSession con su elección de los datos, la respuesta y el error.

Para configurar el fake, vaya a HalfTunesFakeTests.swift y añadir lo siguiente en setUp(), después de la declaración que crea el SUT:

let testBundle = Bundle(for: type(of: self))let path = testBundle.path(forResource: "abbaData", ofType: "json")let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")let urlResponse = HTTPURLResponse( url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)sut.defaultSession = sessionMock

Esto configura los datos y la respuesta falsos y crea el objeto de sesión falso. Por último, al final, inyecta la sesión falsa en la aplicación como una propiedad de sut.

Ahora, estás listo para escribir la prueba que comprueba si llamar a updateSearchResults(_:) analiza los datos falsos. Añade la siguiente prueba:

func test_UpdateSearchResults_ParsesData() { // given let promise = expectation(description: "Status code: 200") // when XCTAssertEqual( sut.searchResults.count, 0, "searchResults should be empty before the data task runs") let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") let dataTask = sut.defaultSession.dataTask(with: url!) { data, response, error in // if HTTP request is successful, call updateSearchResults(_:) // which parses the response data into Tracks if let error = error { print(error.localizedDescription) } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { self.sut.updateSearchResults(data) } promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")}

Todavía tienes que escribir esto como una prueba asíncrona porque el stub está pretendiendo ser un método asíncrono.

La afirmación del when es que searchResults está vacío antes de que se ejecute la tarea de datos. Esto debería ser cierto, porque has creado un SUT completamente nuevo en setUp().

Los datos falsos contienen el JSON de tres objetos Track, por lo que la aserción then es que el array searchResults del controlador de vista contiene tres elementos.

Ejecuta la prueba. Debería tener éxito rápidamente porque no hay ninguna conexión de red real.

Actualización falsa de un objeto falso

La prueba anterior utilizó un stub para proporcionar la entrada de un objeto falso. A continuación, utilizarás un objeto falso para probar que tu código actualiza correctamente UserDefaults.

Reabre el proyecto BullsEye. La aplicación tiene dos estilos de juego: El usuario mueve el deslizador para que coincida con el valor objetivo o adivina el valor objetivo a partir de la posición del deslizador. Un control segmentado en la esquina inferior derecha cambia el estilo de juego y lo guarda en los valores predeterminados del usuario.

Tu siguiente prueba comprobará que la app guarda correctamente la propiedad gameStyle.

En el navegador de pruebas, haz clic en Nueva clase de prueba unitaria y nómbrala BullsEyeMockTests. Añade lo siguiente debajo de la declaración import:

@testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}

MockUserDefaults anula set(_:forKey:) para incrementar la bandera gameStyleChanged. A menudo, verá pruebas similares que establecen una variable Bool, pero el incremento de un Int le da más flexibilidad – por ejemplo, su prueba podría comprobar que el método se llama sólo una vez.

Declara el SUT y el objeto mock en BullsEyeMockTests:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

A continuación, reemplaza los setUp() y tearDown() por defecto con esto:

override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? ViewController mockUserDefaults = MockUserDefaults(suiteName: "testing") sut.defaults = mockUserDefaults}override func tearDown() { sut = nil mockUserDefaults = nil super.tearDown()}

Esto crea el SUT y el objeto mock e inyecta el objeto mock como una propiedad del SUT.

Ahora, reemplaza los dos métodos de prueba por defecto en la plantilla con esto:

func testGameStyleCanBeChanged() { // given let segmentedControl = UISegmentedControl() // when XCTAssertEqual( mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions") segmentedControl.addTarget(sut, action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged) segmentedControl.sendActions(for: .valueChanged) // then XCTAssertEqual( mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")}

La aserción when es que la bandera gameStyleChanged es 0 antes de que el método de prueba cambie el control segmentado. Por lo tanto, si la aserción then también es verdadera, significa que set(_:forKey:) fue llamado exactamente una vez.

Ejecuta la prueba; debería tener éxito.

Pruebas de UI en Xcode

Las pruebas de UI te permiten probar las interacciones con la interfaz de usuario. Las pruebas de UI funcionan encontrando los objetos de UI de una app con consultas, sintetizando eventos y enviando luego los eventos a esos objetos. La API le permite examinar las propiedades y el estado de un objeto de interfaz de usuario para compararlos con el estado esperado.

En el navegador de pruebas del proyecto BullsEye, añada un nuevo objetivo de prueba de interfaz de usuario. Compruebe que el Target a probar es BullsEye, y luego acepte el nombre por defecto BullsEyeUITests.

Abra BullsEyeUITests.swift y añada esta propiedad en la parte superior de la clase BullsEyeUITests:

var app: XCUIApplication!

En setUp(), sustituya la declaración XCUIApplication().launch() por lo siguiente:

app = XCUIApplication()app.launch()

Cambie el nombre de testExample() por testGameStyleSwitch().

Abre una nueva línea en testGameStyleSwitch() y pulsa el botón rojo de grabación en la parte inferior de la ventana del editor:

Esto abre la app en el simulador en un modo que graba tus interacciones como comandos de prueba. Una vez que la app se cargue, pulsa el segmento Slide del interruptor de estilo de juego y la etiqueta superior. A continuación, haz clic en el botón Xcode Record para detener la grabación.

Ahora tienes las siguientes tres líneas en testGameStyleSwitch():

let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()

El Recorder ha creado código para probar las mismas acciones que has probado en la app. Envía un toque al deslizador y a la etiqueta. Utilizarás esos como base para crear tu propia prueba de interfaz de usuario.
Si ves alguna otra declaración, simplemente elimínala.

La primera línea duplica la propiedad que creaste en setUp(), así que elimina esa línea. No necesitas tocar nada todavía, así que borra también .tap() al final de las líneas 2 y 3. Ahora, abre el pequeño menú junto a y selecciona segmentedControls.buttons.

Lo que te queda debería ser lo siguiente:

app.segmentedControls.buttonsapp.staticTexts

Toca cualquier otro objeto para que la grabadora te ayude a encontrar el código al que puedes acceder en tus pruebas. Ahora, sustituye esas líneas por este código para crear una sección determinada:

// givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts

Ahora que tienes nombres para los dos botones del control segmentado, y las dos posibles etiquetas superiores, añade el siguiente código a continuación:

// thenif slideButton.isSelected { XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists) typeButton.tap() XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists)} else if typeButton.isSelected { XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists) slideButton.tap() XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists)}

Esto comprueba si existe la etiqueta correcta cuando tap() en cada botón del control segmentado. Ejecute la prueba – todas las afirmaciones deben tener éxito.

Pruebas de rendimiento

De la documentación de Apple: Una prueba de rendimiento toma un bloque de código que se quiere evaluar y lo ejecuta diez veces, recogiendo el tiempo medio de ejecución y la desviación estándar de las ejecuciones. El promedio de estas mediciones individuales forman un valor para la ejecución de la prueba que luego se puede comparar con una línea de base para evaluar el éxito o el fracaso.

Es muy sencillo escribir una prueba de rendimiento: Sólo tienes que colocar el código que quieres medir en el cierre del measure().

Para ver esto en acción, vuelve a abrir el proyecto HalfTunes y, en HalfTunesFakeTests.swift, añada la siguiente prueba:

func test_StartDownload_Performance() { let track = Track( name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a") measure { self.sut.startDownload(track) }}

Ejecute la prueba y, a continuación, haga clic en el icono que aparece junto al comienzo del cierre de measure() para ver las estadísticas.

Haga clic en Establecer línea de base para establecer un tiempo de referencia. A continuación, ejecute la prueba de rendimiento de nuevo y ver el resultado – que podría ser mejor o peor que la línea de base. El botón Editar le permite restablecer la línea de base a este nuevo resultado.

Las líneas de base se almacenan por configuración de dispositivo, por lo que puede tener la misma prueba que se ejecuta en varios dispositivos diferentes, y cada uno mantiene una línea de base diferente dependiendo de la velocidad del procesador de la configuración específica, la memoria, etc.

Cada vez que realice cambios en una aplicación que podría afectar el rendimiento del método que se está probando, ejecute la prueba de rendimiento de nuevo para ver cómo se compara con la línea de base.

Cobertura de código

La herramienta de cobertura de código te dice qué código de la aplicación está siendo ejecutado por tus pruebas, para que sepas qué partes del código de la aplicación no han sido probadas (todavía).

Para habilitar la cobertura de código, edite la acción Test del esquema y marque la casilla Gather coverage for en la pestaña Options:

Ejecute todas las pruebas (Comando-U), luego abra el navegador Report (Comando-9). Seleccione Cobertura en el elemento superior de la lista:

Haga clic en el triángulo de divulgación para ver la lista de funciones y cierres en SearchViewController.swift:

Desplácese hacia abajo hasta updateSearchResults(_:) para ver que la cobertura es del 87,9%.

Haga clic en el botón de flecha de esta función para abrir el archivo fuente de la función. Al pasar el ratón por encima de las anotaciones de cobertura en la barra lateral derecha, las secciones de código se resaltan en verde o en rojo:

Las anotaciones de cobertura muestran cuántas veces llega una prueba a cada sección de código; las secciones que no fueron llamadas se resaltan en rojo. Como era de esperar, el bucle for se ejecutó 3 veces, pero no se ejecutó nada en las rutas de error.

Para aumentar la cobertura de esta función, podrías duplicar abbaData.json, y luego editarlo para que provoque los diferentes errores. Por ejemplo, cambie "results" por "result" para una prueba que golpea print("Results key not found in dictionary").

¿Cobertura al 100%?

¿Cuánto debe esforzarse por alcanzar el 100% de cobertura del código? Busca en Google «100% de cobertura de pruebas unitarias» y encontrarás una serie de argumentos a favor y en contra, junto con el debate sobre la propia definición de «100% de cobertura». Los argumentos en contra dicen que el último 10-15% no merece el esfuerzo. Los argumentos a favor dicen que el último 10-15% es el más importante, porque es muy difícil de probar. Busca en Google «hard to unit test bad design» para encontrar argumentos persuasivos de que el código no testeable es un signo de problemas de diseño más profundos.

¿A dónde ir desde aquí?

Ahora tienes algunas grandes herramientas para usar en la escritura de pruebas para tus proyectos. Espero que este tutorial de pruebas unitarias y pruebas de interfaz de usuario de iOS le haya dado la confianza para probar todas las cosas.

Puede descargar la versión completa del proyecto utilizando el botón de descarga de materiales en la parte superior o inferior de este tutorial. Continuar el desarrollo de sus habilidades mediante la adición de pruebas adicionales de su propio.

Aquí hay algunos recursos para el estudio adicional:

  • Hay varios videos WWDC sobre el tema de las pruebas. Dos buenos de la WWDC17 son: Engineering for Testability y Testing Tips & Tricks.
  • El siguiente paso es la automatización: Integración continua y entrega continua. Empieza con Apple’s Automating the Test Process with Xcode Server y xcodebuild, y el artículo de Wikipedia sobre entrega continua, que se basa en la experiencia de ThoughtWorks.
  • Si ya tienes una aplicación pero no has escrito pruebas para ella todavía, puede que quieras consultar Working Effectively with Legacy Code de Michael Feathers, ¡porque el código sin pruebas es código heredado!
  • Los archivos de aplicaciones de ejemplo de Quality Coding de Jon Reid son geniales para aprender más sobre Test Driven Development.

raywenderlich.com Weekly

El boletín de raywenderlich.com es la forma más fácil de estar al día en todo lo que necesitas saber como desarrollador móvil.

¡Obtén un resumen semanal de nuestros tutoriales y cursos, y recibe un curso gratuito en profundidad por correo electrónico como bono!

Valoración media

4,7/5

Añade una valoración para este contenido

Inicia sesión para añadir una valoración

90 valoraciones

Deja una respuesta

Tu dirección de correo electrónico no será publicada.