Pruebas basadas en propiedades en Go: rompemos el código más rápido de lo que lo escribes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Cuando las pruebas unitarias mienten y los errores persisten



Escribiste 20 pruebas unitarias. Todas en verde. El CI pasa. Estás contento. Y luego — un error en producción. ¿Te suena? El escenario clásico: solo probaste los casos que se te ocurrieron. Y la realidad, como para fastidiar, presenta combinaciones que ni siquiera imaginaste.



En Go existe un enfoque potente pero subestimado: pruebas basadas en propiedades (o property-based testing). En lugar de verificar entradas y salidas específicas, describes las propiedades que debe cumplir una función, y el framework genera cientos de datos de prueba aleatorios. Si se viola una propiedad — has encontrado un error.



En este artículo, veremos cómo implementar pruebas basadas en propiedades en Go con la biblioteca gopter, escribiremos ejemplos reales y observaremos qué errores se pueden detectar. ¡Allá vamos!



¿Qué son las pruebas basadas en propiedades y por qué son necesarias?



Imagina que pruebas una función de ordenamiento. Una prueba unitaria típica:



func TestSort(t *testing.T) {    input := []int{3, 1, 2}    result := sort(input)    expected := []int{1, 2, 3}    if !reflect.DeepEqual(result, expected) {        t.Errorf("got %v, want %v", result, expected)    }}


Bien, funciona. Pero ¿qué pasa si el array está vacío? ¿Y si todos los elementos son iguales? ¿Y si hay números negativos? Puedes escribir 10 pruebas más, pero igual algo se te escapará.



Las pruebas basadas en propiedades resuelven este problema de raíz: formulas invariantes — propiedades que deben ser verdaderas para cualquier entrada válida. Por ejemplo, para el ordenamiento:



  • La longitud del array ordenado es igual a la del array original.
  • Cada elemento del array original está presente en el ordenado (y viceversa).
  • El array está ordenado de forma ascendente.


El framework (por ejemplo, gopter o el integrado testing/quick) genera datos de entrada aleatorios y verifica que todas las propiedades se cumplan. Si hay un error — te enterarás, incluso sin saber dónde buscar.



Bibliotecas de Go para pruebas basadas en propiedades



En el ecosistema de Go hay varias herramientas:



  • testing/quick — paquete integrado, minimalista, pero con capacidades limitadas.
  • gopter — biblioteca potente con generadores, reducción (minimización de datos de entrada) y configuraciones flexibles. Recomendamos esta.
  • rapid — alternativa moderna, más eficiente.


Usaremos gopter por su mejor documentación y comunidad. Instalación:



go get github.com/leanovate/gopter


Escribimos nuestra primera prueba basada en propiedades en Go



Supongamos que tenemos una función Reverse que invierte una cadena:



func Reverse(s string) string {    runes := []rune(s)    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {        runes[i], runes[j] = runes[j], runes[i]    }    return string(runes)}


¿Qué propiedad podemos verificar? Si invertimos la cadena dos veces, deberíamos obtener la original:



import (    "testing"    "github.com/leanovate/gopter"    "github.com/leanovate/gopter/gen")

func TestReverseTwice(t *testing.T) { properties := gopter.NewProperties(nil)

properties.Property("reverse(reverse(s)) == s", prop.ForAll( func(s string) bool { return Reverse(Reverse(s)) == s }, gen.AnyString(), ))

properties.TestingRun(t)}


Ejecuta la prueba — y gopter generará 100 cadenas aleatorias, verificando la propiedad. Si todo está bien — la prueba estará en verde. Si no — obtendrás un contraejemplo (la cadena más pequeña con la que falló la prueba).



Bo

Blogs

Book Recommendations