Testes baseados em propriedades em Go: quebrando código mais rápido do que você escreve

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Quando os testes unitários mentem e os bugs permanecem



Você escreveu 20 testes unitários. Todos verdes. O CI passa. Você está satisfeito. E então — um bug em produção. Familiar? Cenário clássico: você testou apenas os casos que você mesmo imaginou. E a realidade, como de propósito, apresenta combinações que você nem sequer considerou.



Em Go existe uma abordagem poderosa, mas subestimada — testes baseados em propriedades (ou teste de propriedades). Em vez de verificar entradas e saídas específicas, você descreve as propriedades que a função deve satisfazer, e o framework gera centenas de dados de teste aleatórios. Se a propriedade for violada — você encontrou um bug.



Neste artigo, vamos explorar como implementar testes baseados em propriedades em Go usando a biblioteca gopter, escrever exemplos reais e ver quais bugs podemos capturar. Vamos nessa!



O que são testes baseados em propriedades e por que você precisa deles?



Imagine que você está testando uma função de ordenação. Um teste unitário comum:



func TestSort(t *testing.T) {    input := []int{3, 1, 2}    result := sort(input)    expected := []int{1, 2, 3}    if !reflect.DeepEqual(result, expected) {        t.Errorf("got %v, want %v", result, expected)    }}


Ok, funciona. Mas e se o array estiver vazio? E se todos os elementos forem iguais? E se houver números negativos? Você pode escrever mais 10 testes, mas ainda assim vai deixar algo passar.



Testes baseados em propriedades resolvem esse problema radicalmente: você formula invariantes — propriedades que devem ser verdadeiras para qualquer entrada válida. Por exemplo, para ordenação:



  • O comprimento do array ordenado é igual ao comprimento do array original.
  • Cada elemento do array original está presente no array ordenado (e vice-versa).
  • O array está ordenado em ordem crescente.


O framework (por exemplo, gopter ou o nativo testing/quick) gera dados de entrada aleatórios e verifica se todas as propriedades são atendidas. Se houver um bug — você ficará sabendo, mesmo sem saber onde procurar.



Bibliotecas Go para testes baseados em propriedades



No ecossistema Go existem várias ferramentas:



  • testing/quick — pacote nativo, minimalista, mas com capacidades limitadas.
  • gopter — biblioteca poderosa com geradores, redução (minimização de dados de entrada) e configurações flexíveis. Recomendamos esta.
  • rapid — alternativa moderna, mais performática.


Usaremos gopter, pois possui melhor documentação e comunidade. Instalação:



go get github.com/leanovate/gopter


Escrevendo o primeiro teste baseado em propriedades em Go



Suponha que temos uma função Reverse que inverte uma string:



func Reverse(s string) string {    runes := []rune(s)    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {        runes[i], runes[j] = runes[j], runes[i]    }    return string(runes)}


Qual propriedade podemos verificar? Se invertermos a string duas vezes, devemos obter a original:



import (    "testing"    "github.com/leanovate/gopter"    "github.com/leanovate/gopter/gen")

func TestReverseTwice(t *testing.T) { properties := gopter.NewProperties(nil)

properties.Property("reverse(reverse(s)) == s", prop.ForAll( func(s string) bool { return Reverse(Reverse(s)) == s }, gen.AnyString(), ))

properties.TestingRun(t)}


Execute o teste — e o gopter gerará 100 strings aleatórias, verificando a propriedade. Se tudo ok — o teste fica verde. Se não — você receberá um contraexemplo (a menor string na qual o teste falhou).



Bo

Blogs

Book Recommendations