Desafio Go
Média com erro e subtestes paralelos
Enunciado
Implemente Media(nums []int) (float64, error) que retorna a média dos números e um erro se a slice estiver vazia. Escreva testes table-driven com t.Run cobrindo: slice com valores, slice vazia (erro) e slice com um elemento. Use t.Helper em um helper de comparação de floats com tolerância e chame t.Parallel() em cada subteste.
Requisitos
Mediaretorna erro quando a slice está vazia.- O helper de comparação usa
t.Helper()e tolerância (ex.:1e-9). - Cada caso roda em
t.Rune chamat.Parallel(). - A tabela inclui um campo
querErro boolpara distinguir casos de erro.
Código inicial
package estat
import (
"errors"
"testing"
)
func Media(nums []int) (float64, error) {
// implemente
return 0, nil
}
func verificaFloat(t *testing.T, esperado, obtido float64) {
// implemente com t.Helper()
}
func TestMedia(t *testing.T) {
// implemente
}
Saída esperada
=== RUN TestMedia
=== RUN TestMedia/valores
=== PAUSE TestMedia/valores
=== RUN TestMedia/vazia
=== PAUSE TestMedia/vazia
=== RUN TestMedia/um_elemento
=== PAUSE TestMedia/um_elemento
=== CONT TestMedia/valores
=== CONT TestMedia/vazia
=== CONT TestMedia/um_elemento
--- PASS: TestMedia (0.00s)
--- PASS: TestMedia/valores (0.00s)
--- PASS: TestMedia/vazia (0.00s)
--- PASS: TestMedia/um_elemento (0.00s)
PASS
ok estat 0.002s
Ver dica
Verifique len(nums) == 0 antes de somar. Para comparar floats, use math.Abs(esperado-obtido) > 1e-9.
Mostrar solução
package estat
import (
"errors"
"math"
"testing"
)
// Media retorna a média aritmética dos números; erro se a slice estiver vazia.
func Media(nums []int) (float64, error) {
if len(nums) == 0 {
return 0, errors.New("slice vazia")
}
soma := 0
for _, n := range nums {
soma += n
}
return float64(soma) / float64(len(nums)), nil
}
// verificaFloat compara floats com tolerância; t.Helper aponta para o chamador.
func verificaFloat(t *testing.T, esperado, obtido float64) {
t.Helper()
if math.Abs(esperado-obtido) > 1e-9 {
t.Errorf("esperado %v, obtido %v", esperado, obtido)
}
}
func TestMedia(t *testing.T) {
casos := []struct {
nome string
nums []int
esperado float64
querErro bool
}{
{"valores", []int{2, 4, 6}, 4, false},
{"vazia", nil, 0, true},
{"um elemento", []int{7}, 7, false},
}
for _, c := range casos {
t.Run(c.nome, func(t *testing.T) {
t.Parallel() // subtestes independentes podem rodar em paralelo
obtido, err := Media(c.nums)
if c.querErro {
if err == nil {
t.Fatalf("esperava erro, mas não veio")
}
return
}
if err != nil {
t.Fatalf("erro inesperado: %v", err)
}
verificaFloat(t, c.esperado, obtido)
})
}
}
Passo a passo
Mediavalidalen(nums) == 0e retornaerrors.New("slice vazia").- Somamos com um loop simples e dividimos convertendo para
float64. - O helper
verificaFloatchamat.Helper()para que o erro aponte o teste chamador. - Comparamos floats com tolerância
1e-9, evitando problemas de precisão binária. - A tabela tem um campo
querErroque diferencia casos de sucesso e de erro. - Dentro do
t.Run, chamamost.Parallel()para permitir execução concorrente dos subtestes. - Se o caso espera erro, usamos
t.Fatalfquando ele não vem; senão, retornamos cedo. - No caminho de sucesso, chamamos o helper para verificar o valor.
Por que funciona
t.Parallel() marca o subteste para rodar em paralelo com outros, o que acelera suítes grandes. Como cada subteste só lê dados da tabela, não há compartilhamento de estado mutável — condição essencial para paralelismo seguro.
O campo querErro mantém o padrão table-driven mesmo quando os resultados têm formas diferentes (número + erro). O helper com t.Helper() garante que, ao falhar, o relatório mostre a linha do t.Run, não a do helper.
Erros comuns
- Esquecer
t.Parallel()dentro dot.Run: o subteste roda sequencialmente; nada quebra, mas você perde o ganho. - Comparar floats com
==diretamente:0.1+0.2 != 0.3; use tolerância. - Mutar a slice
c.numsdentro do subteste paralelo: gera corrida de dados. Trate os dados como somente leitura. - Usar
t.Fataldentro de uma goroutine que não é do teste — só chameFatalna goroutine do subteste.
Outra forma de resolver
Sem paralelismo, rodando tudo sequencialmente:
for _, c := range casos {
t.Run(c.nome, func(t *testing.T) {
// sem t.Parallel()
})
}
É preferível quando os testes compartilham recursos (arquivos, banco) ou quando o tempo total já é baixo.
Saída esperada
=== RUN TestMedia
=== RUN TestMedia/valores
=== PAUSE TestMedia/valores
=== RUN TestMedia/vazia
=== PAUSE TestMedia/vazia
=== RUN TestMedia/um_elemento
=== PAUSE TestMedia/um_elemento
=== CONT TestMedia/valores
=== CONT TestMedia/vazia
=== CONT TestMedia/um_elemento
--- PASS: TestMedia (0.00s)
--- PASS: TestMedia/valores (0.00s)
--- PASS: TestMedia/vazia (0.00s)
--- PASS: TestMedia/um_elemento (0.00s)
PASS
ok estat 0.002s