Como é que os testes e a metodologia TDD influenciam a organização do código?
sobes.tech IA
Resposta da IA
Testes e TDD têm uma influência significativa na organização do código:
-
Decomposição e modularidade: Escrever testes antes ou durante o desenvolvimento incentiva a criação de funções e componentes pequenos e facilmente testáveis, o que melhora a modularidade e reduz o acoplamento do código.
-
Limpeza e clareza: Os testes servem como documentação viva e exemplos de uso do código. É difícil escrever testes para código mal projetado e confuso (código espaguete). Isso incentiva a escrever um código mais limpo e compreensível.
-
Melhoria da API: O processo de escrever testes obriga a usar a API dos módulos criados de fora. Isso ajuda a identificar partes incómodas ou ilógicas da API numa fase inicial e a melhorá-la antes que seja amplamente utilizada.
-
Apoio à refatorização: Ter um conjunto completo de testes dá confiança na refatorização. Os testes detectam rapidamente quaisquer regressões, permitindo alterar a estrutura interna do código de forma segura sem alterar o seu comportamento externo.
-
Detecção rápida de erros: Os testes permitem detectar erros nas primeiras fases do desenvolvimento, o que reduz significativamente o custo de os corrigir em comparação com a sua deteção em produção.
Exemplo de organização de código inspirada em TDD (em Golang):
package calculator // Pacote para funcionalidade específica
import "errors" // Dependências
// Add soma dois números inteiros.
func Add(a, b int) int {
return a + b // Lógica simples
}
// Divide divide num por den. Retorna um erro se den for 0.
func Divide(num, den int) (int, error) {
if den == 0 {
// Tratamento explícito de casos limite, fácil de testar
return 0, errors.New("divisão por zero não permitida")
}
return num / den, nil
}
Testes correspondentes:
package calculator // Testes no mesmo pacote, mas em arquivo separado (_test.go)
import (
"testing"
)
// TestAdd verifica a função Add.
func TestAdd(t *testing.T) {
// Casos de teste para diferentes entradas
testCases := []struct {
name string
a, b int
expected int
}{
{"Números positivos", 1, 2, 3},
{"Números negativos", -1, -2, -3},
{"Números mistos", -1, 2, 1},
{"Zero e positivo", 0, 5, 5},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { // Uso de t.Run para estruturar testes
result := Add(tc.a, tc.b) // Chamada da função a testar
if result != tc.expected {
// Mensagem de erro clara
t.Errorf("Add(%d, %d): Esperado %d, obtido %d", tc.a, tc.b, tc.expected, result)
}
})
}
}
// TestDivide verifica a função Divide.
func TestDivide(t *testing.T) {
testCases := []struct {
name string
num, den int
expected int
expectError bool
}{
{"Divisão positiva", 10, 2, 5, false},
{"Resultado negativo", 10, -2, -5, false},
{"Divisão por um", 7, 1, 7, false},
{"Divisão por zero", 5, 0, 0, true}, // Caso de erro
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := Divide(tc.num, tc.den)
if tc.expectError {
if err == nil {
// Verificação de erro esperado
t.Errorf("Divide(%d, %d): Era esperado erro, mas não ocorreu", tc.num, tc.den)
}
} else {
if err != nil {
// Verificação de ausência de erro
t.Errorf("Divide(%d, %d): Erro inesperado: %v", tc.num, tc.den, err)
}
if result != tc.expected {
// Verificação do resultado em sucesso
t.Errorf("Divide(%d, %d): Esperado %d, obtido %d", tc.num, tc.den, tc.expected, result)
}
}
})
}
}