Como evitar o uso de Monkey Patching em testes Go: alternativas seguras e confiáveis

Postador por : at

Categoria : golang


Go é uma linguagem de programação poderosa e escalável, mas como qualquer outra linguagem, é importante garantir que o código seja testado de maneira eficaz e confiável. Uma técnica comum de teste é o Monkey Patching, que consiste em modificar o comportamento de uma função ou método já existente sem modificar sua implementação original. No entanto, essa técnica pode trazer problemas de confiabilidade e manutenção no seu código.

Primeiramente, o Monkey Patching aumenta a complexidade do código, já que é necessário garantir que as modificações não afetem outras partes do sistema. Além disso, pode haver problemas de compatibilidade com outras bibliotecas ou pacotes que dependem do comportamento original. E finalmente, pode haver problemas de testabilidade, já que é necessário garantir que as modificações não afetem os testes de outras partes do sistema.

O que pode dar de errado?

Imagine que você tem uma função “ProcessarPagamento” que chama uma outra função “ValidarCartão” para validar o cartão de crédito do cliente antes de processar o pagamento. Durante os testes, você decide usar monkey patching para substituir a função “ValidarCartão” por uma função mock. Isso funciona bem no seu ambiente de teste, mas quando o código é colocado em produção, a função “ValidarCartão” original é chamada e o pagamento não é processado corretamente. Isso acontece porque o monkey patching foi aplicado apenas no seu ambiente de teste e não foi considerado no ambiente de produção.

Esse é um exemplo de como o uso de monkey patching pode causar problemas de confiabilidade e manutenção no seu código Go. É importante utilizar ferramentas confiáveis e testar de forma robusta para garantir a qualidade e segurança do seu software.

Alguns tipos de implementações para Mocks

Testes são uma parte fundamental do desenvolvimento de software e permitem que os desenvolvedores verifiquem se o código funciona como o esperado. No entanto, quando se trata de testes unitários, muitas vezes é necessário mockar certos componentes ou dependências para garantir que o teste seja independente e repetível. É aqui que entram em cena os mocks.

Os mocks são uma representação simulada de uma dependência ou componente que permite aos desenvolvedores controlar e prever o comportamento de certas partes do código durante os testes. No entanto, existem várias formas de criar mocks, incluindo o uso de bibliotecas de terceiros, como mockery e testify, gomock, ginkgo ou a criação de interface fakes manualmente.

Neste artigo, exploraremos a vantagem de usar mocks com cada uma dessas bibliotecas em vez de monkey patch, dando exemplos de implementação e mostrando por que essas abordagens são preferíveis ao uso do monkey patch.

Vejamos abaixo exemplos de implementação de testes utilizando as tecnicas citadas acima, vamos mockar o coportamente de um banco de dados.

Inteface Fakes

Interface Fakes: Permite a criação de objetos falsos que implementam uma interface, utilizados para fins de teste.

package main

import (
	"testing"
)

type DB interface {
	Query(query string) ([]byte, error)
}

type DBTest struct{}

func (db *DBTest) Query(query string) ([]byte, error) {
	if query == "SELECT * FROM users" {
		return []byte("fake data"), nil
	}
	return nil, nil
}

func TestMain(t *testing.T) {
	// Gerando o mock
	mockDB := &DBTest{}

	// Usando o mock no código
	data, _ := mockDB.Query("SELECT * FROM users")
	if string(data) != "fake data" {
		t.Errorf("Dados devem ser iguais a 'fake data', mas recebidos '%s'", string(data))
	}
}

Testify

Github: https://github.com/stretchr/testify

Testify é uma biblioteca para Go que oferece recursos para testes de unidade, incluindo assertivas, mocks e outras funcionalidades para ajudar a desenvolver testes de unidade de forma eficiente. É uma das bibliotecas mais usadas para testes em Go.

package main

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type DB interface {
	Query(query string) ([]byte, error)
}

type DBTest struct {
	mock.Mock
}

func (db *DBTest) Query(query string) ([]byte, error) {
	args := db.Called(query)
	return args.Get(0).([]byte), args.Error(1)
}

func TestMain(t *testing.T) {
	// Gerando o mock
	mockDB := new(DBTest)
	mockDB.On("Query", "SELECT * FROM users").Return([]byte("fake data"), nil)

	// Usando o mock no código
	data, _ := mockDB.Query("SELECT * FROM users")
	assert.Equal(t, []byte("fake data"), data, "Dados devem ser iguais a 'fake data'")
}

Monkery

Github: https://github.com/vektra/mockery

Mockery é uma biblioteca para gerar código de mocks em Go. É fácil de usar e oferece uma interface amigável para gerar mocks para suas interfaces e estruturas. Ele também oferece recursos para personalização de mocks e integração com outras bibliotecas de testes.

package main

import (
	"fmt"
	"testing"

	"github.com/vektra/mockery/mockery"
)

type DB interface {
	Query(query string) ([]byte, error)
}

func TestMain(t *testing.T) {
	// Gerando o mock
	generator, err := mockery.NewGenerator()
	if err != nil {
		t.Fatalf("Não foi possível criar o gerador de mock: %s", err)
	}
	err = generator.Start("./mocks")
	if err != nil {
		t.Fatalf("Não foi possível iniciar o gerador de mock: %s", err)
	}
	err = generator.Generate("DB")
	if err != nil {
		t.Fatalf("Não foi possível gerar o mock: %s", err)
	}

	mockDB := new(mocks.DB)
	mockDB.On("Query", "SELECT * FROM users").Return([]byte("fake data"), nil)

	// Usando o mock no código
	data, _ := mockDB.Query("SELECT * FROM users")
	fmt.Println(string(data))
}

Note que o código acima gera um arquivo de mock mocks/DB.go que contém a implementação do mock para a interface DB. Em seguida, esse mock é usado no teste para simular o comportamento da interface DB.

Gomock

Github: https://github.com/golang/mock

Gomock é uma biblioteca de mock gerada automaticamente que permite criar mocks de interface rapidamente. Ele fornece uma sintaxe simples e intuitiva para definir expectativas de chamadas e verificar se foram atendidas.

package main

import (
	"testing"
	"github.com/golang/mock/gomock"
)

type DB interface {
	Query(query string) ([]byte, error)
}

func TestMain(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	// Gerando o mock
	mockDB := NewMockDB(ctrl)
	mockDB.EXPECT().Query("SELECT * FROM users").Return([]byte("fake data"), nil)

	// Usando o mock no código
	data, _ := mockDB.Query("SELECT * FROM users")
	if string(data) != "fake data" {
		t.Errorf("Dados devem ser iguais a 'fake data', mas recebidos '%s'", string(data))
	}
}

Ginkgo

Github: https://github.com/onsi/ginkgo

Ginkgo é uma biblioteca de testes BDD que fornece uma sintaxe fluente e uma ampla gama de recursos para escrever testes em Go. Ele inclui suporte integrado para mocks e permite a criação de mocks facilmente usando a funcionalidade de “spies”.

package main

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type DB interface {
	Query(query string) ([]byte, error)
}

type DBTest struct{}

func (db *DBTest) Query(query string) ([]byte, error) {
	if query == "SELECT * FROM users" {
		return []byte("fake data"), nil
	}
	return nil, nil
}

var _ = Describe("TestMain", func() {
	It("Deve retornar os dados fake corretos", func() {
		// Gerando o mock
		mockDB := &DBTest{}
		// Usando o mock no código
		data, _ := mockDB.Query("SELECT * FROM users")
		Expect(string(data)).To(Equal("fake data"))
	})
})

Em geral, todas as bibliotecas acima são úteis para criar mocks de objetos e interfaces em Go, mas a escolha depende das necessidades específicas de cada projeto. Algumas pessoas preferem Ginkgo por sua integração fácil com testes BDD, enquanto outras preferem Gomock por sua facilidade de uso e geração automática de código. Mocket é uma boa escolha para quem procura uma solução simples e rápida para criar mocks.

Conclusão

Em resumo, o uso de mocks é fundamental em testes de software para simular comportamentos de dependências externas. No entanto, é importante escolher a abordagem correta de mock para garantir a qualidade e a confiabilidade dos testes.

O monkey patching pode ser uma técnica fácil e rápida, mas não é recomendado devido aos problemas de manutenção e a possibilidade de interferir em outros testes. Em vez disso, é melhor usar ferramentas como vektra/mockery, testify ou interface fakes, que oferecem recursos avançados de mock e garantem uma abordagem mais profissional e segura para a criação de mocks.

Portanto, sempre que precisar de mocks em seus testes, considere usar as melhores práticas e ferramentas para garantir a qualidade e confiabilidade do seu código.

Github: https://github.com/phelliperodrigues/mock-examples-golang