Poco a poco con Go y TDD: package, funciones, bucles, arrays y cobertura de test

Todo funcionando, $GOPATH, HelloWorld,… ahora vamos a hincar el diente de verdad a Go con TDD.
Crearemos una pequeña calculadora con la que aprenderemos a hacer un package, funciones, tocaremos un poco los arrays y además nos servirá para mejorar nuestras skills de TDD.

Empezando por los test

Para empezar con la calculadora, vamos a crear un package llamado calculator, así que creamos un directorio con ese mismo nombre (calculator) y nuestro primer fichero será calculator_test.go (TDD a tope).

*nota: solo puede existir un package por directorio

El código de ese primer test será algo así:

package calculator

import "testing"

func TestAdder(t *testing.T) {
    sum := Add(2, 2)
    expected := 4

    if sum != expected {
        t.Errorf("expected '%d' but got '%d'", expected, sum)
    }
}

Si nos hemos fijado un poco en el mensaje de error, vemos que ahora utilizamos %d en lugar de %s porque lo que queremos es imprimir por consola un número.

Si ejecutamos go test nos aparecerá que no esta definida la función Add

$ go test
# github.com/jeslopcru/golang-examples/02-calculator
./calculator_test.go:8:9: undefined: Add

Compilation finished with exit code 2
7997258366_7e77afa99d_z
Atardecer – Paula Gonzalez Perez

Mínimo código para que funcione

Ahora lo minimo que tenemos que hacer para que pase el test es crear el fichero calculator.go y la función Add, algo así:

package calculator

func Add(x int, y int) int {
    return 4;
}

Si ejecutamos esto, tendremos nuestro test en verde. Siendo puristas, vamos por el camino correcto, ahora añadiríamos otro caso y lo haríamos pasar para obtener la función.

$ go test
=== RUN TestAdder
--- PASS: TestAdder (0.00s)
PASS

 

No vamos a extendernos mucho con esto. Al el test quedaría algo así:

func TestAdder(t *testing.T) {
    sum := Add(2, 2)
    expected := 4

    if sum != expected {
        t.Errorf("expected '%d' but got '%d'", expected, sum)
    }

    otherSum := Add(3, 2)
    otherExpected := 5

    if otherSum != otherExpected {
        t.Errorf("expected '%d' but got '%d'", otherExpected, otherSum)
    }
}

y el código de producción sería:

func Add(x int, y int) int {
    return x + y
}

el refactor lo es todo

Ahora que ya tenemos los tests en verde con el código de producción listo, nos queda el siguiente paso: Refactor
Parece que aquí tenemos poco código que tocar, pero todo no es el código también la documentación.

Bola extra: Leyendo la documentación de los paquetes

Para ver la documentación de los paquetes que tenemos descargados, abrimos un terminal y escribimos:

$ godoc -http=:6060

Ahora entrando en [localhost:6060/pkg/] (localhost:6060/pkg/) veremos toda la lista de paquetes descargados y podemos buscar el nuestro: http://localhost:6060/pkg/github.com/jeslopcru/golang-examples/02-calculator/

Esta pagina corresponde al proyecto https://github.com/jeslopcru/golang-examples 

Como vemos esa página está desierta, así que vamos a añadir un poco de documentación.

Lo primero será añadir documentación a la función. Con solo poner un comentario ya se añadirá aquí.

// GIVEN two integers WHEN call Add function THEN result is sum of them
func Add(x int, y int) int {
    return x + y
}

Si además queremos añadir un ejemplo, lo único que tenemos que hacer es añadir un test como este:

func ExampleAdd() {
    sum := Add(2, 4)
    fmt.Println(sum)
    // Output: 6
}

Notemos que tenemos un comentario con la salida esperada, si ejecutamos los tests tendremos algo como esto:

╰─$ go test -v
=== RUN   TestAdder
--- PASS: TestAdder (0.00s)
=== RUN   ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS

Si eliminamos el comentario // Output: 6 el segundo test no se ejecutará.

Añadiremos comentarios antes de las funciones para describir su funcionamiento. Si además queremos enriquecer esa documentación podemos hacer ejemplos como ExampleAdd. Eso hará que la página de documentación sea más amigable y encima si publicamos la librería haremos el camino más fácil para poder utilizarla. De momento nosotros con saber que existen los test de tipo example nos vale.

Nuevo caso: Sumando varios números

Vamos a seguir aprendiendo Go y ahora es la hora de los arrays. Para ello vamos a implementar una nueva funcionalidad de nuestra calculadora.
Vamos a sumar todos los números de un array. En principio, empezaremos con un array de 5 elementos, por simplificar.

Test de suma multiple

Como somos fieles seguidores de TDD, lo primero será crear un nuevo test como este:

func TestAddMultiple(t *testing.T) {

    numbers := [5]int{1, 2, 3, 4, 5}

    got := AddMultiple(numbers)
    expected := 15

    if expected != got {
        t.Errorf("got %d expected %d given, %v", got, expected, numbers)
    }
}

Así que al ejecutarlo tenemos:

$ go test
# github.com/jeslopcru/golang-examples/02-calculator
./calculator_test.go:27:9: undefined: AddMultiple

Ahora vamos a aprender un poco sobre los array en Go. En Go los arrays se declaran e inicializan de 2 maneras: con longitud fija,

//con una longitud fija
numbers := [3]int{1, 2, 3}
//sin declarar la longitud
numbers := [...]int{1, 2, 3, 4, 5}

Otra de las cosas nuevas que estamos aprendiendo con este test, es la manera de imprimir por pantalla arrays. Para hacerlo utilizamos %v que es el comportamiento por defecto y nos sirve perfectamente para imprimir arrays.

Implementando la suma múltiple

Haciendo el mínimo código para que pase.

//GIVEN an array WHEN call AddMultiple function THEN result is sum of elements
func AddMultiple(numberList [5]int) int {
    result := 0
    for i := 0; i < 5; i++ {
        result = Add(result, numberList[i])
    }
    return result
}

Ejecutamos los test y como vemos que todo está en verde, podemos hacer un commit.

$ go test
=== RUN TestAdder
--- PASS: TestAdder (0.00s)
=== RUN TestAddMultiple
--- PASS: TestAddMultiple (0.00s)
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS

Refactorizando y aprendiendo range

Vamos a intentar usar toda la potencia de Go utilizando range

//GIVEN an array WHEN call AddMultiple function THEN result is sum of elements
func AddMultiple(numberList [5]int) int {
    result := 0
    for _, number := range numberList {
        result = Add(result, number)
    }
    return result
}

Con range podemos iterar a través del array de una manera mucho más limpia.
además con “_” estamos ignorando el indice del array.

Bueno, comprobamos que seguimos estando en verde y  así ya podemos hacer otro commit con un ammend para dejar el código perfecto.

Sumando array multiples

Hemos empezado con una funcionalidad muy sencilla de un array de 5 elementos. Ahora vamos a extrapolar la solución a un array de n elementos.
Para ello vamos a añadir un nuevo case a nuestro test así:

func TestAddMultiple(t *testing.T) {

    t.Run("array of 5 numbers", func(t *testing.T) {
        numbers := [5]int{1, 2, 3, 4, 5}

        got := AddMultiple(numbers)
        expected := 15

        if expected != got {
            t.Errorf("got %d expected %d given, %v", got, expected, numbers)
        }
    })

    t.Run("array of any syze", func(t *testing.T) {
        numbers := []int{1, 2}

        got := AddMultiple(numbers)
        expected := 3

        if expected != got {
            t.Errorf("got %d expected %d given, %v", got, expected, numbers)
        }
    })
}

En un primer momento, GoLand nos indica un error de type int, porque espera un array de 5 elementos.

Si cambiamos la firma del método AddMultiple lo que nos pasará será que el otro test falla, así que lo podemos solucionar de 2 maneras:

  • Cambiando la API, es decir, cambiamos la firma para que acepte arrays de cualquier numero de elementos y por tanto cambiamos el test que falla (el de fijo a 5).
  • Creando un nuevo método.

Vamos a optar por la primera opción. Así que el código quedaría así:

//GIVEN an array WHEN call AddMultiple function THEN result is sum of elements
func AddMultiple(numberList []int) int {
    result := 0
    for _, number := range numberList {
        result = Add(result, number)
    }
    return result
}

Y en los test solo tenemos que cambiar la declaración de los arrays para que no sean fijos (numbers := []int{1, 2, 3, 4, 5})

Bola Extra: cobertura de test

Lo importante no es tener muchos tests, cuantos más test más difícil de mantener será la suite.
Debemos mantener un equilibrio y fijarnos sobre todo en la calidad de los mismos.

En nuestro caso, mantener las pruebas duplicadas es redundante. En un principio estas pruebas nos aportaron valor, pero ahora que tenemos el código funcionando… estas pruebas son redundantes.
Del mismo modo, es importante saber que parte del código no está cubierto de test, para ello tenemos https://blog.golang.org/cover

Para utilizarlo:

╰─$ go test -cover
PASS
coverage: 100.0% of statements
ok      github.com/jeslopcru/golang-examples/02-calculator      0.007s

Si eliminamos las pruebas redundantes seguiremos obteniendo el 100%. Aunque el objetivo nunca debe ser conseguir el 100% de cobertura.
El objetivo es tener unas pruebas robustas y confiables. Siendo estrictos con TDD, llegaremos al 100% de cobertura fácilmente y sin esfuerzo, otra cuestión es si esa cobertura nos proporciona la confianza suficiente como para modificar el código.

Conclusiones

Seguimos aprendiendo Go. Ahora ya sabemos crear un package, funciones que hacen cálculos matemáticos. Todo tiene tests y además hemos aprendido a añadir documentación con ejemplos.
Nuestro flujo de TDD sigue su curso. Hemos aprendido como trabajar con arrays, hacer bucles con y cobertura de test.
Y la lección más importante que no debemos olvidar es que el objetivo es tener unas pruebas robustas y confiables. Siendo estrictos con TDD, llegaremos al 100% de cobertura fácilmente y sin esfuerzo.

Y ahora, ¿seremos capaces de añadir una función para restar?

Anuncios

Un comentario sobre “Poco a poco con Go y TDD: package, funciones, bucles, arrays y cobertura de test

Comenta la entrada

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s