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 ellas.
@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 de 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 repetir una prueba con unas condiciones específicas, sin tener que ejecutarlas todas.
Para parametrizar una prueba solo hay que poner un parámetro al método de la prueba. Luego, agregar la colección de valores que definen las condiciones de las pruebas 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. Por otro lado, ahora se puede ejecutar la prueba de cada condición por separado.
@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 como se muestra a continuación.
@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 = values1 * values2
#expect(multiplication.isMultiple(of: 2))
}
}
}
Sin embargo, podemos conseguir el mismo resultado aprovechando la sobrecarga del "trait" 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 de las colecciones sino solo uno-a-uno, dado 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 viene destructurada en M parámetros en el método de la prueba.
En el siguiente ejemplo se va a ejecutar la prueba con dos tuplas de tres elementos. Observar que la prueba tiene tres parámetros, y se ejecuta dos veces porque tenemos dos condiciones.
@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 más legible a la prueba. Por ejemplo:
@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))
}
El problema de la implementación anterior es que @Test únicamente permite combinar dos colecciones (ver documentación). Por esta razón, en caso de que sea necesario combinar más de dos colecciones, lo mejor es crear solo una 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 a la condición de una prueba. Por ejemplo:
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)
}
Este tipo de aproximación, además de ser más legible en términos de construcción de la prueba, permite agregar una capa más de legibilidad si hacemos que la estructura conforme el protocolo CustomTestStringConvertible para agregar una descripción testDescription.
Por ejemplo, considerar la siguiente estructura TestModel junto con la prueba basic(testModel: TestModel).
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 lo siguiente:
Definitivamente es mucho más legible que el escenario esté descrito como "1 + 2 debería ser igual a 3", que simplemente "70, 100, 170".
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í.
- Artículo "Cómo escribir pruebas parametrizadas con Swift Testing", aquí.

Top comments (0)