DEV Community

Cover image for TestingGo
Wagner Abrantes
Wagner Abrantes

Posted on

TestingGo

É muito provável que você já tenha ouvido falar de testes então vamos dar uma molhada nos pés aqui.

O teste em si serve pra você verificar a integridade do seu software e por meio desse garantir que funcione sem ou com o mínimo de efeitos colaterais. Em outras palavras, você precisa garantir que seu código faz o que você diz que ele faz.

Existe uma infinidade de convenções a respeito de testes em várias comunidades, e pode ser que em cada uma delas os testes sejam feitos de formas diferentes.

Meu foco não será na técnica e sim em um tipo especifico de teste, o Teste de Unidade ou Unit Test, onde se verifica a integridade da menor unidade dentro do software, uma função por exemplo.

Ao final do desenvolvimento de uma feature é muito comum que se tenha dezenas de testes de unidade inclusive mais código de teste do que de feature, isso porque o teste de unidade é muito simples e rápido de ser escrito.

Por ser rápido e simples costumamos chamar esse tipo de teste de “barato”, pois demanda pouco esforço durante do desenvolvimento.

Podemos mensurar o custo dos tipos de teste através da pirâmide de testes.

Geralmente no meio da pirâmide tem outros tipos, essa é uma versão mais simplificada.

Teste de Integração: É definido como um tipo de teste onde os módulos do software são testados como um grupo. Um projeto de software típico consiste em vários módulos de software, codificados por diferentes programadores. O objetivo deste nível de teste é expor defeitos na interação entre esses módulos quando eles são integrados.

E2E (End to End) O objetivo do teste de ponta a ponta é testar tanto o software quanto as dependências, integridade de dados e comunicação com outros sistemas, interfaces e bancos de dados para exercer a produção completa como cenário.

Existem ainda mais alguns, aqui eu vou falar mais dos unitários pois quem está aprendendo a programar começa com programas menores então nem faz muito sentido algo como E2E.

Na maioria das empresas é o mais utilizado justamente por ter um custo menor, pelo menos é o que dizem hahaha. Inclusive algumas empresas nem testam…

Como funciona?

Matemática básica é sempre muito simples de se entender então acredito que a função mais simples de uma calculadora como, soma é um bom começo, se você quiser pode complicar depois, mas não seremos rasos aqui.

A questão aqui é saber quais os dados de entrada e saída, o teste é basicamente uma afirmação do que você deseja como software.

EX: Entrada{2,2}, Esperado{4} para uma função de soma, é bobinho a principio, mas boa parte das coisas sobre software são assim, as pessoas que estragam tudo criando complexidade desnecessária, muitos iniciantes evitam testes no inicio por isso.

Vamos dizer que alguém escreveu a função Sum() e de alguma forma onde temos Esperado{4} o teste nos retorna Obteve{0}, provável que a função tenha sido mal escrita e precisa ser corrigida, então depois de mudanças finalmente ela retorna Obteve{4} PASS, significa que obtivemos sucesso em fazer com que a nossa função se comportasse como era esperado.

Vamos a um exemplo com código um pouco mais concreto:

Essa é a nossa função de soma, muito simples nada demais.

func Sum(a, b int) int {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

E esse é o nosso teste:

package calculator

import (
    "testing"
)

func TestSum(t *testing.T) {

    verifica := func(t *testing.T, resultado, esperado int) {
        t.Helper()
        if resultado != esperado {
            t.Errorf("resultado '%d esperado '%d'", resultado, esperado)
        }
    }

    t.Run("Soma dois inteiros", func(t *testing.T) {
        resultado := Sum(2, 2)
        esperado := 4
        verifica(t, resultado, esperado)
    })

    t.Run("Soma dois negativos:", func(t *testing.T) {
        resultado := Sum(-4, -5)
        esperado := -9
        verifica(t, resultado, esperado)
    })

    t.Run("Soma numeros grandes:", func(t *testing.T) {
        resultado := Sum(100000000000000, 455456745455745342)
        esperado := 455556745455745342
        verifica(t, resultado, esperado)
    })
}
Enter fullscreen mode Exit fullscreen mode

Dissecação

Disse que era bem simples e isso na verdade é 10x maior que a função que estamos testando haha, mas agora que você sabe o conceito eu vou explicar a parte prática e tu vai ver que é tranquilo.

Uma das nossas opções, seria algo como isso:

func TestSum(t *testing.T) {
    soma := Sum(10, 10)
    if soma != 20 {
        t.Error("Falhou no teste!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Assim é bem mais simples, apesar de ser um teste feito na mesma unidade (Sum) temos apenas um bloco de código. Todo teste tem um prefixo Test, seguido do nome da função que queremos testar.

func TestXxx(*testing.T)

Um parâmetro com um ponteiro importado do pacote “testing” o type T possui três propriedades:

common: É outra struct que compartilha os elementos em comum entre type T e type B essa segunda serve para funcionalidades de benchmark que falarei sobre futuramente. O type common é usado como receiver em várias funções presentes na interface TB que agrega todas as funções que utilizam dados de common, sendo a maioria de log, error e fatal.

isParallel: É um tipo booleano, que sinaliza se os testes estão rodando em paralelo ou não.

context: Um ponteiro de testContext que define testes em paralelo, channels e coisas como onde começam e onde terminam usando o pacote Time.

Esse miolo que é onde entra a especificação do que seria a entrada de dados, a função Sum com os parâmetros é declarada em uma variável e em seguida perguntamos, soma é diferente de 20?

    soma := Sum(10, 10)
    if soma != 20
Enter fullscreen mode Exit fullscreen mode

Por final temos o que seria a saída, a função Error que vem da interface TB usa a type common como receiver.

t.Error(“Falhou no teste!”)

Analisando o conjunto

Agora que temos uma ideia de como funciona a estrutura de um teste vamos ver com mais detalhes aquele primeiro maior mas por partes, vamos saindo do micro para o macro.

Esse teste possui uma estrutura parecida, podemos ver que a função Run vem da mesma struct que estávamos falando type T então é muito provável que ela venha da interface TB, mas não, ela tem apenas type T como receiver existe outra função Run que tem como receiver type B mas essa fica no arquivo benchmark.go do pacote testing.

A estrutura de Run é assim:

func (t *T) Run(name string, f func(t *T))

Name: “Soma dois inteiros”

f: func TestSum()

Temos um miolo com uma separação mais interessante, aqui fica muito claro que resultado é o retorno de Sum() e nosso esperado é um int ao invés de colocar o numero hardcoded numa condicional.

t.Run("Soma dois inteiros", func(t *testing.T) {
    resultado := Sum(2, 2)
    esperado := 4
    verifica(t, resultado, esperado)
})
Enter fullscreen mode Exit fullscreen mode

Agora temos verifica(), vamos dar uma olhada melhor nela:

verifica  :=  func(t *testing.T, resultado, esperado int)
Enter fullscreen mode Exit fullscreen mode

verifica recebe uma função que tem type T, Sum() e o retorno de Sum() como argumentos.

Helper() faz parte de common então temos duas versões uma com type T e outra type B ela serve para marcar a sua função como auxiliar o que é justamente o caso.

A condicional é usada em verifica para que todas as condições de teste que você deseja não precisem aumentar a complexidade.

Errorf() indica que temos uma saída formatada, ela funciona como Logf() porem tem uma chamada de Fail() que serve pra marcar o teste como falho, mas continua a execução para que os demais testes terminem.

E então como argumento de Errorf() temos as saídas que irão nos dizer o que o teste nos retornou e o que ele esperava, porém agora numa função auxiliar dedicada a dar a saída de dados de todos os test cases.

verifica := func(t *testing.T, resultado, esperado int) {
    t.Helper()
    if resultado != esperado {
        t.Errorf("resultado '%d esperado '%d'", resultado, esperado)
    }
}
Enter fullscreen mode Exit fullscreen mode

O que parecia complicado, já não é mais tão complicado assim, teste analisar o mesmo código agora conhecendo a base.

package calculator

import (
    "testing"
)

func TestSum(t *testing.T) {

    verifica := func(t *testing.T, resultado, esperado int) {
        t.Helper()
        if resultado != esperado {
            t.Errorf("resultado '%d esperado '%d'", resultado, esperado)
        }
    }

    t.Run("Soma dois inteiros", func(t *testing.T) {
        resultado := Sum(2, 2)
        esperado := 4
        verifica(t, resultado, esperado)
    })

    t.Run("Soma dois negativos:", func(t *testing.T) {
        resultado := Sum(-4, -5)
        esperado := -9
        verifica(t, resultado, esperado)
    })

    t.Run("Soma numeros grandes:", func(t *testing.T) {
        resultado := Sum(100000000000000, 455456745455745342)
        esperado := 455556745455745342
        verifica(t, resultado, esperado)
    })
}
Enter fullscreen mode Exit fullscreen mode

Agora que você leu até aqui eu tenho duas coisas pra dizer, uma é que não terminei de falar sobre testes ainda e tem mais conteúdo pela frente, a segunda é que eu gostaria do seu feedback aqui nos comentários ou você pode me chamar no meu Twitter.

Eu quero saber é a sua senioridade e qual a adequação da minha abordagem e linguagem durante não apenas esse artigo mas também em relação aos demais, assim posso ter uma ideia mais clara de como devo abordar os assuntos por aqui.

Top comments (0)