Caso de estudio: Concatenación de cadenas
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/property-based-testing-kt
Puedes ejecutar el siguiente comando para crear el módulo
./gradlew setupStrCatModule
Mientras se crean los archivos necesarios, puedes leer el código para saber qué está pasando.
import tasks.ModuleSetupTask
tasks.register<ModuleSetupTask>("setupStrCatModule") {
description = "Creates the base module and files for the string concatenation properties lesson"
module.set("str-cat")
doLast {
createFiles(
packageName = "string",
test to "StringConcatenationLengthTest.kt",
test to "StringConcatenationMonoidTest.kt",
)
}
}
Preocúpate de que el plugin str-cat
esté aplicado en el archivo build.gradle.kts
de tu proyecto.
./gradlew setupStrCatModule
Preocúpate de que el nuevo módulo esté incluido en el archivo settings.gradle.kts
.
Supongamos que queremos asegurarnos de que la concatenación de strings en nuestro proyecto funciona de forma correcta. En lugar de probar casos específicos con entradas puntuales, podemos buscar definir propiedades generales que deberían cumplirse sin importar los valores de entrada. Esto nos lleva a preguntarnos: ¿qué propiedades fundamentales debe respetar la concatenación de strings?
📦 Integrando Property-Based Testing en Nuestro Proyecto
Para comenzar a utilizar Property-Based Testing en tu proyecto, lo primero que debes hacer es agregar las dependencias necesarias en tu catálogo de versiones. Esto incluye las bibliotecas de Kotest para pruebas basadas en propiedades.
Añade las dependencias correspondientes en el archivo versions.toml
:
- Código esencial
- Código completo
[libraries]
kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" }
[bundles]
kotest = ["kotest-runner-junit5", "kotest-datatest", "kotest-property"]
[versions]
kotlin = "2.1.10"
testing = "1.0.0"
detekt = "1.23.8"
kotest-framework = "5.9.1"
[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest-framework" }
kotest-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest-framework" }
kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest-framework" }
[plugins]
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
[bundles]
kotest = ["kotest-runner-junit5", "kotest-datatest", "kotest-property"]
Con este bundle configurado, ya no es necesario añadir las dependencias de Kotest manualmente en el bloque dependencies
de tu archivo de configuración build.gradle.kts
. Simplemente, puedes referenciar el bundle kotest
y asegurarte de tener todas las herramientas necesarias para realizar pruebas basadas en propiedades.
Con este enfoque, el archivo de dependencias se mantiene limpio y organizado, facilitando futuras actualizaciones o la adición de herramientas de prueba.
🛠️ Implementación del Test
Queremos asegurarnos de que al concatenar dos strings, la longitud del resultado siempre sea igual a la suma de sus longitudes. Esto es una propiedad universal de la concatenación de cadenas, y podemos verificarlo con Property-Based Testing de la siguiente manera:
- Código esencial
- Código completo
checkAll(Arb.string(), Arb.string()) { s1, s2 ->
(s1 + s2) shouldHaveLength (s1.length + s2.length)
}
package com.github.username.strings
import io.kotest.core.spec.style.FreeSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.string
import io.kotest.property.checkAll
import io.kotest.matchers.string.shouldHaveLength
class StringConcatenationLengthTest : FreeSpec({
"A String" - {
"when concatenated" - {
"should have the sum of the lengths of the original strings" {
checkAll(Arb.string(), Arb.string()) { s1, s2 ->
(s1 + s2) shouldHaveLength (s1.length + s2.length)
}
}
}
}
})
- Usamos
checkAll
para realizar testing basado en propiedades, probando con diferentes combinaciones de inputs generados automáticamente. Arb.string()
es un generador arbitrario que proporciona strings aleatorios, incluyendo casos de borde como el string vacío.- La propiedad que estamos verificando es que la longitud de la concatenación de dos strings debe ser igual a la suma de las longitudes de los strings originales. Esto se mantiene sin importar los valores específicos de los strings generados.
Esta técnica permite probar de forma exhaustiva la propiedad del código, garantizando que funcione correctamente bajo una variedad de condiciones.
🔍 Probando más propiedades
Además de la propiedad de longitud que ya hemos probado, podemos definir otras propiedades fundamentales sobre los strings, que deberían mantenerse sin importar la implementación. Aquí es útil introducir el concepto de monoide.
📖 ¿Qué es un Monoide?
Un monoide es una estructura algebraica formada por:
- Un conjunto
- Una operación binaria que combina dos elementos de
- Un elemento neutro que no altera el valor de ningún elemento al ser combinado con él
Dicha estructura debe cumplir con las siguientes propiedades:
- Asociatividad: Para todo , , y en , se cumple que .
- Elemento Neutro: Para todo en , se cumple que .
En el contexto de strings, la operación de concatenación cumple con estas propiedades, y el string vacío actúa como el elemento neutro. Diremos entonces que los strings forman un monoide bajo la operación de concatenación.
🧪 Definiendo Propiedades del Monoide para Strings
Podemos verificar que la concatenación de strings sigue las leyes de los monoides: la identidad (izquierda y derecha) y la asociatividad.
- Código esencial
- Código completo
"left identity" {
checkAll(Arb.string()) { s ->
("" + s) shouldBe s
}
}
"right identity" {
checkAll(Arb.string()) { s ->
(s + "") shouldBe s
}
}
"associativity" {
checkAll(Arb.string(), Arb.string(), Arb.string()) { s1, s2, s3 ->
((s1 + s2) + s3) shouldBe (s1 + (s2 + s3))
}
}
package com.github.username.strings
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.kotest.property.Arb
import io.kotest.property.arbitrary.string
import io.kotest.property.checkAll
class StringConcatenationMonoidTest : FreeSpec({
"String concatenation" - {
"should follow monoid laws" - {
"left identity" {
checkAll(Arb.string()) { s ->
("" + s) shouldBe s
}
}
"right identity" {
checkAll(Arb.string()) { s ->
(s + "") shouldBe s
}
}
"associativity" {
checkAll(Arb.string(), Arb.string(), Arb.string()) { s1, s2, s3 ->
((s1 + s2) + s3) shouldBe (s1 + (s2 + s3))
}
}
}
}
})
- Identidad por la izquierda: Concatenar el string vacío por la izquierda no cambia el valor original del string.
- Identidad por la derecha: Concatenar el string vacío por la derecha tampoco altera el valor original.
- Asociatividad: La concatenación de strings es asociativa, es decir, el orden en que se agrupan las operaciones de concatenación no afecta el resultado.
Este enfoque nos permite probar formalmente propiedades importantes de los strings y asegurar que el comportamiento esperado se mantenga bajo cualquier circunstancia.
📌 Conclusiones
En esta lección, exploramos cómo Property-Based Testing nos permite verificar propiedades fundamentales de la concatenación de strings, asegurando su comportamiento correcto sin depender de casos de prueba específicos.
🔑 Puntos clave
- Testing basado en propiedades: Probamos principios generales en lugar de valores concretos, lo que nos permite detectar más errores de manera automática.
- Concatenación y longitud: Verificamos que la longitud de una concatenación siempre sea igual a la suma de las longitudes individuales.
- Monoides y concatenación: Demostramos que los strings forman un monoide, cumpliendo con las propiedades de asociatividad e identidad.
Al aplicar estos principios, aseguramos que la concatenación de strings sea correcta en cualquier situación, sin importar los valores de entrada.
Bibliografías Recomendadas
- 📚 "8. Property-based testing". (2021). Marco Vermeulen, Rúnar Bjarnason, Paul Chiusano, en Functional Programming in Kotlin, (pp. 150–176.) Manning Publications Co. LLC.
Bibliografías Adicionales
- 📚 "Estructuras Algebráicas como Ejemplos de 2-Gráficas". (2012). Zbigniew Oziewicz & Fernando Raymundo Velázquez Quesada, en Gráficas De Gráficas: Introducción a Teoría De Categorías, (pp. 29–34.)