V trae el testing integrado en el propio compilador, sin librerías externas ni configuración previa: basta con nombrar el fichero de una forma concreta y usar assert. Es una filosofía muy próxima a la de go test, que también viene de serie con el toolchain, y bastante alejada de ecosistemas como el de JavaScript, donde elegir framework de test es una decisión en sí misma.
Convención de nombres
Cualquier función de test tiene que vivir en un fichero que termine en _test.v, y el nombre de la función tiene que empezar por test_:
// suma_test.v
module main
fn suma(a int, b int) int {
return a + b
}
fn test_suma_basica() {
assert suma(2, 3) == 5
}
fn test_suma_con_negativos() {
assert suma(-1, 1) == 0
}
Es la misma idea que en Go (sufijo _test.go, prefijo Test en la función), con la diferencia de que en V el sufijo va también en minúscula y sin separación de palabras especial.
Ejecutar los tests
v suma_test.v // un fichero concreto v test mimódulo // un módulo entero v test . // el directorio actual v test . -stats // con estadísticas de tiempo por test
Assert con mensaje personalizado
fn test_validacion() {
i := 10
assert i * 2 - 45 < 75 + 10, 'fallo la aserción para i: ${i}'
}
Cuando un assert falla, V te señala la línea exacta y, si le has dado un mensaje, lo añade al informe. No hay que instalar nada para tener aserciones legibles, algo que en otros lenguajes suele requerir una librería de terceros tipo testify en Go o chai en JavaScript.
Asserts que no abortan el test
Por defecto, un assert fallido corta la ejecución del test en ese punto. Si prefieres que el test siga corriendo y acumule todos los fallos, existe el atributo [assert_continues]:
@[assert_continues]
fn test_multiples_validaciones(ii int) {
assert ii == 2
assert ii > 0 // esta línea se evalúa aunque la anterior falle
}
Funciones de ciclo de vida
Dos funciones especiales se ejecutan automáticamente si las defines en el fichero de test: testsuite_begin, antes de todos los tests del fichero, y testsuite_end, después:
fn testsuite_begin() {
println('preparando datos de prueba')
}
fn testsuite_end() {
println('limpiando datos de prueba')
}
Es el equivalente a TestMain en Go o a los hooks beforeAll/afterAll de frameworks como Jest, pero sin necesidad de importar nada: el compilador reconoce esos nombres de función directamente.
Tests con funciones que devuelven error
Como vimos en el artículo sobre manejo de errores de esta serie, muchas funciones en V devuelven !Type. Un test también puede declarar ese tipo de retorno, y si el error se propaga sin capturar, el test se marca como fallido automáticamente:
fn test_conversion() ! {
assert strconv.atoi('1')! == 1
}
No hace falta un bloque or explícito dentro del test: si atoi falla, el ! final propaga el error hacia el runner de tests, que lo interpreta como un fallo.
El paralelismo con Go es directo en casi todos los puntos: convención de nombres de fichero, comando integrado en el toolchain, sin dependencias externas para lo básico. Si quieres verlo con más profundidad en Go, con subtests y table-driven tests, tenemos esta introducción y esta guía avanzada con benchmarks y testify. En el próximo artículo de la serie vemos cómo V gestiona paquetes y dependencias con vpm y v.mod.
Imagen: Pexels / Jakub Zerdzicki
