Test edilen kod
Go'nun test araçları standart araç zincirinin bir parçasıdır: testing paketi ve go test komutu. Kurulacak bir framework yok, assertion kütüphanesi gerekmiyor.
Bu sayfadaki örnekler bir başlığı URL slug'ına çeviren küçük bir fonksiyonu, Slugify'ı test eder. İşte çalıştırılabilir hali, main içinde elle yazılmış bir kontrolle:
Tarayıcı editörü main'i çalıştırır; go test çalıştıramaz. Aşağıdaki her şey gerçek bir projede durduğu haliyle yazılmıştır ve her komuttan sonra terminal çıktısı gösterilir. Hepsi Go 1.24 ile çalıştırılmıştır.
İlk test
Bir projede fonksiyon bir pakette yaşar, testleri ise adı _test.go ile biten bir dosyada onun yanında yaşar:
textutil/
├── go.mod module example.com/textutil
├── slug.go package textutil, func Slugify
└── slug_test.go package textutil, the tests
// slug_test.go
package textutil
import "testing"
func TestSlugify(t *testing.T) {
got := Slugify("Hello, World!")
want := "hello-world"
if got != want {
t.Errorf("Slugify(%q) = %q, want %q", "Hello, World!", got, want)
}
}
go test'in kullandığı kurallar:
- Yalnızca
_test.goile biten dosyalar test dosyalarıdır.go buildonları yok sayar, bu yüzden test kodu binary'nize asla girmez. - Bir test, tek bir
*testing.TalanTestXxxadlı bir fonksiyondur (Test'ten sonraki kısım küçük harfle başlamamalıdır). - Bir test, başarısızlık metotlarından birini çağırmadıkça ya da panic olmadıkça geçer.
go test # the package in the current directory
go test ./... # every package in the module
$ go test
PASS
ok example.com/textutil 0.318s
Func(input) = got, want expected başarısızlık mesajı biçimi Go geleneğidir. Girdiyi mesaja koyun: yalnızca got "a", want "b" diyen bir başarısızlık, hangi girdi olduğunu bulmak için sizi koda geri gönderir.
t.Errorf ve t.Fatalf
| Metot | Başarısız işaretler | Testi durdurur |
|---|---|---|
t.Error, t.Errorf | evet | hayır, çalışmaya devam eder |
t.Fatal, t.Fatalf | evet | evet, hemen |
t.Log, t.Logf | hayır | hayır, yalnızca -v ile ya da başarısızlıkta yazdırır |
t.Skip, t.Skipf | hayır | evet, atlandı olarak raporlanır |
Varsayılan olarak Errorf kullanın, böylece tek bir çalıştırma her yanlış alanı bildirir. Testin geri kalanı çalışamayacaksa, genellikle beklenmedik bir hatadan sonra, Fatalf kullanın:
func TestCountWords(t *testing.T) {
path := writeFile(t, "doc.txt", "the quick brown\nfox jumps\n")
got, err := CountWords(path)
if err != nil {
t.Fatalf("CountWords: unexpected error: %v", err) // stop: got is meaningless
}
if got != 5 {
t.Errorf("CountWords = %d, want 5", got)
}
}
Fatal, testi runtime.Goexit'i çağırarak durdurur, bu yüzden testin kendi goroutine'inden çağrılmalıdır. Sizin başlattığınız bir goroutine'den t.Error ile bildirin ve dönün.
t.Run ile tabloya dayalı testler
Çoğu Go testi tablodur: bir durum slice'ı, tek bir döngü ve her duruma kendi adını veren t.Run.
func TestSlugifyTable(t *testing.T) {
tests := []struct {
name, in, want string
}{
{"simple", "Go Testing", "go-testing"},
{"punctuation", "What's new in Go 1.24?", "what-s-new-in-go-1-24"},
{"extra spaces", " lots of space ", "lots-of-space"},
{"non-ascii dropped", "Café au lait", "caf-au-lait"},
{"empty", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Slugify(tt.in); got != tt.want {
t.Errorf("Slugify(%q) = %q, want %q", tt.in, got, tt.want)
}
})
}
}
Bir durum eklemek tek bir satırdır. Her alt test ayrı raporlanır, birinin içindeki bir Fatal yalnızca o alt testi bitirir ve tek birini adıyla çalıştırabilirsiniz. Her şeyi -v ile çalıştırın:
$ go test -v
=== RUN TestSlugify
--- PASS: TestSlugify (0.00s)
=== RUN TestSlugifyTable
=== RUN TestSlugifyTable/simple
=== RUN TestSlugifyTable/punctuation
=== RUN TestSlugifyTable/extra_spaces
=== RUN TestSlugifyTable/non-ascii_dropped
=== RUN TestSlugifyTable/empty
--- PASS: TestSlugifyTable (0.00s)
--- PASS: TestSlugifyTable/simple (0.00s)
--- PASS: TestSlugifyTable/punctuation (0.00s)
--- PASS: TestSlugifyTable/extra_spaces (0.00s)
--- PASS: TestSlugifyTable/non-ascii_dropped (0.00s)
--- PASS: TestSlugifyTable/empty (0.00s)
=== RUN TestCountWords
--- PASS: TestCountWords (0.00s)
=== RUN TestCountWordsMissingFile
--- PASS: TestCountWordsMissingFile (0.00s)
=== RUN ExampleSlugify
--- PASS: ExampleSlugify (0.00s)
PASS
ok example.com/textutil 0.182s
Alt test adlarındaki boşluklar alt çizgiye dönüşür. Bir durum başarısız olduğunda çıktı alt testi ve satırı adlandırır:
--- FAIL: TestSlugifyTable (0.00s)
--- FAIL: TestSlugifyTable/underscore (0.00s)
slug_test.go:27: Slugify("snake_case_name") = "snake-case-name", want "snake_case_name"
FAIL
FAIL example.com/textutil 0.289s
Go 1.22'den beri her döngü yinelemesinin kendi tt'si vardır, bu yüzden alt testler paralel çalışsa bile t.Run içindeki closure'lar doğru durumu görür. Eski kodda bu nedenle döngü gövdesinin başında sık sık tt := tt bulunur; artık gerekmez.
Alt testleri paralel çalıştırmak için alt test fonksiyonunun başında t.Parallel() çağırın. Bunu yalnızca durumlar bağımsız ve bunun fark yaratacağı kadar yavaş olduğunda yapın.
Kullanacağınız go test bayrakları
| Komut | Ne yapar |
|---|---|
go test -v | her testin adını, sonucunu ve t.Log çıktısını yazdırır |
go test -run TestSlugify | adı düzenli ifadeyle eşleşen testleri çalıştırır |
go test -run 'TestSlugifyTable/empty' | tek bir alt testi çalıştırır |
go test -count=1 | önbelleğe alınmış sonuçları yok sayıp yeniden çalıştırır |
go test -race | data race detector ile çalıştırır |
go test -short | uzun testlere kendilerini atlamalarını söyler (if testing.Short() { t.Skip() }) |
go test -failfast | ilk başarısız testten sonra durur |
go test -timeout 30s | çalıştırma daha uzun sürerse başarısız olur (varsayılan 10 dakika) |
go test -cover | ifade kapsamını yazdırır |
go test -bench=. | benchmark'ları da çalıştırır |
Paketleri adlandırdığınızda (go test ., go test ./...), go test kodu ve girdileri değişmemiş paketlerin sonuçlarını önbelleğe alır ve onlardan sonra (cached) yazdırır. Argümansız düz go test asla önbelleğe almaz. Önbellek, bir testin os paketi üzerinden okuduğu dosyaları ve ortam değişkenlerini takip eder, ama bir veritabanı ya da ağ servisi gibi dış durumu takip etmez; bu yüzden birine bağlı bir test, şimdi başarısız olacakken önbellekten geçebilir. -count=1 gerçek bir çalıştırmayı zorlar.
Yardımcılar, geçici dizinler ve temizlik
Yukarıdaki TestCountWords içindeki writeFile çağrısı bir test yardımcısıdır:
// writeFile is a test helper: t.Helper makes failures point at the caller.
func writeFile(t *testing.T, name, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), name) // removed automatically after the test
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("writing %s: %v", name, err)
}
return path
}
t.Helper()fonksiyonu yardımcı olarak işaretler, böylece bir başarısızlık yardımcının içinde değil, onu çağıran testteki satırda raporlanır.t.TempDir()yeni bir dizin oluşturur ve test bittiğinde siler. Testlerin dosyaları kendilerinin temizlemesi hiç gerekmez.t.Cleanup(func() { ... })diğer her türlü sökümü (bir sunucuyu kapatmak, bir tabloyu silmek) kaydeder. Temizlikler test ve alt testlerinden sonra, son kaydedilen ilk olacak şekilde çalışır.t.Setenv("KEY", "value")bir ortam değişkenini yalnızca bu test için ayarlar ve sonra geri yükler.t.Context()(Go 1.24), temizlikler çalışmadan hemen önce iptal edilen bir context döndürür.
Test fixture dosyaları testlerin yanındaki testdata adlı bir dizine gider. go aracı onu paket olarak yok sayar ve testler çalışma dizini olarak paket diziniyle çalışır, bu yüzden os.ReadFile("testdata/input.json") çalışır.
Hataları ve HTTP handler'larını test etmek
Hataları, çağıran kodun yapacağı gibi errors.Is ya da errors.As ile kontrol edin:
func TestCountWordsMissingFile(t *testing.T) {
_, err := CountWords(filepath.Join(t.TempDir(), "nope.txt"))
if !errors.Is(err, fs.ErrNotExist) {
t.Errorf("err = %v, want fs.ErrNotExist", err)
}
}
Hata string'lerini karşılaştırmak, biri bir mesajı yeniden ifade ettiği anda bozulur.
HTTP handler'ları için net/http/httptest size sahte bir ResponseWriter verir, böylece handler'ı ağ olmadan doğrudan çağırabilirsiniz:
func TestHealth(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
rec := httptest.NewRecorder()
healthHandler(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
}
if body := rec.Body.String(); body != "ok\n" {
t.Errorf("body = %q, want %q", body, "ok\n")
}
}
httptest.NewServer, istemci kodunu test etmek için localhost'ta gerçek bir sunucu başlatır. HTTP istemcisi sayfası onu her örnekte kullanıyor.
Kapsam
$ go test -cover
PASS
coverage: 100.0% of statements
ok example.com/textutil 0.578s
$ go test -coverprofile=cover.out
$ go tool cover -func=cover.out
example.com/textutil/slug.go:11: Slugify 100.0%
example.com/textutil/words.go:9: CountWords 100.0%
total: (statements) 100.0%
$ go tool cover -html=cover.out # opens a browser with covered lines in green
Kapsam size hangi satırların hiç çalışmadığını söyler; bu da test edilmemiş dalları bulmak için kullanışlıdır. Yüksek bir sayı, assertion'ların iyi olduğunu söylemez: her fonksiyonu çağırıp hiçbir şeyi kontrol etmeyen bir test %100'e ulaşır.
Benchmark'lar
Bir benchmark func BenchmarkXxx(b *testing.B)'dir. Go 1.24, artık önerilen biçim olan b.Loop'u ekledi:
func BenchmarkSlugify(b *testing.B) {
for b.Loop() {
Slugify("The Go Programming Language, 2nd Edition")
}
}
b.Loop, gövdeyi kararlı bir ölçüm için gerektiği kadar çalıştırır, döngüden önceki kurulum kodunu zamanlamanın dışında tutar ve derleyicinin çağrıyı optimize edip atmasını engeller. Go 1.24'ten önce yazılmış kod, hâlâ çalışan ama pahalı kurulumdan sonra b.ResetTimer() gerektiren ve ölü kod elemesiyle yanıltılabilen for i := 0; i < b.N; i++'yı kullanır.
Benchmark'lar düz go test ile çalışmaz. Onları isteyin ve normal testleri -run='^$' ile atlayın:
$ go test -bench=. -benchmem -run='^$'
goos: darwin
goarch: arm64
pkg: example.com/textutil
cpu: Apple M4
BenchmarkSlugify-10 5061945 238.1 ns/op 168 B/op 5 allocs/op
PASS
ok example.com/textutil 1.410s
Sütunlar şunlardır: GOMAXPROCS eklenmiş ad, çalıştırılan yineleme sayısı, çağrı başına süre, çağrı başına ayrılan bayt, çağrı başına bellek ayırma sayısı. Sayılar makineye bağlıdır; çalıştırmaları aynı makinede karşılaştırın. Bir değişiklik öncesi ve sonrasını güvenilir biçimde karşılaştırmak için her tarafı -count=10 ile birkaç kez çalıştırın ve iki çıktıyı da benchstat'a (golang.org/x/perf/cmd/benchstat) verin.
Örnekler de testtir
Bir örnek fonksiyonu bir şey yazdırır ve beklenen çıktıyı bir yorumda bildirir. go test onu çalıştırır ve çıktı farklıysa başarısız olur; go doc ve pkg.go.dev ise onu dokümantasyon olarak gösterir:
// example_test.go
package textutil_test
import (
"fmt"
"example.com/textutil"
)
func ExampleSlugify() {
fmt.Println(textutil.Slugify("Hello, World!"))
// Output: hello-world
}
textutil_test paket adı bunu bir dış test yapar: gerçek bir çağıran gibi yalnızca dışa açık API'yi kullanabilir. Bu tür dosyalar paketle aynı dizinde durabilir. // Output: yorumu olmadan örnek derlenir ama çalıştırılmaz. Satırlar herhangi bir sırayla görünebiliyorsa // Unordered output: kullanın.
Fuzzing, kısaca
Go 1.18, çökmeleri ve bozulan değişmezleri bulmak için girdi üreten fuzz testlerini ekledi:
func FuzzSlugify(f *testing.F) {
f.Add("Hello, World!") // seed input
f.Fuzz(func(t *testing.T, s string) {
slug := Slugify(s)
if strings.Contains(slug, "--") || strings.HasPrefix(slug, "-") {
t.Errorf("Slugify(%q) = %q: bad hyphens", s, slug)
}
})
}
Düz go test yalnızca tohum girdileri (f.Add değerleri ve testdata/fuzz altındaki kayıtlı dosyalar) çalıştırır. go test -fuzz=FuzzSlugify bir başarısızlık bulana ya da siz durdurana kadar yenilerini üretmeye devam eder ve başarısız girdileri testdata/fuzz altına kaydeder, böylece normal test durumlarına dönüşürler.
Sık yapılan hatalar
- Yanlış yerde ya da yanlış adlandırılmış test dosyası.
_test.goile bitmeli ve paket dizininde durmalıdır.slug_tests.gopakete derlenir, test olarak çalıştırılmaz. Test'ten sonra küçük harf.Testslugifybir test değildir.TestSlugifyveTest_slugifytesttir.- Başka bir goroutine'den
t.Fatal. Oradat.Errorile bildirin ve test dönmeden önce goroutine'i bekleyin. - Girdiyi içermeyen mesajlar. Neyin verildiğini, neyin çıktığını ve neyin beklendiğini dahil edin.
- Önbelleğe alınmış bir başarıya güvenmek. Bir test paketin dışındaki herhangi bir şeye bağlıysa
-count=1kullanın. - Birbirinin sırasına ya da paylaşılan global'lere bağlı testler. Her test kendi durumunu kurmalıdır;
t.TempDir,t.Setenvvet.Cleanupbunun içindir.
Sıkça Sorulan Sorular
Go'da birim testi nasıl yazılır?
Kodla aynı dizinde _test.go ile biten bir dosya oluşturun, testing'i import edin ve kodunuzu çağırıp uyuşmazlıkları t.Errorf ile bildiren bir func TestName(t *testing.T) fonksiyonu yazın. O dizinde go test ya da tüm modül için go test ./... çalıştırın. Framework ya da assertion kütüphanesi gerekmez.
Go'da t.Error ile t.Fatal arasındaki fark nedir?
t.Error ve t.Errorf testi başarısız olarak işaretler ve çalışmaya devam eder, böylece tek bir çalıştırma birkaç sorunu bildirebilir. t.Fatal ve t.Fatalf testi başarısız olarak işaretler ve hemen durdurur. Devam etmenin anlamsız olduğu durumlarda, örneğin sonucu kullanılamaz bırakan beklenmedik bir hatadan sonra, Fatal kullanın.
Go'da tek bir test nasıl çalıştırılır?
-run'a bir düzenli ifade verin: go test -run TestSlugify adı eşleşen her testi çalıştırır, go test -run 'TestSlugifyTable/punctuation' ise tek bir alt testi çalıştırır (alt test adlarındaki boşluklar alt çizgiye dönüşür). Her testin adını ve sonucunu görmek için -v, test önbelleğini atlamak için -count=1 ekleyin.
Go'da benchmark nasıl yazılır?
Bir _test.go dosyasında func BenchmarkName(b *testing.B) yazın ve ölçülecek kodu bir for b.Loop() { ... } döngüsüne koyun (Go 1.24; eski kod for i := 0; i < b.N; i++ kullanır). İşlem başına nanosaniye, bayt ve bellek ayırma raporlayan go test -bench=. -benchmem ile çalıştırın.
Go'da test kapsamı nasıl görülür?
go test -cover, testlerin çalıştırdığı ifadelerin yüzdesini yazdırır. Ayrıntılar için go test -coverprofile=cover.out ile bir profil yazın, sonra fonksiyon başına sayılar için go tool cover -func=cover.out, kapsanan ve kapsanmayan satırları tarayıcıda görmek için go tool cover -html=cover.out çalıştırın.