Skip to main content

Introducción a la automatización de pruebas

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/testing-kt
Si quieres seguir el código del tutorial puedes comenzar desde este punto

Si tienes gh instalado, puedes obtener el código haciendo:

gh repo clone r8vnhill/testing-kt
cd testing-kt || exit
git checkout base

Si quieres tener tu propia copia del código, puedes hacer un fork del repositorio y clonarlo desde tu cuenta de GitHub.

gh repo fork r8vnhill/testing-kt
cd testing-kt || exit
git checkout --track origin/base
Cambia la propiedad group en gradle.properties
Recuerda cambiar la propiedad .group en el archivo gradle.properties por tu nombre de dominio.

Be lazy...

Puedes ejecutar el siguiente comando para crear el módulo

./gradlew setupIntroModule

Mientras se crean los archivos necesarios, puedes leer el código para saber qué está pasando.

Primero definiremos una tarea reutilizable para esta unidad, no te dejes intimidar por la cantidad de código, es solo una plantilla para crear módulos en el proyecto que ya viene incluido en el repositorio. Sin embargo, comprender cómo funciona puede ser útil como práctica y repaso sobre tareas de Gradle.

abstract class ModuleSetupTask @Inject constructor(
private val layout: ProjectLayout
) : DefaultTask() {
init {
group = "setup"
}
}
¿Qué acabamos de hacer?

Esta clase define una tarea personalizada que hereda de DefaultTask, como es habitual en Gradle.

Justo después del nombre de la clase, aparece una sección que empieza con @Inject constructor(...). Esto indica que la tarea necesita ciertos datos para funcionar, y que Gradle se encargará de proporcionarlos automáticamente. A esto se le llama inyección de dependencias.

En este caso, lo que Gradle entrega es una instancia de ProjectLayout, un objeto que ofrece una forma segura de acceder a carpetas y archivos dentro del proyecto.

Usar ProjectLayout en lugar de acceder directamente con project.file(...) es importante porque mejora la compatibilidad con el configuration cache, una característica de Gradle que permite acelerar las compilaciones.

Finalmente, la línea group = "setup" simplemente indica que esta tarea forma parte del grupo de tareas de configuración, lo cual ayuda a organizarla mejor cuando se listan las tareas con gradle tasks.

Con la tarea definida, podemos registrarla de la siguiente manera:

settings.gradle.kts
import tasks.ModuleSetupTask

tasks.register<ModuleSetupTask>("setupIntroModule") {
description = "Creates the base module and files for the testing introductory lesson"
module.set("intro")

doLast {
createFiles(
"intro",
test to "MyStringSpecTest.kt",
test to "MyFunSpecTest.kt",
test to "MyFreeSpecTest.kt",
test to "MyWordSpecTest.kt",
test to "MyBehaviorSpecTest.kt",
test to "MyFeatureSpecTest.kt",
)
}
}

Preocúpate de que el plugin intro esté aplicado en el archivo build.gradle.kts de tu proyecto.

./gradlew setupIntroModule

Preocúpate de que el nuevo módulo esté incluido en el archivo settings.gradle.kts.

Imagina que lanzas una actualización crítica y, sin saberlo, rompes una funcionalidad clave.
La automatización de pruebas previene estos errores al verificar continuamente que el software funcione correctamente en distintas condiciones.
En esta lección, aprenderás cómo Kotest y TDD pueden ayudarte a escribir pruebas eficientes y confiables para tu código en Kotlin.

El objetivo del testing es proporcionar una validación continua de que el software:

  • Cumple con los requisitos: Asegura que todas las funcionalidades esperadas estén presentes y se comporten correctamente.
  • Previene la regresión: Verifica que los cambios recientes no rompan las funcionalidades ya existentes.
  • Identifica errores: Facilita la detección de errores o problemas de rendimiento que puedan afectar la experiencia de lx usuarix final.

Además de verificar la funcionalidad, el testing mejora la confianza en el código, permite una evolución más rápida del software y asegura que sea más mantenible a largo plazo.

🔍 Test-Driven Development (TDD)

TDD es una metodología de desarrollo de software que prioriza la creación de pruebas antes de implementar cualquier funcionalidad. El flujo típico de TDD consiste en los siguientes pasos:

  1. Escribir casos de prueba: Antes de escribir el código, los requisitos del software se traducen en casos de prueba que describen cómo debería comportarse la funcionalidad.
  2. Desarrollar el código: Se implementa el código necesario para pasar los casos de prueba.
  3. Refactorizar: Una vez que las pruebas son exitosas, el código puede ser mejorado o refactorizado sin miedo a romper la funcionalidad, ya que los tests ya están en su lugar.

Este enfoque asegura que el desarrollo esté alineado con los requisitos desde el principio y ayuda a capturar errores en etapas tempranas del desarrollo, lo que mejora la calidad del software y reduce el costo de corregir fallos más adelante.

✅ Kotest: Un framework flexible para pruebas en Kotlin

Kotest es un framework de testing para Kotlin que ofrece una API flexible y expresiva para la creación de pruebas unitarias, de integración y más. Entre sus principales características se encuentran:

  • API Declarativa: Kotest permite escribir pruebas de forma clara y legible, facilitando el mantenimiento y la comprensión de los casos de prueba.
  • Data-Driven Testing (DDT): Permite ejecutar un mismo caso de prueba con diferentes conjuntos de datos, mejorando la cobertura de pruebas y asegurando que las funciones se comporten correctamente en distintos escenarios.
  • Property-Based Testing (PBT): Genera automáticamente casos de prueba basados en propiedades definidas, verificando que las funciones cumplan con condiciones específicas bajo un amplio rango de entradas.
  • Matchers Flexibles: Kotest incluye una amplia gama de matchers para realizar aserciones en pruebas, desde comparaciones simples hasta validaciones más complejas como excepciones o estructuras anidadas.
  • Soporte para Coroutines: Kotest está diseñado para trabajar de manera fluida con las coroutines de Kotlin, lo que permite escribir pruebas asíncronas de manera sencilla y eficiente.
  • Integración con JUnit: Kotest se integra sin problemas con JUnit, lo que facilita la adopción en proyectos existentes y permite el uso de herramientas y configuraciones ya familiares.

Kotest se integra fácilmente con otros frameworks y bibliotecas populares en el ecosistema de Kotlin, como Ktor, Spring, y Arrow, haciendo de él una herramienta robusta y versátil para cualquier tipo de prueba.

📦 Incluyendo Kotest en un Proyecto

Para comenzar a utilizar Kotest en nuestro proyecto, lo primero que haremos es agregar las dependencias necesarias en el catálogo de versiones.

gradle/libs.versions.toml
[versions]
kotest-framework = "5.9.1"

[libraries]
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest-framework" }

[bundles]
kotest = ["kotest-runner-junit5"]
¿Qué acabamos de hacer?

En este archivo, definimos la versión de Kotest que utilizaremos (kotest-framework) y la dependencia de JUnit 5 para Kotest (kotest-runner-junit5). Luego, agrupamos estas dependencias en un bundle llamado kotest, que nos permitirá añadir más dependencias de Kotest de manera sencilla en el futuro.

Ahora podemos aplicar este bundle a nuestro proyecto de Gradle:

build.gradle.kts
val kotestBundle = libs.bundles.kotest

subprojects {
dependencies {
implementation(kotestBundle)
}
}
¿Qué acabamos de hacer?

En este bloque de código, aplicamos el bundle de Kotest a todos los subproyectos de nuestro proyecto de Gradle, lo que nos permite utilizar Kotest en cualquier módulo de nuestro proyecto.

📝 Escribiendo Pruebas con Kotest

Kotest permite escribir pruebas utilizando diferentes estilos; aquí mostramos varios ejemplos:

type-fundamentals/src/test/kotlin/com/github/username/basics/MyTest.kt
package com.github.username.basics

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class MyStringSpecTest : StringSpec({
"String length should be equal to the number of characters" {
val str = "Hello, World!"
str.length shouldBe 13
}
})

StringSpec permite escribir pruebas de manera declarativa utilizando cadenas como descripciones.

¿Qué acabamos de hacer?
  1. Creamos una clase de pruebas MyTest que extiende una clase base de Kotest. Cada estilo (StringSpec, FunSpec, FreeSpec, WordSpec, BehaviorSpec, etc.) ofrece diferentes formas de estructurar las pruebas, permitiéndote elegir el que más te acomode.
  2. Definimos un bloque de prueba que describe el comportamiento esperado de una cadena de texto.
  3. Escribimos una aserción (shouldBe) para verificar que la longitud de la cadena sea la esperada.

Finalmente, podemos ejecutar las pruebas con Gradle desde la terminal:

./gradlew test
EstiloUso recomendado
StringSpecPruebas rápidas y concisas.
FunSpecSimilar a JUnit, más estructurado.
FreeSpecPermite anidamiento flexible de pruebas.
WordSpecMejor para pruebas estilo BDD.
BehaviorSpecDefinir pruebas en términos de Given-When-Then.
FeatureSpecOrganizar pruebas por características del sistema.
Plugin de IntelliJ IDEA

El IDE recomendado para trabajar con Kotest es IntelliJ IDEA, que ofrece un plugin oficial para ejecutar y depurar pruebas de Kotest de manera sencilla. Puedes instalarlo desde el Marketplace de IntelliJ IDEA.

Puedes encontrar más información en la documentación oficial de Kotest.

🏁 Conclusiones

A lo largo de esta lección, exploramos la importancia de la automatización de pruebas en el desarrollo de software y cómo herramientas como Kotest y TDD pueden mejorar la calidad y confiabilidad del código.

🔑 Puntos clave

Automatización de pruebas: Permite detectar errores antes de que lleguen a producción, asegurando que el software funcione correctamente en distintas condiciones.

TDD (Test-Driven Development): Fomenta la escritura de pruebas antes del código, asegurando que cada funcionalidad cumpla con los requisitos desde su concepción.

Kotest: Un framework flexible y expresivo para Kotlin que ofrece múltiples estilos de pruebas, soporte para data-driven testing y property-based testing, así como integración con JUnit y otras herramientas populares.

Estrategias de testing: Aprendimos a escribir pruebas con diferentes enfoques (BDD, unitarias, de integración, etc.), organizando el código de manera modular y reutilizable.

Configuración en Gradle: Implementamos Kotest en un proyecto de Kotlin, gestionando sus dependencias mediante un bundle de Gradle para facilitar su mantenimiento.

Bibliografías Recomendadas