Sobes.tech
Middle

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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)
				}
			}
		})
	}
}