Go dla początkujących - Część 7: Testy jednostkowe
Mateusz Kędziora

Cześć! Jeśli zaczynasz swoją przygodę z językiem Go, to ten artykuł jest dla Ciebie. Dzisiaj zajmiemy się czymś niezwykle ważnym w programowaniu – testowaniem jednostkowym. Testy jednostkowe pozwalają nam upewnić się, że poszczególne części naszego kodu działają tak, jak tego oczekujemy. Dzięki nim możemy szybciej wykrywać błędy i tworzyć bardziej niezawodne aplikacje.
W Go testowanie jest bardzo proste dzięki wbudowanemu pakietowi testing
. Nie musisz instalować żadnych dodatkowych bibliotek. W tym artykule pokażemy Ci, jak korzystać z testing
, aby pisać testy dla różnych rodzajów kodu: funkcji, obsługi błędów i kodu współbieżnego. Nauczymy się też, jak stosować testowanie tabelaryczne i benchmarki, które pomogą nam ulepszyć nasze testy.
Co to jest testowanie jednostkowe?
Zanim przejdziemy do kodu, warto zrozumieć, czym jest testowanie jednostkowe. Wyobraź sobie, że budujesz dom. Testowanie jednostkowe to jak sprawdzanie, czy każda cegła, każdy element konstrukcyjny, jest solidny i dobrze wykonany, zanim przejdziesz do kolejnych etapów.
W programowaniu, test jednostkowy sprawdza działanie jednej, małej części kodu – najczęściej funkcji lub metody. Celem jest upewnienie się, że ta konkretna część działa poprawnie w różnych warunkach. Testy jednostkowe pomagają nam:
- Wykrywać błędy na wczesnym etapie: Łatwiej jest znaleźć i naprawić błąd w małej funkcji, niż szukać go w całym programie.
- Zwiększać pewność siebie: Mając dobrze przetestowany kod, możesz wprowadzać zmiany z większą pewnością, że nie zepsujesz czegoś innego.
- Ułatwiać refaktoryzację: Testy dają pewność, że zmiany w kodzie nie zepsują jego funkcjonalności.
- Dokumentować kod: Testy pokazują, jak dana funkcja powinna działać i w jakich warunkach.
Pakiet testing
- Twój pomocnik w testowaniu
Pakiet testing
w Go jest Twoim najlepszym przyjacielem, jeśli chodzi o pisanie testów. Zawiera wszystko, czego potrzebujesz, aby tworzyć i uruchamiać testy jednostkowe. Aby rozpocząć, musisz:
- Utworzyć plik testowy: Plik testowy ma taką samą nazwę jak plik, który testujesz, z dodatkiem
_test
. Na przykład, jeśli masz plikmath.go
, Twój plik testowy będzie się nazywałmath_test.go
. - Zaimportować pakiet
testing
: Na początku pliku testowego dodaj import pakietutesting
. - Napisać funkcję testową: Funkcje testowe mają nazwy zaczynające się od
Test
i przyjmują argument typu*testing.T
.
Przykład: Testowanie prostej funkcji
Załóżmy, że mamy prostą funkcję w pliku math.go
:
// math.go
package main
// Add dodaje dwie liczby całkowite.
func Add(a, b int) int {
return a + b
}
Teraz stwórzmy plik testowy math_test.go
:
// math_test.go
package main
import "testing"
// TestAdd sprawdza funkcję Add.
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) returned %d, expected %d", result, 5)
}
}
Omówienie kodu:
package main
: Plik testowy jest w tym samym pakiecie, co testowany kod.import "testing"
: Importujemy pakiettesting
.func TestAdd(t *testing.T) { ... }
: Definiujemy funkcję testową o nazwieTestAdd
.result := Add(2, 3)
: Używamy funkcjiAdd
, którą testujemy.if result != 5 { ... }
: Sprawdzamy, czy wynik jest taki, jakiego oczekujemy.t.Errorf(...)
: Jeżeli wynik jest niepoprawny, funkcjat.Errorf
wypisuje komunikat o błędzie.
Uruchamianie testów
Aby uruchomić test, otwórz terminal w katalogu zawierającym pliki math.go
i math_test.go
, a następnie wykonaj polecenie:
go test
Jeśli test przejdzie pomyślnie, nie zobaczysz żadnych komunikatów. W przypadku niepowodzenia, zobaczysz informacje o błędach.
Testowanie obsługi błędów
W prawdziwym świecie funkcje często zwracają błędy. Musimy więc testować, czy nasza funkcja poprawnie obsługuje błędy. Załóżmy, że mamy funkcję dzielącą dwie liczby, która zwraca błąd, gdy dzielnik jest równy zero:
// math.go
package main
import "errors"
// Divide dzieli dwie liczby całkowite, zwraca błąd, gdy b jest równe 0.
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("dzielenie przez zero")
}
return a / b, nil
}
Oto jak możemy przetestować tę funkcję:
// math_test.go
package main
import (
"errors"
"testing"
)
// TestDivide sprawdza funkcję Divide.
func TestDivide(t *testing.T) {
// Test poprawny wynik dzielenia
result, err := Divide(10, 2)
if err != nil {
t.Errorf("Oczekiwano nil, a wystąpił błąd: %v", err)
}
if result != 5 {
t.Errorf("Divide(10, 2) returned %d, expected %d", result, 5)
}
// Test dzielenie przez zero
_, err = Divide(10, 0)
if err == nil {
t.Errorf("Oczekiwano błędu dzielenia przez zero, ale go nie otrzymano")
}
if !errors.Is(err, errors.New("dzielenie przez zero")){
t.Errorf("Oczekiwano błędu 'dzielenie przez zero' a otrzymano: %v", err)
}
}
Omówienie kodu:
- Testujemy zarówno poprawny wynik dzielenia, jak i sytuację, gdy dzielimy przez zero.
- Sprawdzamy, czy funkcja
Divide
zwraca oczekiwany błąd (dzielenie przez zero
) gdy dzielimy przez zero. - Wykorzystujemy
errors.Is
do sprawdzenia konkretnego rodzaju błędu.
Testowanie kodu współbieżnego
Go jest znany ze swojego wsparcia dla współbieżności (ang. concurrency). Testowanie kodu, który wykorzystuje gorutyny i kanały, może być trudniejsze, ale pakiet testing
pomaga nam i w tym. Załóżmy, że mamy funkcję, która wysyła liczby do kanału:
// concurrent.go
package main
// SendNumbers wysyła liczby od 0 do n-1 do kanału.
func SendNumbers(n int, ch chan int) {
for i := 0; i < n; i++ {
ch <- i
}
close(ch)
}
Oto jak możemy przetestować tę funkcję:
// concurrent_test.go
package main
import (
"testing"
)
// TestSendNumbers sprawdza funkcję SendNumbers.
func TestSendNumbers(t *testing.T) {
ch := make(chan int)
go SendNumbers(5, ch)
expected := []int{0, 1, 2, 3, 4}
i := 0
for val := range ch {
if val != expected[i] {
t.Errorf("Otrzymano %d, oczekiwano %d", val, expected[i])
}
i++
}
if i != len(expected){
t.Errorf("Oczekiwano %d liczb, a otrzymano %d", len(expected), i)
}
}
Omówienie kodu:
- Tworzymy kanał
ch
. - Uruchamiamy
SendNumbers
w osobnej gorutynie, żeby nie zablokować testu. - Sprawdzamy, czy liczby odbierane z kanału są takie, jakich oczekujemy.
- Dodatkowo sprawdzamy, czy otrzymaliśmy wszystkie elementy.
Testowanie tabelaryczne
Często mamy do przetestowania funkcję z wieloma różnymi przypadkami testowymi. W takim przypadku warto skorzystać z testowania tabelarycznego, które sprawia, że nasz kod staje się bardziej czytelny i zwięzły. Spójrzmy na przykład:
// stringutil.go
package main
import "strings"
// Reverse odwraca 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)
}
A oto jak możemy przetestować tę funkcję za pomocą testowania tabelarycznego:
// stringutil_test.go
package main
import "testing"
// TestReverse sprawdza funkcję Reverse za pomocą testowania tabelarycznego.
func TestReverse(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{"hello", "olleh"},
{"Go", "oG"},
{"", ""},
{"a", "a"},
{"12345", "54321"},
}
for _, tc := range testCases {
result := Reverse(tc.input)
if result != tc.expected {
t.Errorf("Reverse(%q) returned %q, expected %q", tc.input, result, tc.expected)
}
}
}
Omówienie kodu:
- Definiujemy strukturę
testCases
, która zawiera input oraz oczekiwany wynik. - Iterujemy po strukturze i dla każdego testu wywołujemy testowaną funkcję.
- Porównujemy uzyskany wynik z oczekiwanym.
- Dzięki takiemu zapisowi kod testowy jest bardziej czytelny i łatwiejszy w rozbudowie.
Benchmarki - Mierzenie wydajności
Testy to nie tylko sprawdzanie poprawności, ale również mierzenie wydajności. Benchmarki pozwalają nam na zbadanie, jak szybko działa nasz kod. W Go możemy to zrobić za pomocą funkcji z przedrostkiem Benchmark
w pakiecie testing
. Spójrzmy jak to wygląda na przykładzie naszej funkcji Reverse
:
// stringutil_test.go
package main
import "testing"
// BenchmarkReverse mierzy wydajność funkcji Reverse.
func BenchmarkReverse(b *testing.B) {
input := "abcdefghijklmnopqrstuvwxyz"
for i := 0; i < b.N; i++ {
Reverse(input)
}
}
Omówienie kodu
- Funkcja
BenchmarkReverse
ma argument typu*testing.B
. - Pętla
for
wykonuje sięb.N
razy.b.N
jest liczbą iteracji dostosowaną przez pakiettesting
w celu uzyskania wiarygodnych wyników. - Uruchamiamy benchmark za pomocą
go test -bench=.
:
go test -bench=.
Wyniki benchmarka pokazują, ile operacji można wykonać na sekundę i ile czasu zajmuje jedna operacja. Jest to przydatne, gdy chcemy zoptymalizować nasz kod.
Podsumowanie
Testowanie jednostkowe to kluczowy element tworzenia niezawodnego kodu. Dzięki pakietowi testing
w Go pisanie testów jest proste i przyjemne. Nauczyliśmy się, jak testować funkcje, obsługę błędów, kod współbieżny, stosować testowanie tabelaryczne i benchmarki. Pamiętaj, że im więcej piszesz testów, tym lepszy będzie Twój kod!
Praca domowa
- Napisz testy dla funkcji obliczającej silnię liczby (np.
Factorial
). - Stwórz funkcję, która pobiera listę liczb i zwraca ich sumę. Napisz testy, które przetestują różne scenariusze, w tym pusta lista i lista z ujemnymi liczbami.
- Napisz benchmark dla funkcji sortowania (możesz użyć
sort.Ints
z pakietusort
).
Zachęcam Cię do eksperymentowania z testami i eksplorowania różnych możliwości, jakie oferuje pakiet testing
. Im więcej będziesz praktykować, tym bardziej poczujesz się pewnie w pisaniu testów w Go. Nie bój się zaglądać do dokumentacji i korzystać z dostępnych zasobów. Przeczytaj też pozostałe artykuły na naszym blogu, aby jeszcze bardziej rozwinąć swoje umiejętności programistyczne w Go.
Przydatne linki
- Oficjalna dokumentacja pakietu
testing
- Tutorial na temat testowania w Go
- Kurs Go online
- Efektywne programowanie w Go
Pamiętaj, że nauka programowania to proces ciągłego rozwoju. Nie zniechęcaj się, gdy coś nie wychodzi. Eksperymentuj, pytaj i nie przestawaj się uczyć! Powodzenia!
Polecane artykuły
Docker vs Kubernetes: Który dla Ciebie w 2025?
Docker i Kubernetes objaśnione! Która technologia lepsza dla początkujących w 2025? Porównanie, przykłady i przyszłość.
Mateusz Kędziora
DevOps: Automatyzacja zadań sysadmina dla programistów
Zautomatyzuj pracę sysadmina w środowisku DevOps! Praktyczne przykłady, skrypty, Ansible, Terraform, Prometheus i Grafana.
Mateusz Kędziora
Automatyzacja Linux/macOS z Bash: Praktyczny Przewodnik
Zacznij automatyzować system Linux/macOS z Bash! Dowiedz się, czym jest Bash, jak pisać skrypty i używać podstawowych komend.
Mateusz Kędziora