Escritura de pruebas
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Al aprovechar las características del lenguaje disponibles en Kotlin, Kotest ofrece un enfoque más potente y a la vez sencillo para definir pruebas. Quedaron atrás los días en que las pruebas debían ser métodos definidos en archivos Java.
En Kotest, una prueba es esencialmente una función que contiene tu lógica de prueba. Cualquier sentencia de aserción (matchers en la nomenclatura de Kotest) invocada en esta función que lance una excepción será interceptada por el framework y usada para marcar esa prueba como fallida o exitosa.
Las funciones de prueba se definen mediante el DSL de Kotest, que ofrece varias formas de crear y anidar estas funciones. Se accede al DSL creando una clase que extiende de una superclase que implementa un estilo de prueba particular.
Por ejemplo, usando el estilo Fun Spec, creamos funciones de prueba mediante la palabra clave test, proporcionando un nombre y la función de prueba real.
class MyFirstTestClass : FunSpec({
test("my first test") {
1 + 2 shouldBe 3
}
})
Las pruebas deben definirse dentro de un bloque init {} o una lambda del cuerpo de la clase, como en el ejemplo anterior.
Pruebas anidadas
La mayoría de estilos permiten anidar pruebas. La sintaxis concreta varía según el estilo, pero esencialmente consiste en usar palabras clave diferentes para las pruebas externas.
Por ejemplo, en Describe Spec, las pruebas externas se crean con la función describe y las internas con it. Los desarrolladores de JavaScript y Ruby reconocerán instantáneamente este estilo, ya que es común en frameworks de pruebas para esos lenguajes.
class NestedTestExamples : DescribeSpec({
describe("an outer test") {
it("an inner test") {
1 + 2 shouldBe 3
}
it("an inner test too!") {
3 + 4 shouldBe 7
}
}
})
En la nomenclatura de Kotest, las pruebas que pueden contener otras pruebas se llaman test containers (contenedores de prueba), y las pruebas terminales o nodos hoja se llaman test cases (casos de prueba). Ambos pueden contener lógica de prueba y aserciones.
Pruebas dinámicas
Dado que las pruebas son simplemente funciones, se evalúan en tiempo de ejecución.
Este enfoque ofrece una gran ventaja: las pruebas pueden crearse dinámicamente. A diferencia de los frameworks tradicionales de pruebas JVM, donde las pruebas siempre son métodos declarados en tiempo de compilación, Kotest puede añadir pruebas condicionalmente en tiempo de ejecución.
Por ejemplo, podríamos añadir pruebas basadas en elementos de una lista.
class DynamicTests : FunSpec({
listOf(
"sam",
"pam",
"tim",
).forEach {
test("$it should be a three letter name") {
it.shouldHaveLength(3)
}
}
})
Esto resultaría en la creación de tres pruebas en tiempo de ejecución. Sería equivalente a escribir:
class DynamicTests : FunSpec({
test("sam should be a three letter name") {
"sam".shouldHaveLength(3)
}
test("pam should be a three letter name") {
"pam".shouldHaveLength(3)
}
test("tim should be a three letter name") {
"tim".shouldHaveLength(3)
}
})
Callbacks de ciclo de vida
Kotest proporciona varios callbacks que se invocan en distintos puntos del ciclo de vida de una prueba. Son útiles para restablecer estados, configurar y liberar recursos que una prueba pueda usar, entre otros casos.
Como se mencionó anteriormente, las funciones de prueba en Kotest se etiquetan como test containers o test cases, además de que la clase contenedora se etiqueta como spec (especificación). Podemos registrar callbacks que se invocan antes o después de cualquier función de prueba, contenedor, caso de prueba o de la propia especificación.
Para registrar un callback, simplemente pasamos una función a uno de los métodos de callback.
Por ejemplo, podemos añadir un callback antes y después de cualquier test case usando un literal de función:
class Callbacks : FunSpec({
beforeEach {
println("Hello from $it")
}
test("sam should be a three letter name") {
"sam".shouldHaveLength(3)
}
afterEach {
println("Goodbye from $it")
}
})
Nota: el orden de los callbacks en el archivo no es importante. Por ejemplo, un bloque afterEach puede colocarse primero en la clase si así se desea.
Si queremos extraer código común, podemos crear una función con nombre y reutilizarla en múltiples archivos. Por ejemplo, si quisiéramos restablecer una base de datos antes de cada prueba en más de un archivo, podríamos hacer esto:
val resetDatabase: BeforeTest = {
// truncate all tables here
}
class ReusableCallbacks : FunSpec({
beforeTest(resetDatabase)
test("this test will have a sparkling clean database!") {
// test logic here
}
})
Para detalles de todos los callbacks y cuándo se invocan, consulta aquí y aquí.