Testes
Last updated
Last updated
Escrever testes para um código é parte essencial do desenvolvimento de aplicações robustas. É extremamente importante sabermos testar a sua validade, conseguir fazer alterações em código existente e saber se elas afetarão demais partes do sistema.
Existem diferentes tipos de testes. Aqui, focaremos em testes unitários.
Em Go, tudo que é necessário para testar já está disponível na biblioteca padrão. O pacote que provê o essencial se chama .
Para executar os testes presentes na aplicação, usamos o comando go test
.
Vamos aproveitar e introduzir a prática do TDD (do inglês Test Driven Development). Vamos abordar apenas o suficiente para explicar testes em Go, mas existem diversos sites, vídeos e livros publicados sobre TDD que você pode buscar posteriormente caso queira saber mais sobre.
A ideia do TDD é que trabalhemos em ciclos:
Primeiro escrevemos um teste que inicialmente irá falhar, tendo em vista que o código ainda não foi escrito
Implementamos a funcionalidade com o mínimo de código para o teste passar
Refatoramos para eliminar redundância e melhorar a legibilidade
Como exemplo, nossa tarefa será escrever uma calculadora (ou melhor, calculadora que faça apenas somas 😆).
Como primeiro passo, começamos a planejar o que o primeiro teste precisa:
Precisamos de uma função que faça a soma de dois algarismos;
Quando chamarmos esta função, queremos “guardar” o seu retorno em uma variável;
Queremos então comparar o valor recebido da função com o valor que esperávamos.
Vamos ao teste: criaremos o arquivo calculadora_test.go
e representaremos no código o que foi descrito acima
O que aconteceu até aqui? Vamos explicar…
Primeiro, criamos um arquivo chamado calculafora_test.go
:
Nomeamos o arquivo terminando com _test.go. Assim ele será ignorado quando o executável da aplicação for criado, mas será incluído quando o comando go test for chamado.
Em seguida, criamos a função que é o teste em si:
O nome da função segue o padrão TestXxx
: Xxx serve para identificar o nome da função que está sendo testada e deve iniciar com letra maiúscula.
Como parâmetro, teremos a variável t
que é um ponteiro para testing.T
, que por sua vez é um tipo fornecido pela biblioteca de testes do Go, que serve para gerenciar o estado do teste e fornecer suporte à formatação de mensagens.
Depois disso, começamos a implementar o teste propriamente dito:
Caso o valor não seja o esperado, utilizamos um dos métodos que testing.T
fornece para formatar e imprimir uma mensagem de erro em testes - como t.Errorf()
, que usamos nesse exemplo.
Pronto, agora vamos rodar o teste usando o seguinte comando:
E a seguinte mensagem será mostrada:
Ótimo! Era isso mesmo que esperávamos: a função Adicione()
ainda não foi implementada, por isso a mensagem de erro indica que Adicione é "undefined".
Seguimos em frente e a adicionamos a um arquivo chamado calculadora.go
, que criaremos no mesmo pacote que o teste está e adicionamos o mínimo de código a fim de compilar.
Agora, ao rodarmos o comando go test
novamente, o erro é outro. Nos livramos do erro de compilação, mas agora recebemos um erro relacionado ao teste:
Agora sim, vamos para o próximo passo, que é fazer o teste passar. E qual é o passo mais rápido para fazê-lo executar com sucesso?
Pode causar estranhamento, mas, nessa etapa, o passo que buscamos é simplesmente substituir o zero pelo resultado esperado no único caso de testes que temos, dessa forma:
O que Kent Beck, criador do método, fala sobre essa etapa é que devemos cometer quaisquer “pecados” (sim, o autor fala em sins no original em inglês) que nos façam obter o resultado esperado. Explorar os diferentes casos de teste e iterar o ciclo básico do TDD (escrever o teste, fazer o teste passar e refatorar para eliminar redundâncias) nos levará a superar quaisquer aberrações que adicionarmos nessa etapa.
Agora, quando rodarmos o teste novamente com o comando go test
, ele passará (aqui usamos a flag -v
, para exibir o resultado de forma verbosa, então o comando ficou assim: go test -v
).
Nessa etapa, a ideia é eliminar todo tipo de duplicação que tenha sido inserida para os testes passarem. No nosso caso, onde está essa duplicação?
O número 7 aparece tanto no nosso teste (na linha 7 do arquivo calculadora_test.go
: espero := 7
) e no retorno da função sendo testada (na linha 4 do arquivo calculadora.go
: return 7
).
Como removemos essa duplicação? Bem, o caso esperado do teste precisa permanecer, afinal é ele que nos diz o que esperamos obter da função que está sendo testada. Assim, o que podemos mudar é o retorno da função: não mais fazê-la retornar o número 7 fixo, mas sim a soma de seus dois parâmetros.
Quando rodamos os testes, eles permanecem passando:
Com essa mudança, nosso código passa no caso de teste e está livre de redundâncias. Note que do passo 2 para o passo 3 os testes não devem quebrar: eles precisam continuar passando. Devemos remover as redundâncias e “pecados” do nosso código, mas não alterar a saída da função testada.
Em teoria, temos o código pronto e funcionando para outras somas que fizermos. Vamos testar se isso é verdade? Faremos isso adicionando um novo caso de testes ao nosso arquivo calculadora_test.go
:
Note que adicionamos o método t.Run()
da biblioteca de testes (pacote testing
): ele roda sub-testes dentro de uma mesma função de testes, cada um desses sub-testes terá o nome que for passado como primeiro argumento. No exemplo, usamos os nomes "a soma de 3 e 4 é igual a 7" e "a soma de 27 e 113 é igual a 140".
Como esperávamos, os testes continuam passando! Esse é o resultado que obtemos ao rodar go test -v
:
Assim, podemos seguir adicionando casos até alcançarmos todos os cenários que julgarmos necessários.
Quando adicionamos o segundo caso de testes, pudemos notar bastante código sendo repetido no arquivo calculadora_test.go
. Quando os casos de teste são muito similares e nos levam a escrever código repetido, podemos aplicar os testes orientados por tabela (traduzido do inglês Table-driven tests).
Como faremos? Vamos por partes:
Primeiro, utilizaremos a sintaxe de struct
literal para representar os atributos que precisamos e escrever uma lista de testes para remover a duplicação de código dos próprios testes.
Tudo que precisamos de entrada para os testes está representado nessa struct: o nome do sub-teste, os dois operandos da soma e o resultado esperado.
Em seguida, vamos adicionar uma instrução for range
para percorrer todos os elementos da struct
"testes" e, em cada iteração:
Chamar o método que roda sub-testes, passando o nome de cada sub-teste
Chamar a função Adicione()
, passando como argumentos os operandos 1 e 2
Fazer a comparação entre esperado e obtido
Dessa forma:
É importante aqui entendermos se tudo continua funcionando como o esperado. Desta forma, adicionamos todos os testes que nosso negócio planeja implementar com facilidade. Por exemplo, vamos adicionar mais dois casos de teste:
Se rodarmos o comando go test -v
, receberemos o seguinte:
Percebeu que adicionar novos testes ficou muito simples?
Esse tipo de abordagem é bastante útil quando não existem (ou existem poucas) variações entre os casos de teste.
testing
da biblioteca padrão:Sugerimos esse conteúdo para aprofundar os conhecimentos em testes (e em Go também):