Ir al contenido principal
Versión: 5.8.x

Reducción

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

En las pruebas basadas en propiedades, el caso de fallo detectado inicialmente puede contener mucha complejidad que realmente causa el fallo. La reducción es el mecanismo mediante el cual un marco de pruebas basadas en propiedades simplifica los casos fallidos para identificar el caso reproducible mínimo. En Kotest, la forma en que se reducen los casos fallidos de los generadores se define mediante implementaciones de la interfaz Shrinker. Los generadores incorporados generalmente tienen un Shrinker predeterminado definido por el marco, mientras que los generadores personalizados pueden recibir una implementación personalizada de Shrinker.

Reducción para generadores incorporados

Los generadores incorporados (ver Lista de generadores) tienen un Shrinker predeterminado definido por el marco. Una función de reducción toma como entrada el valor que falló en la prueba y devuelve una lista de nuevos valores sobre los que Kotest puede aplicar la prueba. El comportamiento exacto depende del tipo de dato. Por ejemplo, una cadena podría reducirse eliminando el primer o último carácter, mientras que para enteros podríamos decrementar o reducir a la mitad el valor. Además, se define un comportamiento de reducción para casos límite como una cadena vacía o el entero 0. La reducción se realiza cuando falla una prueba que utiliza dicho generador.

Arb.positiveInt().checkAll { i ->
calculateProperty(i) shouldBe true
}

Si la prueba falla para una de las entradas generadas, se muestra el resultado de la reducción:

Property test failed for inputs

0) 1792716902

Caused by io.kotest.assertions.AssertionFailedError: expected:<1792716902> but was:<0> at
PropertyBasedTest$1$1$3$1.invokeSuspend(PropertyBasedTest.kt:54)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
io.kotest.property.internal.ProptestKt$proptest$3$2.invokeSuspend(proptest.kt:45)

Attempting to shrink arg 1792716902
Shrink #1: 1 pass
Shrink #2: 597572300 fail
Shrink #3: 199190766 fail
Shrink #4: 66396922 fail
Shrink #5: 22132307 fail
Shrink #6: 7377435 fail
Shrink #7: 2459145 fail

[...]

Shrink #999: 29948 pass
Shrink #1000: 44922 pass
Shrink #1001: 59896 pass
Shrink #1002: 89839 fail
Shrink result (after 1002 shrinks) => 89839

Caused by io.kotest.assertions.AssertionFailedError: expected:<89839> but was:<0> at
PropertyBasedTest$1$1$3$1.invokeSuspend(PropertyBasedTest.kt:54)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
io.kotest.property.internal.ShrinkfnsKt$shrinkfn$1$1$smallestA$1.invokeSuspend(shrinkfns.kt:19)

Por defecto, Kotest realizará 1000 reducciones. Este comportamiento es configurable. Por ejemplo, si deseas continuar reduciendo sin límites:

Arb.positiveInt().checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Unbounded)) { i ->
calculateProperty(i) shouldBe true
}

Reducción para generadores personalizados

Los generadores personalizados no tienen un Shrinker definido por Kotest. En su lugar, se pueden implementar Shrinkers personalizados. A continuación se muestra un ejemplo donde el Shrinker devuelve coordenadas adyacentes al valor original.

data class Coordinate(val x: Int, val y: Int)

class CoordinateTest : FunSpec({
context("Coordinate Transformations") {
// Shrinker takes the four neighbouring coordinates
val coordinateShrinker = Shrinker<Coordinate> { c ->
listOf(
Coordinate(c.x - 1, c.y),
Coordinate(c.x, c.y - 1),
Coordinate(c.x + 1, c.y),
Coordinate(c.x, c.y + 1),
)
}
val coordinateArb = arbitrary(coordinateShrinker) {
Coordinate(Arb.nonNegativeInt().bind(), Arb.nonNegativeInt().bind())
}

test("Coordinates are always positive after transformation") {
coordinateArb.checkAll {
transform(it).x shouldBeGreaterThanOrEqualTo 0
transform(it).y shouldBeGreaterThanOrEqualTo 0
}
}
}
})