Swift Testing #5: Parametrizando pruebas con arguments

Published: (December 4, 2025 at 05:27 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Parametrizando una sola condición

En ocasiones, se necesita verificar el funcionamiento de cierto código que recibe un argumento, usando varios valores. La forma tradicional de hacerlo sería construyendo una colección y luego iterando sobre ella.

@Test("Even Value")
func even() {
  let values = [2, 8, 50]
  values.forEach { value in
    #expect(value.isMultiple(of: 2))
  }
}

Aunque la implementación anterior funciona, tiene algunas desventajas:

  • En el reporte solo se muestra el resultado global de la ejecución de la prueba. Es decir: si se ejecutan 1000 escenarios y solo uno de ellos falla, simplemente se muestra la prueba como fallida.
  • No se puede repetir la prueba con una condición específica, sino que se tienen que ejecutar todas las iteraciones.

Ante esta problemática, las pruebas @Test de Swift Testing pueden ser parametrizadas con una colección. De esta forma, se repite la prueba con cada elemento de la colección, uno a la vez. Con esta aproximación, el reporte de pruebas muestra todos los escenarios por separado y se puede ejecutar una prueba con una condición específica sin tener que ejecutar todas.

Para parametrizar una prueba solo hay que poner un parámetro al método de la prueba y agregar la colección de valores como el argumento arguments de @Test. (ver documentación). Gracias a estos cambios, ahora en el reporte de pruebas aparece cada condición discriminada, como si fuera una prueba independiente.

@Test("Even Value", arguments: [2, 8, 50])
func even(value: Int) {
  #expect(value.isMultiple(of: 2))
}

Probar combinaciones de argumentos

Como extensión del escenario anterior, queremos probar un sistema combinando las condiciones de dos colecciones. Manualmente lo haríamos anidando dos ciclos:

@Test("Even Value")
func even() {
  let values1 = [2, 8, 50]
  let values2 = [3, 6, 9]
  values1.forEach { value1 in
    values2.forEach { value2 in
      let multiplication = value1 * value2
      #expect(multiplication.isMultiple(of: 2))
    }
  }
}

Sin embargo, podemos conseguir el mismo resultado aprovechando la sobrecarga del arguments que recibe dos colecciones de datos (ver documentación).

@Test("Even Value", arguments: [2, 8, 50], [3, 6, 9])
func even(first: Int, second: Int) {
  let multiplication = first * second
  #expect(multiplication.isMultiple(of: 2))
}

En el sistema anterior de prueba + condiciones tendríamos un total de nueve escenarios. Sin embargo, ¿qué pasa si no queremos probar todas las combinaciones sino solo una a una, usando el mismo índice?

Condiciones emparejadas por tupla

Para no combinar colecciones debemos tener una única colección de entrada con N condiciones (con N > 1). Cada condición viene dada por una tupla de M valores, que se desestructura en M parámetros en el método de la prueba.

@Test("Even Value", arguments: [(2, 3, true), (3, 5, false)])
func even(first: Int, second: Int, expectedResult: Bool) {
  let multiplication = first * second
  #expect(multiplication.isMultiple(of: 2) == expectedResult)
}

Debido a la naturaleza de combinación uno‑a‑uno, se puede usar el operador zip para crear una colección de tipo Zip2Sequence a partir de dos colecciones. Esto permite identificar por separado a las colecciones y, por ende, hacer la prueba más legible:

@Test("Even Value", arguments: [zip([2, 3], [3, 4])])
func even(first: Int, second: Int, expectedResult: Bool) {
  let multiplication = first * second
  #expect(multiplication.isMultiple(of: 2) == expectedResult)
}

El problema de la implementación anterior es que @Test únicamente permite combinar dos colecciones (ver documentación). Por esta razón, si es necesario combinar más de dos colecciones, lo mejor es crear una única colección compuesta de tuplas de M elementos (con M > 2).

Condiciones emparejadas por estructura

Usar una tupla para emparejar valores de una misma condición es la solución trivial al problema de combinar más de dos colecciones, sin embargo, también se puede conseguir un resultado semejante usando una estructura cuyos atributos sean los valores que componen la condición de una prueba.

struct TestModel {
  let first: Int
  let second: Int
  let result: Int
}

@Test(arguments: [
  TestModel(first: 1, second: 2, result: 3),
  TestModel(first: 2, second: 3, result: 5),
  TestModel(first: 3, second: 4, result: 7),
])
func basic(testModel: TestModel) {
  let result = testModel.first + testModel.second
  #expect(result == testModel.result)
}

Esta aproximación, además de ser más legible en la construcción de la prueba, permite agregar una capa extra de claridad si la estructura conforma el protocolo CustomTestStringConvertible para proporcionar una descripción testDescription.

struct TestModel: CustomTestStringConvertible {
  let first: Int
  let second: Int
  let result: Int
  var testDescription: String {
    "\(first) + \(second) debería ser igual a \(result)"
  }
}

@Test(arguments: [
  TestModel(first: 1, second: 2, result: 3),
  TestModel(first: 2, second: 3, result: 5),
  TestModel(first: 3, second: 4, result: 7),
])
func basic(testModel: TestModel) {
  let result = testModel.first + testModel.second
  #expect(result == testModel.result)
}

En este caso, el reporte mostraría cada caso con su descripción legible, por ejemplo:

1 + 2 debería ser igual a 3
2 + 3 debería ser igual a 5
3 + 4 debería ser igual a 7

Bibliografía

  • Video “Mastering Swift Testing: Eliminate Duplicate Tests with Parameterized Testing” (Swift and Tips), aquí.
  • Documentación sobre Swift Testing, aquí.
  • Documentación “Implementing parameterized tests”, aquí.
  • Artículo “Swift Parameterized Testing”, aquí.
  • Artículo “Introducing Swift Testing. Parameterized Tests.”, aquí.
Back to Blog

Related posts

Read more »