Go'da test yazmak için ek bir kütüphaneye ihtiyacınız yoktur: adı _test.go ile biten bir dosyaya func TestXxx(t *testing.T) imzalı fonksiyonlar yazar, go test komutuyla çalıştırırsınız. Fonksiyon içinde t.Error veya t.Errorf çağrısı testi başarısız sayar ama çalışmayı sürdürür; t.Fatal ise testi o noktada bitirir. Birden çok girdiyi tek fonksiyonda denemek için tablo testleri ve t.Run ile alt testler kullanılır.

Bu yazıda testing paketinin belgesine, go help test ve go help testflag çıktılarına dayanarak gerçek bir paket üzerinde ilerliyoruz: basit test, tablo testi ve alt testler, yardımcı fonksiyonlar için t.Helper, t.Cleanup, t.TempDir ve Go 1.24 ile gelen t.Context, belgede çalışan örnekler (Example fonksiyonları), kara kutu testleri, kapsama ölçümü ve test önbelleği. Tüm çıktılar Go 1.27.3 ile alındı. Performans ölçümü ve fuzzing ayrı bir konu olduğu için Go benchmark ve fuzz testi yazısında ele alınıyor; Go'ya genel bakış için Go öğrenme rehberi sayfasına bakabilirsiniz.

Test edilecek paket

Örneklerin hepsi example.com/hesap modülündeki küçük bir paketi test ediyor. Paket, kuruş cinsinden bir tutara indirim uygulayan, bir başlığı URL dostu hâle getiren ve dışa aktarılmayan bir yuvarlama fonksiyonu içeriyor:

go
// Package hesap, basit fiyat hesapları yapar.
package hesap

import (
	"errors"
	"strings"
	"unicode"
)

// ErrNegatif, eksi tutar verildiğinde döner.
var ErrNegatif = errors.New("tutar negatif olamaz")

// Indirim, kuruş cinsinden tutara yüzde indirimi uygular.
func Indirim(kurus, yuzde int) (int, error) {
	if kurus < 0 {
		return 0, ErrNegatif
	}
	if yuzde > 100 {
		yuzde = 100
	}
	return kurus - kurus*yuzde/100, nil
}

// Slug, başlığı küçük harfe çevirip boşlukları tireyle değiştirir.
func Slug(baslik string) string {
	return strings.ReplaceAll(strings.ToLowerSpecial(unicode.TurkishCase, strings.TrimSpace(baslik)), " ", "-")
}

func yuvarla(kurus int) int {
	return (kurus + 50) / 100 * 100
}

İlk test ve go test çıktısı

İlk test ve go test çıktısı: t.Log / t.Logf, t.Fail, t.Error / t.Errorf, t.FailNow

testing belgesine göre go test, adı Test ile başlayan ve ardından küçük harf gelmeyen, *testing.T parametreli her fonksiyonu çalıştırır. Test dosyaları normal go build sırasında derlenmez, yalnızca go test çalıştığında pakete eklenir. Test dosyası test ettiği paketle aynı paket adını taşıyorsa dışa aktarılmamış adlara da erişebilir; aşağıda yuvarla fonksiyonunu doğrudan test edebilmemiz bu yüzdendir:

go
package hesap

import (
	"errors"
	"testing"
)

func TestIndirim(t *testing.T) {
	got, err := Indirim(10000, 15)
	if err != nil {
		t.Fatalf("beklenmeyen hata: %v", err)
	}
	if got != 8500 {
		t.Errorf("Indirim(10000, 15) = %d; want 8500", got)
	}
}

func TestIndirimNegatif(t *testing.T) {
	_, err := Indirim(-1, 10)
	if !errors.Is(err, ErrNegatif) {
		t.Errorf("hata = %v; want ErrNegatif", err)
	}
}

func TestYuvarla(t *testing.T) {
	if got := yuvarla(1249); got != 1200 {
		t.Errorf("yuvarla(1249) = %d; want 1200", got)
	}
}
bash
$ go test
PASS
ok  	example.com/hesap	0.022s
$ go test -v
=== RUN   TestIndirim
--- PASS: TestIndirim (0.00s)
=== RUN   TestIndirimNegatif
--- PASS: TestIndirimNegatif (0.00s)
=== RUN   TestYuvarla
--- PASS: TestYuvarla (0.00s)
PASS
ok  	example.com/hesap	0.021s

Geçen paket için go test yalnızca özet satırı yazar; -v her testin adını ve sonucunu gösterir. Süreler her çalıştırmada değişir. Mesajlardaki got ... want ... kalıbı Go topluluğunun ve testing belgesinin kullandığı biçimdir: önce elde edilen, sonra beklenen değer yazılır. Hata kontrolünde == yerine errors.Is kullanmak, hata ileride fmt.Errorf("...: %w", err) ile sarmalansa bile testin doğru kalmasını sağlar (bkz. Go hata yönetimi).

MetotNe yaparTest devam eder mi?
t.Log / t.LogfMesajı kaydeder; test başarısızsa ya da -v verilmişse görünürEvet
t.FailTesti başarısız işaretlerEvet
t.Error / t.ErrorfLog ardından FailEvet
t.FailNowBaşarısız işaretler ve runtime.Goexit ile çıkarHayır
t.Fatal / t.FatalfLog ardından FailNowHayır
t.Skip / t.SkipfLog ardından SkipNow; test atlandı sayılırHayır

t.Fatal ailesini, sonraki adımların anlamsız kalacağı durumlarda kullanın; örneğin beklenmeyen bir hata döndüyse dönen değeri karşılaştırmanın anlamı yoktur. Belge önemli bir kısıt da koyar: FailNow, Fatal ve Skip türevleri yalnızca test fonksiyonunu çalıştıran goroutine'den çağrılmalıdır. Testin başlattığı başka bir goroutine'de t.Fatal çağırmak o goroutine'i durdurmaz; go vet içindeki testinggoroutine denetimi bu hatayı arar.

Tablo testleri ve t.Run ile alt testler

Aynı fonksiyonu birçok girdiyle denemenin Go'daki yerleşik yolu, girdileri bir dilimde toplayıp döngüyle dolaşmaktır. Her satırı t.Run ile bir alt teste dönüştürdüğünüzde bir durumun başarısızlığı diğerlerini durdurmaz ve her biri ayrı adla raporlanır. Belgeye göre t.Run, verilen fonksiyonu ayrı bir goroutine'de çalıştırır ve o fonksiyon dönene kadar (ya da t.Parallel çağırana kadar) bekler. Aşağıdaki tablo, ilk sürümde Slug fonksiyonu şöyle yazılmışken çalıştırıldı:

go
func Slug(baslik string) string {
	return strings.ReplaceAll(strings.ToLower(strings.TrimSpace(baslik)), " ", "-")
}
go
package hesap

import "testing"

func TestSlug(t *testing.T) {
	tests := []struct {
		name   string
		baslik string
		want   string
	}{
		{"basit", "Go Test", "go-test"},
		{"bosluklu", "  Merhaba Dünya  ", "merhaba-dünya"},
		{"turkce", "IŞIK Hızı", "ışık-hızı"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Slug(tt.baslik); got != tt.want {
				t.Errorf("Slug(%q) = %q; want %q", tt.baslik, got, tt.want)
			}
		})
	}
}
bash
$ go test -run TestSlug -v
=== RUN   TestSlug
=== RUN   TestSlug/basit
=== RUN   TestSlug/bosluklu
=== RUN   TestSlug/turkce
    slug_test.go:18: Slug("IŞIK Hızı") = "işik-hızı"; want "ışık-hızı"
--- FAIL: TestSlug (0.00s)
    --- PASS: TestSlug/basit (0.00s)
    --- PASS: TestSlug/bosluklu (0.00s)
    --- FAIL: TestSlug/turkce (0.00s)
FAIL
exit status 1
FAIL	example.com/hesap	0.023s

Tablo testi gerçek bir hata yakaladı: strings.ToLower Unicode'un genel kurallarını uygular ve büyük I harfini noktalı i yapar. Türkçede doğru sonuç noktasız ı'dır. Paketteki çözüm, yukarıdaki son sürümde görüldüğü gibi strings.ToLowerSpecial(unicode.TurkishCase, ...) kullanmaktır; bu konunun ayrıntısı Go string, rune, byte ve UTF-8 yazısında. Düzeltmeden sonra üç alt test de geçti.

Alt testlerin adı üst testin adına eğik çizgiyle eklenir: TestSlug/turkce. -run bayrağı çapası olmayan bir düzenli ifade alır ve eğik çizgilerle bölünür; her parça adın ilgili düzeyiyle eşleşmelidir. Böylece tek bir durumu çalıştırabilirsiniz:

bash
$ go test -run 'TestSlug/basit' -v
=== RUN   TestSlug
=== RUN   TestSlug/basit
--- PASS: TestSlug (0.00s)
    --- PASS: TestSlug/basit (0.00s)
PASS
ok  	example.com/hesap	0.024s

t.Helper, t.Cleanup ve t.TempDir

t.Helper, t.Cleanup ve t.TempDir

Karşılaştırma kodunu bir yardımcı fonksiyona taşıdığınızda hata mesajındaki satır numarası yardımcı fonksiyonun içini gösterir, bu da hangi çağrının başarısız olduğunu gizler. t.Helper(), çağıran fonksiyonu yardımcı olarak işaretler ve dosya ile satır bilgisi yazılırken o fonksiyonu atlar. Aşağıdaki testte beklentiyi bilerek yanlış yazdık (200 kuruşa yüzde 50 indirim 100 eder):

go
package hesap

import (
	"os"
	"path/filepath"
	"testing"
)

func esitMi(t *testing.T, got, want int) {
	t.Helper()
	if got != want {
		t.Errorf("got %d; want %d", got, want)
	}
}

func TestYardimci(t *testing.T) {
	got, _ := Indirim(200, 50)
	esitMi(t, got, 150)
}

func TestGeciciDosya(t *testing.T) {
	dir := t.TempDir()
	t.Cleanup(func() { t.Log("temizlik 1") })
	t.Cleanup(func() { t.Log("temizlik 2") })
	yol := filepath.Join(dir, "fiyat.txt")
	if err := os.WriteFile(yol, []byte("8500"), 0o644); err != nil {
		t.Fatal(err)
	}
	b, err := os.ReadFile(yol)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("okunan: %s", b)
}
bash
$ go test -run TestYardimci
--- FAIL: TestYardimci (0.00s)
    yardim_test.go:18: got 100; want 150
FAIL
exit status 1
FAIL	example.com/hesap	0.022s

Hata, çağrının yapıldığı 18. satırı gösteriyor. Aynı testi t.Helper() satırı olmadan çalıştırdığımızda mesaj yardim_test.go:12, yani esitMi içindeki t.Errorf satırı oldu. Aynı dosyadaki ikinci test ise t.TempDir ile t.Cleanup kullanıyor. TempDir her çağrıda benzersiz bir dizin döndürür ve test ile tüm alt testleri bitince dizini otomatik siler. Cleanup ile kaydedilen fonksiyonlar test bitince son eklenen ilk çalışacak şekilde çağrılır:

bash
$ go test -run TestGeciciDosya -v
=== RUN   TestGeciciDosya
    yardim_test.go:33: okunan: 8500
    yardim_test.go:24: temizlik 2
    yardim_test.go:23: temizlik 1
--- PASS: TestGeciciDosya (0.00s)
PASS
ok  	example.com/hesap	0.027s

Cleanup sırası defer ile aynı mantıktadır (bkz. Go defer, panic ve recover), ama bir farkı vardır: yardımcı fonksiyonun içinden de kaydedilebilir ve yardımcı fonksiyon döndüğünde değil, test bittiğinde çalışır. Bu yüzden "bir sunucu başlat ve test bitince kapat" türü yardımcılar için uygundur.

t.Context: test bitince iptal edilen context

Go 1.24 sürüm notlarına göre T.Context ve B.Context, test tamamlandıktan sonra ve temizlik fonksiyonları çalışmadan hemen önce iptal edilen bir context.Context döndürür. Testte başlattığınız goroutine'lere bu context'i verirseniz, Cleanup içinde onların kapanmasını bekleyebilirsiniz:

go
package hesap

import (
	"testing"
)

func TestContext(t *testing.T) {
	ctx := t.Context()
	bitti := make(chan struct{})
	go func() {
		<-ctx.Done()
		close(bitti)
	}()
	t.Cleanup(func() {
		<-bitti
		t.Log("context, Cleanup'tan önce iptal edildi:", ctx.Err())
	})
}

func TestUcYuz(t *testing.T) {
	got, _ := Indirim(500, 150)
	if got != 0 {
		t.Errorf("got %d; want 0", got)
	}
}
bash
$ go test -run TestContext -v
=== RUN   TestContext
    ctx_test.go:16: context, Cleanup'tan önce iptal edildi: context canceled
--- PASS: TestContext (0.00s)
PASS
ok  	example.com/hesap	0.026s

Context'in iptal yayılımı ve zaman aşımı desenleri Go context paketi yazısında. go tool fix help listesindeki testingcontext düzelticisi, testlerde elle kurulan context.WithCancel çağrılarını t.Context ile değiştirmeyi önerir.

Example fonksiyonları: belge ve test bir arada

testing paketi örnek kodu da çalıştırıp doğrular. Adı Example ile başlayan bir fonksiyonun sonunda // Output: ile başlayan bir yorum varsa, fonksiyonun standart çıktısı bu yorumla karşılaştırılır; karşılaştırma baştaki ve sondaki boşlukları yok sayar. Satır sırası önemsizse // Unordered output: kullanılır. Çıktı yorumu olmayan örnekler derlenir ama çalıştırılmaz. Adlandırma kuralı belgeyle eşleşmeyi sağlar: ExampleIndirim fonksiyonu Indirim fonksiyonunun, ExampleSepet_Ekle ise Sepet tipinin Ekle metodunun belgesinde görünür. Aynı ad için birden çok örnek gerekirse küçük harfle başlayan bir ek eklenir: ExampleIndirim_sinir.

Aşağıdaki dosya ayrıca kara kutu testinin nasıl yazıldığını da gösteriyor: paket adı hesap_test olduğu için test edilen paket example.com/hesap olarak içe aktarılıyor ve yalnızca dışa aktarılan adlar kullanılabiliyor. testing belgesi bu yaklaşımı "black box" testi olarak adlandırır; paketinizi kullanıcının gördüğü biçimde test etmenizi sağlar.

go
package hesap_test

import (
	"fmt"

	"example.com/hesap"
)

func ExampleIndirim() {
	fiyat, err := hesap.Indirim(10000, 15)
	fmt.Println(fiyat, err)
	// Output: 8500 <nil>
}

func ExampleSlug() {
	fmt.Println(hesap.Slug("Kış Kampanyası"))
	// Output: kış-kampanyası
}
bash
$ go test -run Example -v
=== RUN   ExampleIndirim
--- PASS: ExampleIndirim (0.00s)
=== RUN   ExampleSlug
--- PASS: ExampleSlug (0.00s)
PASS
ok  	example.com/hesap	0.025s

Çıktı yorumunu kis-kampanyasi olarak değiştirdiğimizde go test beklenen ile elde edileni alt alta gösterdi:

text
--- FAIL: ExampleSlug (0.00s)
got:
kış-kampanyası
want:
kis-kampanyasi
FAIL
exit status 1
FAIL	example.com/hesap	0.024s

Example fonksiyonlarının asıl değeri, go doc ve pkg.go.dev üzerinde belge olarak görünmeleri ve her test çalıştırmasında doğrulandıkları için güncelliğini yitirmemeleridir. Go 1.27 ile go doc -ex bir paketin örneklerini listeleyebiliyor; ayrıntısı Go araçları yazısında.

go test sırasında çalışan go vet

go help test çıktısına göre go test, test ikilisini oluştururken pakete ve test dosyalarına go vet uygular; sorun bulursa testleri hiç çalıştırmaz. Kullanılan denetimler yüksek güvenilirlikli bir alt kümedir: atomic, bools, buildtag, directive, errorsas, ifaceassert, nilfunc, printf, stdversion, stringintconv ve tests. Bunlardan stdversion, Go 1.27 sürüm notlarına göre artık varsayılan olarak çalışıyor ve go.mod dosyasındaki go sürümü için fazla yeni olan standart kütüphane sembollerini raporluyor. Aşağıdaki testte %d fiiline metin verdik:

go
package hesap

import "testing"

func TestVet(t *testing.T) {
	got, _ := Indirim(100, 10)
	t.Logf("sonuç: %d kuruş", "doksan")
	_ = got
}
text
# example.com/hesap
# [example.com/hesap]
./vet_test.go:7:18: (*testing.common).Logf format %d has arg "doksan" of wrong type string
FAIL	example.com/hesap [build failed]

Bu denetimi kapatmak için -vet=off, tüm vet denetimlerini çalıştırmak için -vet=all verilebilir. Biçim fiillerinin listesi Go fmt ve Printf yazısında.

Kapsama (coverage) ölçümü

-cover bayrağı testlerin paketteki deyimlerin yüzde kaçını çalıştırdığını yazar. -coverprofile sonucu bir dosyaya kaydeder ve -cover bayrağını da açar; dosyayı go tool cover -func ile fonksiyon bazında, -html ile tarayıcıda renkli olarak inceleyebilirsiniz. İlk ölçümde yüzde 100'ün altında kaldık:

bash
$ go test -cover
PASS
coverage: 85.7% of statements
ok  	example.com/hesap	0.028s
$ go test -coverprofile=c.out
PASS
coverage: 85.7% of statements
ok  	example.com/hesap	0.033s
$ go tool cover -func=c.out
example.com/hesap/hesap.go:14:	Indirim		80.0%
example.com/hesap/hesap.go:25:	Slug		100.0%
example.com/hesap/hesap.go:29:	yuvarla		100.0%
total:				(statements)	85.7%

Indirim içindeki yuzde > 100 dalı hiçbir testte çalışmamıştı. ctx_test.go dosyasındaki TestUcYuz testi tam bu durumu (yüzde 150 indirim) deniyor; o eklendikten sonra go test -cover çıktısı coverage: 100.0% of statements oldu. Kapsama, hangi kodun hiç denenmediğini gösteren bir araçtır; yüzde 100 olması kodun doğru olduğunu kanıtlamaz. Varsayılan ölçüm kipi set'tir (deyim çalıştı mı?); -covermode=count her deyimin kaç kez çalıştığını sayar.

Test önbelleği ve sık kullanılan bayraklar

Test önbelleği ve sık kullanılan bayraklar: Her testi ve tüm Log çıktısını gösterir, -run regexp, -skip regexp, -count n

go test iki kipte çalışır. Paket argümanı vermeden (go test) çalıştırdığınızda yalnızca bulunduğunuz dizindeki paket test edilir ve önbellek kullanılmaz. Paket listesi verdiğinizde (go test ., go test ./...) başarılı sonuçlar önbelleğe alınır; aynı test ikilisi ve önbelleğe uygun bayraklarla tekrar çalıştırırsanız test yeniden koşmaz, özet satırında süre yerine (cached) yazar:

bash
$ go test .
ok  	example.com/hesap	0.027s
$ go test .
ok  	example.com/hesap	(cached)
$ go test -count=1 ./...
ok  	example.com/hesap	0.026s

Önbelleği bilerek atlamanın geleneksel yolu -count=1 vermektir. Testler paket içindeki dosyaları ya da ortam değişkenlerini okuyorsa, önbellek yalnızca bunlar değişmediğinde eşleşir. go aracı testdata adlı dizini yok sayar; test girdilerinizi oraya koyabilirsiniz.

Bayrakİşlevi
-vHer testi ve tüm Log çıktısını gösterir
-run regexpYalnızca eşleşen testleri, örnekleri ve fuzz testlerini çalıştırır
-skip regexpEşleşenleri atlar
-count nHer testi n kez çalıştırır; -count=1 önbelleği atlar
-shorttesting.Short() true döner; uzun testler kendini kısaltabilir
-failfastİlk başarısızlıktan sonra yeni test başlatmaz
-timeout dVarsayılan 10 dakika; aşılırsa panic
-cover, -coverprofileKapsama ölçümü
-shuffle onTest sırasını karıştırır; sıraya bağımlılığı ortaya çıkarır
-jsonÇıktıyı makinece okunabilir JSON olarak verir

Uzun süren testleri if testing.Short() { t.Skip("kısa kipte atlanıyor") } ile koşullu hâle getirebilirsiniz. Birbirinden bağımsız testleri t.Parallel() ile paralel çalıştırabilirsiniz; belge, t.Setenv ve t.Chdir gibi tüm süreci etkileyen metotların paralel testlerde kullanılamayacağını belirtir. Paket genelinde kurulum ve yıkım gerekiyorsa func TestMain(m *testing.M) tanımlanabilir, ama belge onu düşük seviyeli bir araç olarak tanımlar ve sıradan ihtiyaçlar için gerekli görmez. Yarış koşullarını yakalamak için go test -race kullanılır; bu konu Go veri yarışı ve sync/atomic yazısında.

Sık yapılan hatalar

  • Test fonksiyonunu yanlış adlandırmak: Testindirim çalışmaz, çünkü Test sonrasındaki harf küçük olmamalıdır. Go 1.24 ile eklenen vet tests denetimi bu tür adlandırma ve imza hatalarını raporlar.
  • Her yerde t.Fatal kullanmak: İlk hatada durduğu için diğer sorunları görmezsiniz. Sonraki adımlar anlamlıysa t.Errorf tercih edin.
  • Başka goroutine'den t.Fatal çağırmak: Belgeye göre bu metotlar yalnızca testin kendi goroutine'inden çağrılmalıdır.
  • Önbelleğe takılıp testin çalıştığını sanmak: (cached) görüyorsanız test yeniden koşmadı. Dış kaynaklara bağlı testlerde -count=1 kullanın.
  • Example çıktısında rastgele sıraya güvenmek: Map dolaşımı gibi sırası belirsiz çıktılar için Unordered output kullanın ya da çıktıyı sıralayın.
  • Testleri sıraya bağımlı yazmak: Bir testin bıraktığı durumu diğeri kullanıyorsa -shuffle on ile çalıştırıp sorunu erken görün.

Sık Sorulan Sorular

Test dosyası aynı pakette mi olmalı, _test paketinde mi?

İkisi de geçerlidir ve aynı dizinde birlikte bulunabilir. Aynı paket (beyaz kutu) dışa aktarılmamış fonksiyonları doğrudan test etmenizi sağlar; paketadi_test (kara kutu) ise paketi kullanıcılarınızın göreceği API üzerinden sınar. Örnek fonksiyonları için kara kutu paketi daha doğal sonuç verir.

Go'da assert kütüphanesi kullanmam gerekir mi?

testing paketi karşılaştırma yardımcıları sunmaz; belgedeki ve standart kütüphanedeki testler düz if ve t.Errorf ile yazılır. Ortak karşılaştırmaları kendi t.Helper işaretli fonksiyonlarınızda toplayabilirsiniz. Üçüncü taraf kütüphaneler bu yazının kapsamı dışında.

Yalnızca tek bir testi nasıl çalıştırırım?

go test -run '^TestIndirim$' yazın. -run değeri çapası olmayan bir düzenli ifade olduğu için ^ ve $ olmadan TestIndirim, TestIndirimNegatif ile de eşleşir. Alt test için eğik çizgi kullanın: -run 'TestSlug/turkce'.

HTTP işleyicilerini nasıl test ederim?

net/http/httptest paketinin NewRecorder ve NewServer fonksiyonlarıyla, gerçek bir port açmadan. Örnekler Go net/http web sunucusu ve Go HTTP istemcisi yazılarında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Go öğrenme rehberi · Go benchmark ve fuzz testi · Go paket yapısı · gofmt, go vet ve go doc · Go hata yönetimi · GitHub Actions CI/CD rehberi