Jak testy i metodologia TDD wpływają na organizację kodu?
sobes.tech AI
Odpowiedź od AI
Testy i TDD mają istotny wpływ na organizację kodu:
-
Dezaktywacja i modularność: Pisanie testów przed lub w trakcie rozwoju zachęca do tworzenia małych, łatwych do przetestowania funkcji i komponentów, co poprawia modularność i zmniejsza powiązania w kodzie.
-
Czystość i czytelność: Testy służą jako żywa dokumentacja i przykłady użycia kodu. Trudno jest napisać testy dla źle zaprojektowanego, zagmatwanego (kod spaghetti) kodu. To motywuje do pisania czystszego i bardziej zrozumiałego kodu.
-
Ulepszanie API: Proces pisania testów zmusza do korzystania z API tworzonych modułów z zewnątrz. Pomaga to wykryć niewygodne lub nielogiczne części API na wczesnym etapie i poprawić je przed szerokim użyciem.
-
Wsparcie refaktoryzacji: Posiadanie pełnego zestawu testów daje pewność podczas refaktoryzacji. Testy szybko wykrywają regresje, umożliwiając bezpieczne zmiany wewnętrznej struktury kodu bez naruszania jego zewnętrznego zachowania.
-
Szybkie wykrywanie błędów: Testy pozwalają wykrywać błędy na wczesnych etapach rozwoju, co znacznie obniża koszty ich naprawy w porównaniu do wykrywania ich w produkcji.
Przykład organizacji kodu inspirowanej TDD (w Golang):
package calculator // Pakiet dla konkretnego funkcjonalności
import "errors" // Zależności
// Add dodaje dwa całkowite liczby.
func Add(a, b int) int {
return a + b // Prosta logika
}
// Divide dzieli num przez den. Zwraca błąd, jeśli den jest 0.
func Divide(num, den int) (int, error) {
if den == 0 {
// Jawne obsługiwanie przypadków brzegowych, łatwe do przetestowania
return 0, errors.New("dzielenie przez zero niedozwolone")
}
return num / den, nil
}
Odpowiednie testy:
package calculator // Testy w tym samym pakiecie, ale w osobnym pliku (_test.go)
import (
"testing"
)
// TestAdd sprawdza funkcję Add.
func TestAdd(t *testing.T) {
// Przypadki testowe dla różnych danych wejściowych
testCases := []struct {
name string
a, b int
expected int
}{
{"Dodatnie liczby", 1, 2, 3},
{"Ujemne liczby", -1, -2, -3},
{"Mieszane liczby", -1, 2, 1},
{"Zero i dodatnie", 0, 5, 5},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { // Użycie t.Run do strukturyzacji testów
result := Add(tc.a, tc.b) // Wywołanie testowanej funkcji
if result != tc.expected {
// Jasny komunikat o błędzie
t.Errorf("Add(%d, %d): oczekiwano %d, otrzymano %d", tc.a, tc.b, tc.expected, result)
}
})
}
}
// TestDivide sprawdza funkcję Divide.
func TestDivide(t *testing.T) {
testCases := []struct {
name string
num, den int
expected int
expectError bool
}{
{"Pozytywne dzielenie", 10, 2, 5, false},
{"Negatywny wynik", 10, -2, -5, false},
{"Dzielenie przez jeden", 7, 1, 7, false},
{"Dzielenie przez zero", 5, 0, 0, true}, // przypadek błędu
}
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 {
// Sprawdzenie oczekiwanego błędu
t.Errorf("Divide(%d, %d): oczekiwano błędu, ale go nie było", tc.num, tc.den)
}
} else {
if err != nil {
// Sprawdzenie braku błędu
t.Errorf("Divide(%d, %d): nieoczekiwany błąd: %v", tc.num, tc.den, err)
}
if result != tc.expected {
// Sprawdzenie wyniku w przypadku sukcesu
t.Errorf("Divide(%d, %d): oczekiwano %d, otrzymano %d", tc.num, tc.den, tc.expected, result)
}
}
})
}
}