---
title: 'Tablo Güdümlü Testler'
source: 'https://academia.sh/tr/kurslar/go-uretim/tablo-gudumlu-testler'
course: 'Üretim Go: Test, Araçlar ve Başarım'
language: tr
updated: '2026-09-13T05:08:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir tablo güdümlü test dosyasındaki vaka sayısını kaynaktan sayar, koşan alt test sayısını koşumdan okur ve ikisini karşılaştırır'
    - 't.Run ile açılan alt testlerin adlandırılmasının ve raporlanmasının nasıl çalıştığını koşturarak gösterir'
    - 'Vaka eklemenin kapsama oranını her zaman artırmadığını, aynı satırı koşturan iki vakayla ölçer'
---

# Tablo Güdümlü Testler

Beş vakalık bir tablonun vaka sayısı kaynaktan sayılıyor, koşan alt test sayısı ise yalnız go test -v ile koşturularak okunuyor ve ikisi 5'te eşleşiyor. Sınırlayıcı ölçüm, tabloya aynı satırları koşturan altıncı bir vaka eklendiğinde kapsama oranının 100.0% olarak değişmeden kaldığını gösteriyor.

Önceki ders bir testin verdiği kararın kaynaktan değil koşumdan geldiğini ölçtü; kaç test
işlevi yazıldığı kaynaktan sayılabiliyordu, ama hangisinin geçtiği yalnız `go test`
çalıştırılarak öğrenilebiliyordu. Bu ders aynı ayrımı bir düzey daha ayrıntılı bir yere
taşıyor: tek bir test işlevinin içinde, birden çok **vaka**nın nasıl sınandığına. Vaka
listesinin kendisi bu kez kaynakta yazılı — konunun iki "kaynak" satırından biri — ama
o listenin koştuğunda kaç ayrı alt test doğurduğu yine yalnız koşumdan okunabiliyor.

Bu kalıba tablo güdümlü test deniyor ve Go'ya özgü değil; aynı kalıp başka dillerin test
kütüphanelerinde parametreli test adıyla geçiyor; burada taksonomi tekrar anlatılmıyor. Bu dersin
eklediği soru dar: test paketinin kendi `t.Run` yordamı bir tabloyu alt testlere nasıl bölüyor ve
vaka sayısıyla koşan alt test sayısı arasındaki ilişki gerçekten birebir mi. Bu soruyu yanıtlamak,
kaynaktan sayılabilen bir bilgiyle koşumdan okunan bir bilgiyi aynı ölçümde yan yana koymayı
gerektiriyor.

Üretim kod tabanlarında tek bir işlevi onlarca girdiyle sınamak sık görülen bir ihtiyaç
ve bu ihtiyacı her girdi için ayrı bir `func Test...` yazarak karşılamak hem tekrarlı
hem de okunması zor bir dosya doğuruyor. Tablo güdümlü biçim bu tekrarı bir kez yazılan
bir gövdeye indirgiyor; asıl soru burada, o tek gövdenin arkasında duran vaka sayısının
kaynaktan mı yoksa yalnız koşumdan mı okunabildiği.

`envanter` paketi bu derste de aynı yol dizesi altında kalıyor; yalnız içine bir işlev
daha ekleniyor. Önceki dersteki `ToplamDeger` bir sayı üretiyordu, `Gecerli` ise bir
doğruluk değeri üretiyor — ikisi de girdi olarak aynı `Kalem` tipini alıyor ve bu
süreklilik konunun altı dersi boyunca aynı paketin farklı köşelerinin sınandığını
gösteriyor.

## Vaka Listesi Kaynakta Yazılı

`envanter` paketine bir işlev daha ekleniyor: `Gecerli`, bir kalemin adının boş
olmadığını, adedinin ve fiyatının pozitif olduğunu sırayla denetliyor ve ilk başarısız
denetimde `false` döndürüyor. Bu işlevi sınayan tablo, her satırı bir vakaya karşılık
gelen bir dilim değişkeni olarak yazılıyor.

`Gecerli`nin üç denetimi de aynı biçimde yazıldı: bir koşul, bir erken dönüş. Bu yapı tabloyu
tasarlamayı kolaylaştırıyor, çünkü her denetim için ayrı bir vaka yazmak yeterli — bir vakanın
hangi denetimi tetiklediğini görmek için işlevin gövdesine bakmak gerekmiyor; vakanın kendi adı
(`"bos ad"`, `"sifir adet"`, `"negatif fiyat"`, `"negatif adet"`) hangi denetimi hedeflediğini
zaten söylüyor. Vaka adının kendisi de bir kaynak bilgisi: hangi dalın sınandığını, o dalı yazan
kişi tabloya yazarken bildiriyor.

**TS4.** Aşağıdaki test dosyasındaki `vakalar` dilimi beş satır içeriyor; bu sayı hiçbir
şey çalıştırmadan, yalnız kaynaktaki `{"` ile başlayan satırları sayarak okunabiliyor —
tabloyu kim yazdıysa, kaç vaka eklediği de aynı kişinin elinde, kaynağın kendisinde.

Önceki dersteki `araclar.Kur` ve `araclar.Cagir` yordamları burada da aynen kullanılıyor;
tanımları değişmedi.

```go
// araclar/araclar.go — go komut ailesini gecici bir modulde program icinden cagiran ortak yordamlar
package araclar

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

func Kur(dosyalar map[string]string) string {
	dizin, _ := os.MkdirTemp("", "sinama")
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

func Cagir(dizin string, ekOrtam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ekOrtam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}
```

```go
// olcum.go — vaka sayisi kaynaktan sayiliyor, kosan alt test sayisi kosumdan okunuyor
package main

import (
	"fmt"
	"strings"

	"ders/araclar"
)

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Gecerli(k Kalem) bool {
	if k.Ad == "" {
		return false
	}
	if k.Adet <= 0 {
		return false
	}
	if k.Fiyat <= 0 {
		return false
	}
	return true
}
`

const testMetniBes = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

func vakaSayisi(kod string) int {
	return strings.Count(kod, "\n\t{\"")
}

func main() {
	fmt.Println("-- kaynak: vaka sayisi --")
	fmt.Println("bes vakali tabloda:", vakaSayisi(testMetniBes))

	fmt.Println()
	fmt.Println("-- kosum: kosan alt test sayisi --")
	dizin := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniBes})
	cikti, _ := araclar.Cagir(dizin, nil, "test", "-v", "./...")
	fmt.Println("kosan alt test sayisi:", strings.Count(cikti, "--- PASS: TestGecerli/"))
}
```

```
-- kaynak: vaka sayisi --
bes vakali tabloda: 5

-- kosum: kosan alt test sayisi --
kosan alt test sayisi: 5
```

**TS5.** Kaynaktan sayılan vaka sayısı ile koşumdan okunan alt test sayısı bu ölçümde
eşleşiyor: 5. Bu eşleşme rastlantı değil, `t.Run`'ın kendi kuralının sonucu — döngü
`vakalar` dilimini gezerken her turda `t.Run(v.ad, ...)` çağrısı bir alt test açıyor ve
test paketi bu alt testi çağırana verdiği adla (`v.ad`) raporluyor: çıktıda
`TestGecerli/gecerli`, `TestGecerli/bos_ad` gibi satırlar beliriyor. `strings.Count`
çağrısı bu satırların kaç tanesinin `PASS` ile bittiğini sayıyor ve tablo doğru
yazıldığı sürece bu sayı her zaman `vakalar`ın uzunluğuna eşit çıkıyor — vaka sayısı ile
alt test sayısı arasındaki bağ, döngünün kendisinde kurulu.

Bu eşleşme, tek bir `TestGecerli` işlevinin aslında beş ayrı sınama yürüttüğünü ama
kaynakta beş ayrı işlev olarak görünmediğini gösteriyor. Önceki derste kaynaktan
sayılan şey `func Test` geçişleriydi ve test işlevi sayısını veriyordu; burada sayılan
şey aynı işlevin içindeki veri satırları ve verdiği şey alt test sayısı. İkisi de
kaynaktan okunabilen bir bilgi, ama iki ayrı düzeyde: bir dosyada kaç test işlevi var
ve bir test işlevi kaç alt test açıyor.

Alt test adları da kendi başına küçük bir dönüşümden geçiyor: `v.ad` içindeki boşluk
karakterleri test paketi tarafından alt çizgiye çevriliyor, bu yüzden `"bos ad"` vakası
çıktıda `TestGecerli/bos_ad` olarak görünüyor. Bu dönüşüm ölçümü etkilemiyor çünkü
`strings.Count` yalnız `--- PASS: TestGecerli/` ön ekini arıyor, tam adı değil — ama
kaynaktaki bir vaka adını okuyup çıktıda birebir aynı dizgiyi aramak isteyen biri bu
dönüşümü bilmek zorunda, çünkü kaynaktaki `"bos ad"` ile koşumdaki `bos_ad` aynı vakaya
işaret etse de aynı bayt dizisi değil.

## Sınırlayıcı Ölçüm: Vaka Eklemek Kapsamayı Artırmayabilir

Tabloya bir vaka eklemek sezgisel olarak "daha fazla sınamak" gibi görünüyor. Aşağıdaki
ölçüm bunun her zaman doğru olmadığını gösteriyor: beş vakalık tabloya, `Gecerli`nin
zaten sınadığı bir satırı yeniden koşturan altıncı bir vaka ekleniyor — ad yine boş, ama
adet ve fiyat farklı — ve kapsama oranı iki tabloda da ölçülüyor.

```go
// kapsam.go — ayni satiri kosturan yeni bir vaka kapsama oranini degistirmiyor
package main

import (
	"fmt"
	"strings"

	"ders/araclar"
)

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Gecerli(k Kalem) bool {
	if k.Ad == "" {
		return false
	}
	if k.Adet <= 0 {
		return false
	}
	if k.Fiyat <= 0 {
		return false
	}
	return true
}
`

const testMetniBes = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

const testMetniAlti = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
	{"bos ad, farkli miktar", Kalem{"", 9, 500}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

func vakaSayisi(kod string) int {
	return strings.Count(kod, "\n\t{\"")
}

func kapsamOrani(cikti string) string {
	for _, s := range strings.Split(cikti, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "yok"
}

func main() {
	dizinBes := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniBes})
	ciktiBes, _ := araclar.Cagir(dizinBes, nil, "test", "-cover", "./...")
	fmt.Println("bes vakali tabloda vaka sayisi:", vakaSayisi(testMetniBes))
	fmt.Println("bes vakali tabloda kapsama orani:", kapsamOrani(ciktiBes))

	dizinAlti := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniAlti})
	ciktiAlti, _ := araclar.Cagir(dizinAlti, nil, "test", "-cover", "./...")
	fmt.Println("alti vakali tabloda vaka sayisi:", vakaSayisi(testMetniAlti))
	fmt.Println("alti vakali tabloda kapsama orani:", kapsamOrani(ciktiAlti))
}
```

```
bes vakali tabloda vaka sayisi: 5
bes vakali tabloda kapsama orani: 100.0% of statements
alti vakali tabloda vaka sayisi: 6
alti vakali tabloda kapsama orani: 100.0% of statements
```

**TS6.** Vaka sayısı 5'ten 6'ya çıkıyor, ama kapsama oranı ikisinde de aynı: `100.0% of
statements`. Bunun nedeni kapsama aracının **satır** düzeyinde ölçüm yapması: altıncı vaka,
`Gecerli`nin ilk `if k.Ad == "" { return false }` satırını yeniden koşturuyor — ikinci vaka (`"bos
ad"`) zaten bu satırı bir kez koşturmuştu ve aracın gözünde bir satırın iki kez mi yoksa bir kez
mi koştuğu fark etmiyor, yalnız koşup koşmadığı önemli. Yeni vaka farklı bir `Adet` ve `Fiyat`
taşısa bile, `Gecerli` bu değerlere hiç bakmadan ilk denetimde dönüyor; kapsama aracı bu farkı
göremiyor çünkü onun ölçtüğü şey hangi **satırların** çalıştığı, hangi **değerlerin** denendiği
değil.

Beş vakalık tabloda hangi vakanın hangi satırı koşturduğu tek tek izlenebilir:
`"gecerli"` üç denetimi de geçip son `return true` satırına ulaşıyor, `"bos ad"` ilk
`return false` satırında duruyor, `"sifir adet"` ikinci `return false`'a,
`"negatif fiyat"` üçüncü `return false`'a kadar ilerleyip orada duruyor ve `"negatif adet"` yine
ikinci `return false`'da duruyor — `"sifir adet"` ile aynı satırda, farklı bir vaka
olmasına rağmen. Bu, altı vakalı tabloda neyin tekrarlandığını da netleştiriyor: yeni
vaka `"bos ad, farkli miktar"`, tıpkı `"negatif adet"`in ikinci satırı `"sifir adet"`
ile paylaşması gibi, ilk satırı `"bos ad"` ile paylaşıyor — kapsama aracının gözünde bu
paylaşım hiçbir fark yaratmıyor, ikinci bir dokunuş sayılmıyor.

Bu, konunun ölçü ekseninin bu derse özgü sınırı: kapsama aracı bir koşum bilgisi
üretiyor (kaynağa bakarak bilinemez, yalnız çalıştırılarak öğrenilir), ama ürettiği
bilginin kendisi kaba — bir satırın bir kez mi on kez mi koştuğunu, hangi girdilerle
koştuğunu ayırt etmiyor. Beş vakalık tablo zaten `Gecerli`nin dört erken dönüşünü ve
son `true` dönüşünü birer kez koşturduğu için kapsama zaten en yüksek noktasında;
altıncı vaka bu noktanın üstüne çıkamıyor, çünkü koşacak yeni bir satır kalmamış. Konunun
kapanış dersi bu sınırı genişletiyor ve kapsamayı tamamen farklı bir vaka kümesiyle
yeniden ölçüyor.

Bu ölçümün üretim tarafındaki karşılığı somut: bir gözden geçirme sırasında "kapsama
oranı yüzde yüze çıktı" ifadesi, tabloya son eklenen vakanın gerçekten yeni bir davranışı
sınadığı anlamına gelmiyor. Aynı satırı farklı bir girdiyle yeniden koşturan bir vaka,
kapsama raporunda hiçbir iz bırakmadan tabloyu büyütüyor — dosya uzuyor, koşum süresi
artıyor, ama aracın döndürdüğü sayı aynı kalıyor. Bu yüzden kapsama oranı, bir vakanın
**değerli** olup olmadığına karar vermek için tek başına yeterli bir ölçüt değil; hangi
satırın hâlâ hiç koşmadığını görmek için kapsama aracının satır satır döktüğü ayrıntılı
rapora bakmak gerekiyor — konunun kapanış dersi tam olarak bu ayrıntıyı okuyor.

Kapsama aracının bu kabalığı bir kusur değil, ne ölçtüğünün doğal bir sonucu. Araç
kaynak metnin hangi satırlarının derlenmiş ikilide karşılık bulduğunu ve koşum sırasında
kaç kez tetiklendiğini izliyor; bir satırın **hangi değerle** tetiklendiğini izlemek,
aracın tuttuğu veriyi bambaşka bir düzeye taşırdı. `Gecerli` gibi kısa, dallanması az bir
işlevde bu kabalık beş vakayla bile en yüksek orana ulaşmayı kolaylaştırıyor; daha çok
dallanan bir işlevde aynı oran çok daha fazla vaka gerektirebilir, ama aracın kendisi
her durumda aynı soruyu soruyor: bu satır en az bir kez koştu mu.

## Özet

- Bir tablo güdümlü test dosyasındaki vaka sayısı kaynaktan sayılabiliyor; bu derste
  `vakalar` dilimindeki beş satır bir araç çağrılmadan okundu.
- `t.Run` bir tabloyu alt testlere bölüyor ve her alt testi verilen adla raporluyor; koşan
  alt test sayısı bu derste vaka sayısıyla birebir eşleşti.
- Vaka sayısı kaynaktan, alt test sayısı ise yalnız `go test -v` çalıştırılarak
  okunabiliyor — ikisi aynı sayıya çıksa da geldikleri yol farklı.
- Sınırlayıcı ölçüm: aynı satırı koşturan bir vaka eklemek kapsama oranını değiştirmedi;
  kapsama aracı satırın kaç kez ve hangi değerle koştuğunu değil, yalnız koşup
  koşmadığını sayıyor.
- Sahte bağımlılık ailesi ve kapsama ölçümü Birim Testi ve Test Odaklı Geliştirme
  kursunda kuruldu; burada yeniden anlatılmadı, yalnız Go'nun kendi düzeneği üzerinden
  ölçüldü.

## Sonraki Adım

Bu derste bir tablonun vaka sayısıyla koşan alt test sayısının nasıl eşleştiği ve vaka eklemenin
kapsamayı her zaman artırmadığı ölçüldü. İki ölçüm de aynı `araclar.Kur` ve `araclar.Cagir`
yordamlarına dayandı; bu yordamlar konunun geri kalanında da değişmeden sürüyor — işleyiciyi
doğrudan çağıran HTTP dersi dışında. Sıradaki ders aynı soruyu ağ katmanına taşıyor: bir sunucunun
isteğe verdiği yanıt da yalnız koşturularak öğrenilebilen bir bilgi, ama bu kez gerçek bir
bağlantı noktası hiç açılmadan — sunucu ve istemci tarafı test yardımcıları aynı süreç içinde, ağ
hiç devreye girmeden nasıl kurulabiliyor?
