Jose Juan MontielJose Juan Montiel

Motivacion

En esta ocasion, voy a comentar mis avances en el mundo del testing con GoLang. Para ello, con la ayuda de el siguiente codigo voy a comentar algunos conceptos que he aprendido.

Interfaces

Esta es la manera de definir una interface en Go.

type Doer interface {
    DoSomething(int, string) error
}

Para que una clase implemente una interfaz, solo es necesario implemntar todos los metodos de la interfaz.

type doerImpl struct {              (1)
}

func NewDoerService() Doer {        (2)
	return &doerImpl{}              (3)
}

func (d *doerImpl) DoSomething(numero int, cadena string) error {   (4)
	fmt.Println("Calling DoSomething method")
	fmt.Println(cadena)                                 (5)
	return errors.New("error")                          (6)
}
1 Toda clase necesita de una estructura.
2 Esta funcion es el constructor de la implementacion, devuelve la interfaz.
3 Asi devolvemos la referencia a la estructura de esta implementacion.
4 Esta seria el metodo que vamos a implementar.
5 La implementacion imprime el parametro de entrada.
6 Devuelve una clase error.

Ahora supongamos que tenemos otra clase donde hacemos uso de esa implementacion de la interfaz.

type User struct {
	Doer doer.Doer                  (1)
}

func NewUser(d doer.Doer) *User {   (2)
	return &User{d}                 (3)
}

func (u *User) Use() error {
	fmt.Println("Calling Use method")
	return u.Doer.DoSomething(123, "Hello GoMock")  (4)
}
1 En este caso, al declarar la estructura de la clase, definimos un atributo del tipo de la interfaz
2 En el constructor de esta clase, pasamos una instancia de la implementacion de la interfaz
3 Aqui es donde la pasamos.
4 Esta funcion, hace uso de la funcion que hemos implementado de la interfaz.

Consideremos ahora el siguiente main que va a instanciar la implementacion y luego pasarla a la funcion que hace uso de la interfaz.

func main() {
	newDoerImpl := doer.NewDoerService()        (1)
	_ = newDoerImpl.DoSomething(1, "hola")      (2)

	newUser := user.NewUser(newDoerImpl)        (3)
	newUser.Use()                               (4)
}
1 Usando el constructor, instanciamos la implementacion
2 Ahora llamamos a la funcion de la implemtacion
3 Si cogemos esa instancia y la pasamos al constructor de la nueva clase
4 Podemos hacer uso de la funcion, que hara uso de la clase de la implementacion

Ahora supongamos que queremos testear la funcion Use() pero no queremos que se llame a DoSomething.

Para ello vamos a usar la libreria de mock en go. Esta libreria al igual que otras similares en otros lenguajes nos permiten mockear los objetos que no queremos testear, en este caso nuestro objetivo es mockerar la implementacion de la interfaz para poder testear el metodo Use.

Notar que para generar esta clase mock, debemos indicarle una interfaz sobre la que se ha construido la implementacion que queremos mockear, en este caso Doer, y gomock nos genera una clase que nos permite instrumentalizar la clase a mockear.

func TestUseReturnsErrorFromDo(t *testing.T) {
	fmt.Println("TestUseReturnsErrorFromDo")

	mockCtrl := gomock.NewController(t)             (1)
	defer mockCtrl.Finish()

	dummyError := errors.New("dummy error")
	mockDoer := mocks.NewMockDoer(mockCtrl)         (3)
	testUser := &user.User{Doer: mockDoer}          (2)

	mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(dummyError).Times(1)  (4)

	err := testUser.Use()                           (5)

	if err != dummyError {
		t.Fail()
	}
}
1 Inicializamos la libreria de mock.
2 Instanciamos la clase a testear, pasandole en vez de la interfaz un mock
3 Instanciamos la clase mock, que hemos pasado como parametro en el constuctor de la clase a testear.
4 Instrumentalizamos el mock, en este caso esperamos que cuando el test llame a la clase mockeada con esos parametros, la respuesta sea la esperada.
5 Lanzamos la clase a testear.

Notar que si lanzamos el test en modo verboso no veremos los print de la implementacion de la interfaz, porque no se habra llamado, sera la instrumentalizacion la encargada de devolver el resultado.

Para un proximo articulo, notar que si la interfaz que queremos mockear esta dentro de un proyecto que usa go dep, podemos tener problemas con la paqueteria del import y que indique vendor.

Pero eso sera para otro dia.