Generadores personalizados
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Para crear tu propio generador para un tipo T, simplemente creas una instancia de Arb<T> o Exhaustive<T>.
Arbitrario
Al crear un arbitrario personalizado, podemos usar el constructor arbitrary que acepta una lambda que debe devolver el tipo que estamos generando.
El parámetro de esta lambda es un RandomSource que contiene la semilla y la instancia Random. Normalmente deberíamos
usar el RandomSource proporcionado si necesitamos acceso a una instancia kotlin.Random, ya que esta instancia habrá sido inicializada por el framework para permitir pruebas repetibles.
Por ejemplo, aquí tenemos un arb personalizado que genera un entero aleatorio entre 3 y 6 usando el constructor arbitrary.
val sillyArb = arbitrary { rs: RandomSource ->
rs.random.nextInt(3..6)
}
Además del parámetro RandomSource, la lambda de construcción arbitraria también proporciona el contexto ArbitraryBuilderSyntax que podemos aprovechar para componer otros arbitrarios al construir el nuestro.
Por ejemplo, aquí tenemos un Arbitrary que soporta una clase personalizada llamada Person, delegando en un arbitrario String y otro Int.
data class Person(val name: String, val age: Int)
val personArb = arbitrary {
val name = Arb.string(10..12).bind()
val age = Arb.int(21, 150).bind()
Person(name, age)
}
Ten en cuenta que esta sintaxis no compone automáticamente los reductores de los arbitrarios internos.
Cuando una prueba de propiedades que utiliza este generador falla, no se producirá ninguna minimización a menos que proporciones explícitamente un Shrinker personalizado.
Consulta Minimización para detalles sobre cómo implementar un reductor personalizado, y la siguiente sección para enfoques que ofrecen minimización automática.
Arb.bind — recomendado para clases de datos
Al construir un Arb personalizado para clases de datos o tipos de registro, Arb.bind es el enfoque recomendado.
Combina automáticamente los reductores de cada arbitrario componente, lo que significa que ante un fallo el framework
intentará minimizar cada campo independientemente hacia un caso de fallo mínimo.
data class Person(val name: String, val age: Int)
val personArb: Arb<Person> = Arb.bind(
Arb.string(10..12),
Arb.int(21, 150),
::Person
)
Comparación de los tres enfoques
Existen tres métodos comunes para construir un Arb personalizado para tipos compuestos, cada uno con comportamientos de minimización diferentes:
| Approach | Shrinking |
|---|---|
Arb.bind(arbA, arbB, ...) { ... } | Full — each component is shrunk independently. Recommended for data classes. |
arbA.flatMap { ... } | Partial — only the outermost arbitrary's shrinker applies. Inner values are held fixed during shrinking. |
arbitrary { arbA.bind(); arbB.bind() } | None — no automatic shrinking unless a custom Shrinker is provided. |
Dado que Arb.bind produce los mejores resultados de minimización sin esfuerzo adicional, prefierelo sobre flatMap o el DSL del constructor arbitrary
siempre que estés componiendo arbitrarios independientes en un tipo de registro.
Utiliza el constructor arbitrary cuando necesites lógica imperativa o dependencias entre campos, y combínalo con un Shrinker personalizado
si la minimización es importante para tus pruebas.
Exhaustivo
Al crear un exhaustivo personalizado, podemos usar la función de extensión exhaustive() sobre una Lista. ¡Realmente no hay más que eso!
val singleDigitPrimes = listOf(2,3,5,7).exhaustive()
class PropertyExample: FreeSpec({
"testing single digit primes" {
checkAll(singleDigitPrimes) { prime ->
isPrime(prime) shouldBe true
isPrime(prime * prime) shouldBe false
}
}
})