Hace tiempo que quiero hincar el diente a Go, el lenguaje de Google. Empecé jugando un poco con exercism.io y creo que es hora de empezar Go con test. Vamos a intentar explicar como testear un ejemplo sencillo con Go, para más adelante evolucionar ese ejemplo con usando un enfoque de TDD. La idea es conocer como funciona el lenguaje: declarar variables, crear funciones, hacer test, usar constantes…
Empezando
Lo primero es instalar Go, aquí tenemos un tutorial que hice sobre como instalar go en Mac.Vamos a utilizar el IDE de IntelliJ Goland que es lo más cómodo si ya estamos acostumbrados a PHPStorm.
Go es un poco especial para el workspace. En Mac por defecto se define dentro de nuestro usuario en una carpeta go y ahí irá añadiendo todas las dependencias, paquetes y movidas. Lo ideal es que tengamos estas variables de entorno definidas para que todo funcione bien.
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
export GOBIN=$GOPATH/bin
Por otro lado gestionar dependencias en go…. vamos lo que viene siendo trabajar con librerias, versiones y tal es un poco «especial» en este lenguaje. Así que para empezar lo ideal es crear esta ruta para empezar a trabajar «~/go/src/github.com//golang-examples» donde username es tu usuario de github.
Recapitulando: Go instalado, variables de entorno creadas, carpeta del proyecto «golang-examples» de ejemplo lista.
Hello World
Tradicionalmente el primer ejemplo que usamos para empezar con un lenguaje es escribir por pantalla «Hello World».
Todo el código del proyecto podemos encontrarlo dentro de este repositorio de GitHub https://github.com/jeslopcru/golang-examples/tree/master
Así que allá vamos:
- Creamos una carpeta hello dentro de nuestro proyecto «golang-examples»
- ahí creamos el archivo hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
Lo guardamos y para ver si todo funciona ha ejecutamos
$ go run hello/hello.go
Hello, world
Usando Goland, tendremos abierto el proyecto «golang-examples», hacemos clic derecho sobre el fichero y en el menu contextual hacemos clic en Run
Cuando escribimos un programa en Go, debe tener un package main. Este paquete debe tener también una func main dentro de él.
Testeando
Lo mejor es separar en funciones nuestro código para no estar imprimiendo por pantalla en cada test. Digamos que imprimir por pantalla sería un «side-effects» no deseado. Por lo que vamos a dejar nuestro código así:
En hello.go
package main
import "fmt"
func main() {
fmt.Println(HelloWorld())
}
func HelloWorld() string {
return "Hello, world"
}
Hemos aprendido como se declaran funciones, que en las funciones debemos especificar el tipo de salida. Sabemos imprimir por pantalla y vamos a tener en cuenta un regla para el futuro Las funciones públicas deben comenzar por mayúscula
Ahora que está separado vamos a por el test.
Creando el test (requisitos)
Lo primero es crear un fichero que termine en _test.go y como regla generar lo mejor es crearlo justo al lado del que tenemos. Así que creamos el fichero hello_test.go
Los test deben empezar por la palabra Test.
Además la funciones de test solo tienen un parámetro de entrada t *testing.T
Este sería nuestro fichero de test:
En hello_test.go
package main
import "testing"
func TestHelloWorld(t *testing.T) {
expected := "Hello, world"
got := HelloWorld()
if expected != got {
t.Errorf("got '%s' expected '%s'", got, expected)
}
}
Con esto ya tenemos el test listo. Para ejecutarlo:
$ go test
PASS
ok github.com/jeslopcru/golang-examples/hello 0.006s
Para ejecutarlo en Goland, tan solo es necesario clic derecho sobre el fichero de test y luego run.
Hemos aprendido a ejecutar los tests, también hemos aprendido a Declarar variables con
name := value
. Del mismo modo hemos usadot.Errorf
para indicar cuando hay un error en el test.
Todo funcionando, ¿empezamos con TDD?
Tenemos todo funcionando, un pequeño fichero para test, código de producción, sabemos cómo ejecutarlo. Ahora vamos a dar un pequeño paso más. Vamos a hacer que este HelloWorld
no saludes si le pasamos un nombre, sino que siga diciendo Hello world
.
Modificando el test
Empezaremos modificando el test que tenemos por algo como esto:
func TestHelloWorld(t *testing.T) {
expected := "Hello, Jesús"
got := HelloWorld("Jesús")
if expected != got {
t.Errorf("got '%s' expected '%s'", got, expected)
}
}
Ya Goland nos indica que tenemos un error y cuando intentamos ejecutar los tests pasa lo siguiente:
# github.com/jeslopcru/golang-examples/hello
./hello_test.go:7:19: too many arguments in call to HelloWorld
have (string)
want ()
Modificando código
Así que lo que vamos a hacer es modificar la función HelloWorld
para que pase el Test.
En hello.go
func HelloWorld(name string) string {
return "Hello, " + name
}
Hemos aprendido a como utilizar parámetros de entrada en una función. también sabemos concatenar
Refactor
Es un paso un poco rápido, pero vemos que "Hello, "
siempre se repite. Así que vamos a extraerlo como constante
En hello.go
const HELLO = "Hello, "
func HelloWorld(name string) string {
return HELLO + name
}
Hemos aprendido como se crean constantes, con la palabra reservada CONST
Volviendo al inicio
Vamos a intentar tener la funcionalidad inicial, es decir: Si ponemos nuestro nombre queremos que no salude, pero en cambio, si el parámetro de entrada está vacío queremos que diga ‘Hello, World’
Podemos crear un nuevo test, o crear distintos casos dentro del mismo. Como hemos venido a aprender allá vamos:
En hello_test.go
func TestHelloWorld(t *testing.T) {
t.Run("when say hello with a name then response is hello name", func(t *testing.T) {
expected := "Hello, Jesús"
got := HelloWorld("Jesús")
if expected != got {
t.Errorf("got '%s' expected '%s'", got, expected)
}
})
t.Run("when say hello with empty name then response is say hello world", func(t *testing.T) {
expected := "Hello, World"
got := HelloWorld("")
if got != expected {
t.Errorf("got '%s' expected '%s'", got, expected)
}
})
}
Hemos aprendido a crear casos de test dentro de un test para que estos sean más legibles.
Al ejecutarlo tendremos un error como éste:
=== RUN TestHelloWorld/when_say_hello_with_empty_name_then_response_is_say_hello_world
--- FAIL: TestHelloWorld/when_say_hello_with_empty_name_then_response_is_say_hello_world (0.00s)
hello_test.go:21: got 'Hello, ' expected 'Hello, World'
Así que para arreglarlo modificamos el código de producción:
En hello.go
func HelloWorld(name string) string {
if name == "" {
name = "World"
}
return HELLO + name
}
Hemos aprendido a hacer comparaciones de una cadena vacía.
¿Eso es todo? La verdad es que no. El paso de refactoring en TDD no es solo para el código de producción sino tambien para los test. Vamos a eliminar el código repetido de los test.
En hello_test.go nos quedará algo así:
package main
import "testing"
func TestHelloWorld(t *testing.T) {
t.Run("when say hello with a name then response is hello name", func(t *testing.T) {
expected := "Hello, Jesús"
got := HelloWorld("Jesús")
assertIsCorrectMessage(got, expected, t)
})
t.Run("when say hello with empty name then response is say hello world", func(t *testing.T) {
expected := "Hello, World"
got := HelloWorld("")
assertIsCorrectMessage(got, expected, t)
})
}
func assertIsCorrectMessage(got string, expected string, t *testing.T) {
if got != expected {
t.Errorf("got '%s' expected '%s'", got, expected)
}
}
Hemos aprendido a extraer un método
Podemos hacerlo un poco más funcional así:
En hello_test.go
package main
import "testing"
func TestHelloWorld(t *testing.T) {
assertIsCorrectMessage := func(got string, expected string, t *testing.T) {
t.Helper()
if got != expected {
t.Errorf("got '%s' expected '%s'", got, expected)
}
}
t.Run("when say hello with a name then response is hello name", func(t *testing.T) {
expected := "Hello, Jesús"
got := HelloWorld("Jesús")
assertIsCorrectMessage(got, expected, t)
})
t.Run("when say hello with empty name then response is say hello world", func(t *testing.T) {
expected := "Hello, World"
got := HelloWorld("")
assertIsCorrectMessage(got, expected, t)
})
}
Con
t.Helper()
le estamos diciendo a Go que esta función es de ayuda, así cuando ocurra un fallo en los tests nos dirá donde fue.
Conclusiones
Aprender Go con un buen IDE, es mucho más sencillo, ya que el Golang intenta ayudarte. Por ejemplo si ponemos parentesis en if name == "" {
nos dirá que son innecesarios. También el formateo de código es importante y por ultimo lo que hacemos con TDD es focalizarnos en un problema cada vez.
Como recordatorio de TDD:
- Escribir un test
- Ejecutar para saber que de verdad falla
- Escribir el código para que pase
- Refactorizar
No refactorizar en rojo y baby steps
¿Y si añadimos idiomas para saludar? Algo así: HelloWorld("Yisus", "spanish")
=> Hola, Yisus
o en frances HelloWorld("Jesús", "french")
=> Salut, Jesús
Bola Extra
Muchas veces necesitamos Internet para buscar ejemplo, pero otras veces con la documentación es suficiente. usando
godoc -http :8000
Tenemos toda la documentación de Go lista para usar desde un navegador en localhost:8000/pkg
2 comentarios en “Aprendiendo Go con TDD”