30/100 Dias de Golang - Testes - Exemplo Prático
Table of Contents
#
Testes - Exemplo Prático
Vamos fazer um exemplo completo com o conhecimento que adquirimos até agora sobre testes na linguagem Go. Para esse exemplo vou criar um pacote chamado “stats” com algumas funções estatísticas simples. Usarei também testify
que vimos no 29/100 Dias de Golang - Testes com Testify.
Vamos iniciar o projeto:
go mod init stats
E já adicionar as libs necessárias:
go get github.com/stretchr/testify/assert
go get github.com/stretchr/testify/require
Vamos criar um arquivo e adicionar duas funções, média e mediana. Veja que fiz algumas validações se o slice é vazio e criei um erro específico para isso.
package stats
import (
"errors"
"sort"
)
// ErrEmptyInput é retornado quando o slice de entrada está vazio.
var ErrEmptyInput = errors.New("input slice cannot be empty")
// Average calcula a média de um slice de inteiros.
func Average(numbers []int) (float64, error) {
if len(numbers) == 0 {
return 0, ErrEmptyInput
}
sum := 0
for _, n := range numbers {
sum += n
}
return float64(sum) / float64(len(numbers)), nil
}
// Median calcula a mediana de um slice de inteiros.
func Median(numbers []int) (float64, error) {
count := len(numbers)
if count == 0 {
return 0, ErrEmptyInput
}
// Copia para não modificar o slice original externamente
// Embora a função internamente precise ordenar.
dataCopy := make([]int, count)
copy(dataCopy, numbers)
sort.Ints(dataCopy) // Ordena a cópia
middle := count / 2
if count%2 == 1 {
// Número ímpar de elementos: a mediana é o elemento do meio
return float64(dataCopy[middle]), nil
}
// Número par de elementos: a mediana é a média dos dois elementos centrais
return float64(dataCopy[middle-1]+dataCopy[middle]) / 2.0, nil
}
#
Testes para a função de média
Vamos criar 3 testes para essa função: valores positivos, valores positivos e negativos e um slice vazio. Vou utilizar aqui os subtestes, eles ajudam a organizar cada caso de teste. Veja que vou utilizar o assert
e o require
, a diferença é que se estivermos usando o assert
e o teste falhar a execução do mesmo continua, quando usamos o require
a execução do testes atual, nesse caso o subteste, é parado. Isso é útil quando uma verificação é pré-requisito para as seguintes (ex: verificar se um erro não ocorreu antes de verificar o valor de retorno).
func TestAverage(t *testing.T) {
t.Run("calcula média de números positivos", func(t *testing.T) {
numbers := []int{1, 2, 32, 4, 5}
expected := 3.0
avg, err := Average(numbers)
assert.NoError(t, err, "Não deveria retornar erro para entrada válida")
assert.InDelta(t, expected, avg, 0.001, "A média calculada está incorreta")
})
t.Run("calcula média incluindo negativos e zero", func(t *testing.T) {
numbers := []int{-1, 0, 1, 2, 3}
expected := 1.0
avg, err := Average(numbers)
assert.NoError(t, err)
assert.InDelta(t, expected, avg, 0.001)
})
t.Run("retorna erro para slice vazio", func(t *testing.T) {
numbers := []int{}
_, err := Average(numbers)
require.Error(t, err, "Deveria retornar erro para entrada vazia")
require.Equal(t, ErrEmptyInput, err, "O tipo de erro retornado está incorreto")
})
}
Uma coisa diferente que uso aqui é o assert.InDelta(t, expected, avg, 0.001)
ele é extremamente útil para comparação de pontos flutuantes, ele verifica se dois floats estão dentro de uma tolerância (delta)
#
Teste para a função de Mediana
Vamos criar 4 casos de teste: mediana para número ímpar de elementos, número par, valores repetidos “espelhados” e slice vazio. Na função de media fazemos um copy(dataCopy, numbers)
para não modificar o slice original, adicionaremos uma verificação para isso.
func TestMedian(t *testing.T) {
t.Run("calcula mediana para número ímpar de elementos", func(t *testing.T) {
numbers := []int{1, 3, 2, 5, 4}
expected := 3.0
median, err := Median(numbers)
assert.NoError(t, err)
assert.Equal(t, expected, median, "Mediana incorreta para contagem ímpar")
assert.Equal(t, []int{1, 3, 2, 5, 4}, numbers, "O slice original não deveria ser modificado")
})
t.Run("calcula mediana para número par de elementos", func(t *testing.T) {
numbers := []int{1, 4, 2, 3}
expected := 2.5
median, err := Median(numbers)
assert.NoError(t, err)
assert.InDelta(t, expected, median, 0.001, "Mediana incorreta para contagem par")
})
t.Run("calcula mediana com números repetidos", func(t *testing.T) {
numbers := []int{1, 3, 2, 3, 1}
expected := 2.0
median, err := Median(numbers)
assert.NoError(t, err)
assert.Equal(t, expected, median)
})
t.Run("retorna erro para slice vazio", func(t *testing.T) {
numbers := []int{}
_, err := Median(numbers)
require.Error(t, err, "Deveria retornar erro para entrada vazia")
require.Equal(t, ErrEmptyInput, err, "O tipo de erro retornado está incorreto")
})
}
#
Cobertura
Só para relembrar como verificamos a cobertura de testes:
go test --coverprofile=coverage.out
PASS
coverage: 100.0% of statements
ok stats 0.005s