Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Go
Code

Vamos Lá: Testando Programas Go

by
Difficulty:IntermediateLength:MediumLanguages:

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Nesse tutorial, ensinarei o básico de teste idiomático em Go usando as boas práticas desenvolvidas pelos projetistas da linguagem e pela comunidade. A arma principal será o pacote padrão de teste. O alvo será um programa simples que soluciona um problema simples do Project Euler.

Diferença da Soma dos Quadrados

O problema da diferença da soma dos quadrados é bem simples: "Encontre a diferença entre a soma dos quadrados dos cem primeiros números e do quadrado da soma".

Esse problema em particular pode ser resolvido concisamente, especialmente se conhecer Gauss. Por exemplo, a soma dos N primeiros números natuais é (1 + N) * N / 2 e a soma dos quadrados dos N primeiros inteiros é (1 + N) * (N * 2 + 1) * N / 6. Assim, o problema pode ser resolvido pela fórmula a seguir, atribuindo 100 a N:

(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)

Bom, isso é bem específico e não há muito o que testar. Por outro lado, criamos algumas funções que são um pouco mais gerais que aquilo preciso para o problema, mas servem para outros programas no futuro (o projeto Euler tem 559 problemas).

O código está disponível no GitHub.

Eis as assinaturas das quatro funções:

Com o programa alvo no lugar (desculpem-me, aficcionados por TDD), vejamos como escrever testes para eles.

O Pacote de Testes

O pacote de teste anda lado-a-lado do comando go test. O teste do pacote deve ter o sufixo "_test.go". Podemos dividir os testes em diversos arquivos seguindo essa convenção. Por exemplo: "whatever1_test.go" e "whatever2_test.go". Devemos colocar nossas funções de teste nesses arquivos de teste.

Cada função de teste é uma função pública exportada cujo nome começa com "Test", aceita um ponteiro para um objeto testing.T e retorna nada. Mais ou menos assim:

O objeto T provê vários métodos que podemos usar para indicar falhas ou registrar erros.

Lembre-se: apenas funções de teste definidas em arquivos de teste serão executadas pelo comando go test.

Escrevendo Testes

Cada teste segue o mesmo fluxo: configurar o ambiente de teste (opcional), alimentar o código sob teste com entradas, capturar resultados e comparar com o resultado esperado. Note que entradas e resultados não precisam ser argumentos de função.

Se o código sob teste busca dados de uma base de dados, então a entrada garantirá que a base de dados contem o dado de teste apropriado (talvez simulando objetos em diversos níveis). Mas, para nossa aplicação, o cenário comum em que passamos argumentos para uma função e comparamos o resultado com o retorno da mesma é suficiente.

Comecemos com a função SumList(). Essa função recebe fatias inteiros e retorna a soma deles. Eis uma função de teste que verifica que SumList() comporta-se como esperado.

Ela testa dois casos e se um retorno esperado não casa com o resultado ela chama o método Error() do objeto testing.T.

Isso é bem direto, mas é um tanto verboso. Teste idiomático em Go usa teste voltado a tabelas, onde criamos um struct para pares de entrada e resultados, e, então, temos uma lista desses pares para aplicar na lógica. Eis como se faz para o teste da função SumList().

Assim está bem melhor. É bem fácil adicionar novos casos de teste. É fácil ter todos os casos de teste em um lugar e se decidirmos alterar a lógica do teste, não precisamos mudar múltiplas instâncias.

Eis outro exemplo para teste da função SquareList(). Nesse caso, tanto entrada quando resultado são "fatias" de inteiros, logo a estrutura é diferente, mas o fluxo é idêntico. É interessante saber que Go não provê uma forma embutida de comparar "fatias", logo usaremos reflect.DeepEqual() para comparar a fatia do resultado com a do valor esperado.

Executando Testes

Executar testes é tão simples quanto digitar go test no diretório do seu pacote. Go encontrará todos os arquivos com o sufixo "_test.go" e todas as funções iniciadas com Test e as executará. Eis como parece quando tudo sai OK.

Nada demais. Façamos o teste falhar de propósito. Mudaremos o caso de teste para SumList() de forma que o resultado esperado da soma entre 1 e 2 seja 7.

Agora, ao digitarmos go test, obtemos:

Isso mostrar bem o que aconteceu e deve dar toda a informação necessária para resolver o problema. Nesse caso, o problema é que o teste em si está errado e o valor esperado deveria ser 3. Essa é uma lição importante. Não assuma, automaticamente, que se um teste falha, o código sob teste está errado. Considere o sistema inteiro, incluindo o código sob teste, o teste em si e o ambiente de teste.

Cobertura de Teste

Para garantir que o código funciona, não basta ter testes passando. Outro aspecto importante é a cobertura de teste. Os testes cobrem cada declaração do código? Algumas vezes, nem isso basta. Por exemplo, se tivermos um laço em seu código que executa até certa condição, podemos testá-lo com uma condição que funciona, mas falharemos em perceber que em outros casos a condição pode sempre ser falsa, dando num laço infinito.

Testes Unitários

Testes unitários são como escovação e fio dental. Não devemos esquecer. São a primeira barreira contra problemas, dando maior confiança na refatoração. Também são benéficos ao tentar reproduzir problemas, e sermos capazes de criar testes que falham demonstrando o problema e fazendo-os passar ao corrigir.

Testes de Integração

Testes de integração também são necessários. Pense neles como visitas ao dentista. Pode ficar bem sem eles por um tempo, mas se negligenciá-los por muito tempo, não vai ser legal.

A maioria dos programas não triviais são feitos por vários módulos ou componentes inter-relacionados. Problemas podem ocorrer ao juntar esses componentes. Testes de integração dão confiança que o sistema por inteiro está funcionando como esperado. Existem muitos outros tipos de testes, como os de aceitação, performance, estresse/carga e teste completos, mas os testes unitários e de integração são duas formas fundamentais de testar software.

Conclusão

Go possui suporte embutido a teste, uma forma bem definida de se escrever testes e recomendações, na forma de testes voltados a tabelas.

A necessidade de escrever structs especiais para cada combinação de entradas e saídas é um tanto irritante, mas é o preço a se pagar pela abordagem simples por padrão da Go.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.