Swift Testing (Parte 3 de 3)1

Introducción a Swift Testing

2

Organiza Pruebas con Swift Testing usando Suites y Tags

3

Evaluando pruebas con Swift Testing

Evaluando pruebas con Swift Testing

Es necesario utilizar Xcode 16 Beta o superior para trabajar con Swift Testing.

Cuando escribimos pruebas, siempre debemos evaluar algún tipo de resultado, ya sea que se arroje un error o que una variable contenga un valor específico. Para realizar esto, Swift Testing provee varias herramientas, dos de estas son:

  • #require para evaluar requerimientos antes de realizar operaciones en las pruebas.
  • #expect para indicar cuál es el resultado esperado dentro de la prueba.

Si no tienes experiencia con Swift Testing, te recomendamos leer los primeros artículos de esta serie:

A continuación, veremos ejemplos de cada una de estas herramientas. Si deseas seguir el tutorial e ir practicando, debes crear un nuevo proyecto con Xcode 16 y escribir el siguiente código en un nuevo archivo:

// 1
final class NotesManager {
private(set) var notes = [String]()
func add(_ note: String) {
notes.append(note)
}
// 2
func removeLast() throws {
guard !notes.isEmpty else {
throw NotesError.noNotesToRemove
}
notes.removeLast()
}
// 3
func getLastNote() -> String? {
return notes.last
}
}
// 4
enum NotesError: Error {
case noNotesToRemove
}

En este código:

  1. Definimos NotesManager, el cual es una clase simple que nos permite agregar notas usando add(:_) y remover la última nota agregada al arreglo usando removeLast().
  2. Nota que removeLast() puede arrojar un error si ejecutamos este método y no hay ninguna nota agregada.
  3. getLastNote() retorna la última nota agregada, en realidad el último String agregado a notes.
  4. Definimos NotesError que contiene noNotesToRemove, que es el error que arrojamos en removeLast().

Evaluar resultados usando #expect

Las expectations permiten verificar los valores o respuestas deseadas en las pruebas unitarias. Swift Testing cambia la forma en que estamos acostumbrados a usar esta herramienta. Para evaluar resultados, usamos la macro #expect(throws:_:sourceLocation:performing:).

Por ejemplo, si quieres escribir una prueba que verifique que un valor es mayor de 100, debes escribir el siguiente código:

@Test func valueShouldBeHigherThan100() {
...
#expect(value > 100, "Custom Comment")
}

La macro #expect accepta un comentario, el cual en caso de fallar la prueba será mostrado en los reportes.

Agreguemos unas cuantas pruebas al código que vimos en un principio. Si creaste un proyecto e indicaste que usarías Swift Testing para las pruebas, debes tener un directorio cuyo nombre termina "Tests". En nuestro caso, el proyecto se llama MyApp, así que dentro del directorio MyAppTests, abriremos MyAppTests.swift, y sustiuiremos todo el código existente con este:

// 1
import Testing
@testable import MyApp
// 2
@Suite
struct NotesManagerTests {
// 3
private let manager = NotesManager()
// 4
@Test func addNote() {
manager.add("New Note")
manager.add("New Note 2")
#expect(manager.notes.count == 2)
}
// 5
@Test func removeNote() throws {
manager.add("New Note")
try manager.removeLast()
#expect(manager.notes.isEmpty)
}
}

Veamos que hace este código:

  1. Importamos Testing para poder hacer uso de Swift Testing y luego importamos MyApp usando @testable para tener acceso al código con nivel internal de acceso.
  2. Creamos una Suite de pruebas llamada NotesManagerTests. Si quieres saber más de Suite y cómo organizar pruebas, puedes leer este artículo Organiza Pruebas con Swift Testing usando Suites y Tags.
  3. Usamos una referencia a NotesManager para todas las pruebas, así que la declaramos de manera global para la Suite. Es importante tener en cuenta que será inicializada nuevamente para cada prueba unitaria.
  4. Escribimos la prueba para verificar que cuando agregamos una nota se refleja en el conteo. Para esto, usamos la macro #expect, luego de agregar dos notas.
  5. Creamos una segunda prueba para verificar que las notas se eliminan correctamente. Como removeLast() puede arrojar un error, necesitamos usa try y marcar la prueba como throws. Gracias a esto último, si ocurre un error, el resultado mostrará la prueba como fallida.

Con esto hemos visto cómo podemos evaluar diferentes condiciones de manera simple y robusta gracias a la macro #expect. Sin embargo, en la segunda prueba removeNote(), podemos hacer algo aún mejor.

Evaluando errores

Usando #expect(throws:) podemos escribir pruebas unitarias que verifican que un error es arrojado. Por ejemplo:

@Test func removeItemError() throws {
let model = Model()
#expect(throws: ItemError.modelEmpty) {
try model.remove(at: 2)
}
}

Con este código estamos validando que al ejecutar model.remove(at: 2), se arroja el error ItemError.modelEmpty. Podemos validar el tipo de error exacto, o usar por ejemplo ItemError.self o (any Error).self si queremos que la verificación sea más genérica.

Si deseáramos hacer verificaciones más complejas cuando estamos haciendo pruebas de que se arroja un error, podemos utilizar otra versión de expect:

@Test func removeItemError() throws {
let model = Model()
#expect {
try model.remove(at: 2)
} throws: { error in
// Validaciones avanzadas
}
}

Usando la macro expect de esta forma, pasamos dos closures: uno con la operación a ejecutar y otro donde recibimos el error arrojado y retornamos true o false según corresponda.

Como ya sabemos cómo crear pruebas unitarias de errores más robustas, creemos una prueba para nuestra Suite. Debajo de removeNote(), agrega el siguiente código:

@Test func removeLastThrowsError() throws {
#expect(throws: Error.self, "No notes to remove") {
try manager.removeLast()
}
}

Con este código estamos evaluando que se arroja un error si ejecutamos removeLast() cuando no hay notas agregadas. Esto puede ser suficiente en muchos casos, pero gracias a Swift 6.0 y Typed Throws, podemos hacer algo mejor. Reemplaza el código anterior con este:

@Test func removeLastThrowsError() throws {
#expect(throws: NotesError.self, "No notes to remove") {
try manager.removeLast()
}
}

Este código es muy parecido al anterior, pero en vez de evaluar Error.self usamos un tipo de error específico NotesError.self, lo cual hace la prueba más específica y expresiva.

Pero espera, aún hay más. Podemos indicar exactamente qué error esperamos. Puedes reemplazar NotesError.self con NotesError.noNotesToRemove en el código anterior. Así expresamos de manera más clara la intención de la prueba.

Evaluar requerimientos con #require

Cuando escribimos pruebas, puede ser muy útil evaluar que el estado de una variable u objeto esté en un estado específico antes de continuar. Para estos casos, podemos usar la macro #require. Veamos un ejemplo. Debajo de la prueba removeLastThrowsError(), agrega el siguiente código:

@Test func getLastNote() throws {
let newNote = "New Note"
manager.add(newNote)
let lastNote = try #require(manager.getLastNote(), "Last note cannot be nil")
#expect(lastNote == newNote)
}

Con esta prueba queremos validar que getLastNote() retorna la última nota agregada. Este método puede retornar nil, así que hacemos uso de #require para validar que debe retornar un valor diferente a nil. Debemos usar try, ya que si no se cumple la condición, se arrojará un error y la prueba se mostrará como fallida. Al igual que #expect podemos pasar un comentario como parámetro.

Si el requerimiento se cumple, usamos #expect para evaluar que el valor retornado es el adecuado.

Si tienes experiencia con XCTest, probablemente has usado XCTUnwrap para realizar estas evaluaciones de requerimientos.

Conclusión

Evaluar resultados y requerimientos es muy sencillo gracias a Swift Testing y las macros #expect y #require. Evaluar los errores que son retornados es muy fácil gracias a la capacidad de #expect de evaluar errores arrojados por el código.

Hasta ahora hemos visto algunas de las funcionalidades y beneficios de Swift Testing. Sin embargo, todavía falta bastante que explorar. ¡Hasta una nueva entrega!

Swift Testing (Parte 3 de 3)1

Introducción a Swift Testing

2

Organiza Pruebas con Swift Testing usando Suites y Tags

3

Evaluando pruebas con Swift Testing

Comparte este artículo

Subscríbete a nuestro Newsletter

Mantente al día en el mundo de las aplicaciones móviles con nuestro blog especializado.

Artículos semanales

Todas las semanas artículos nuevos sobre el mundo de las aplicaciones móviles.

No spam

No te enviaremos spam, solo contenido de calidad. Puedes darte de baja cuando quieras.

Contenido de calidad

Nada de contenido generado de manera automática usando ChatGPT.

Recomendaciones

Tips indispensables sobre mejores prácticas y metodologías.

© 2024 AsyncLearn