Ders 06 / 16
Kapsam Raporlama
Aynı işlev üç ayrı test kümesiyle sınandığında kapsama oranı 57.1%, 85.7% ve 100.0% olarak üç ayrı değer alıyor; kapsanmayan blok sayısı da sırasıyla 3, 1 ve 0'a düşüyor. Sınırlayıcı ölçüm, %100 kapsama veren üçüncü kümenin, dönüş değerini hiç denetlemediği bir dalda gerçek bir hatayı hiç yakalamadığını hatalı bir kaynakla koşturarak gösteriyor.
Yayın Güncelleme
Bu derste
- Aynı işlevi üç farklı test kümesiyle sınayıp kapsama oranını ve kapsanmayan blok sayısını koşumdan okur
- Kapsama oranının koşulan blokları saydığını, hangi iddiaların gerçekten çalıştığını saymadığını ölçer
- %100 kapsamanın doğruluk göstermediğini, gerçek bir hatayı yakalamayan tam kapsamalı bir test kümesiyle sınırlar
İçindekiler
Önceki ders bir programa dair on bir bilgiyi kaynak, araç ve koşum sınıflarına ayırdı;
bu on birin biri — kapsanan satır oranı — o derste yalnız tek bir sayı
(80.0% of statements) olarak geçti, ayrıntısına girilmedi. Bu son ders konunun kapanışı ve o
ayrıntıyı açıyor: kapsama oranı tam olarak neyi sayıyor ve bu sayı yükseldiğinde
gerçekte ne değişiyor?
Kapsama oranı üretim kod tabanlarında sık kullanılan bir gösterge: bir gözden geçirme,
bir eşik (“yeni kod en az şu oranda kapsanmalı”) ya da bir panoda görünen tek bir yüzde.
Bu göstergenin ne söylediği ve ne söylemediği, bu konunun beş dersi boyunca kurulan
“bu bilgiyi kim söylüyor” sorusunun son sınavı — çünkü kapsama, tek başına
bakıldığında, bir programın sınandığını değil, yalnız çalıştırıldığını kanıtlıyor. Bu
soruyu yanıtlamak için konu boyunca kullanılan envanter paketine son bir kez dönülüyor;
bu kez paketin kendisi değil, aynı paketi sınayan üç farklı test kümesi konu.
Aynı İşlev, Üç Test Kümesi, Üç Oran
envanter paketine son bir işlev ekleniyor: Etiketle, bir kalemin adedine bakıp dört
durumdan birini döndürüyor — negatif bir adet "gecersiz", sıfır "tukendi", üçün altı
"az kaldi", geri kalanı "yeterli". Dört durumun her biri kendi return satırında
ve dört durum birbirini dışlıyor: bir çağrı yalnız birine düşüyor.
Bu işlev bilinçli olarak dallanma sayısı ayarlanabilir bir örnek olarak kuruldu — dört
ayrı dal, dört ayrı test vakası ve her vakanın kapsamaya kaç ayrı blok eklediği tek tek
izlenebilir. Gecerli (Tablo Güdümlü Testler) ve StokAzalt (Sahteleme ve Arayüz
Ayrımı) da benzer dallı yapılar taşıyordu; bu ders aynı yapıyı, kapsama aracının bu
dallanmayı nasıl saydığını görmek için yeniden kullanıyor.
Üç test kümesi de aynı envanterMetni kaynağına karşı koşuyor — Etiketle’nin kendisi
hiç değişmiyor, değişen yalnız hangi durumların çağrıldığı. Bu, kapsama oranının bir
kaynağın özelliği değil, bir kaynak-test çiftinin özelliği olduğunu gösteriyor: aynı
kaynağın kapsama oranı, hangi test kümesiyle sorulduğuna göre 57.1% ile 100.0% arasında
herhangi bir değer alabiliyor.
Kur ve Cagir yordamları konunun ilk dersinden beri değişmedi; bu ders de her test
kümesi için tamamen boş bir geçici modül kuruyor, önceki bir koşumun izini taşımadan.
TS18. Aynı Etiketle işlevi, üç farklı test kümesiyle sınanıyor: T1 yalnız
“yeterli” durumunu sınıyor, T2 üç durumu (“yeterli”, “tukendi”, “az kaldi”) sınıyor,
T3 dördünü de çağırıyor.
// araclar.go — kurulum, cagri ve kapsama okuma yordamlari, ve konu boyunca kullanilan Etiketle
package main
import (
"os"
"os/exec"
"path/filepath"
"strings"
)
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
}
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"
}
// kapsanmayanBlok, kapsama profilindeki hic kosmamis blok sayisini sayar.
func kapsanmayanBlok(profil []byte) int {
n := 0
for _, satir := range strings.Split(string(profil), "\n")[1:] {
alanlar := strings.Fields(satir)
if len(alanlar) == 3 && alanlar[2] == "0" {
n++
}
}
return n
}
const envanterMetni = `package sinama
type Kalem struct {
Ad string
Adet int
Fiyat int
}
func Etiketle(k Kalem) string {
if k.Adet < 0 {
return "gecersiz"
}
if k.Adet == 0 {
return "tukendi"
}
if k.Adet < 3 {
return "az kaldi"
}
return "yeterli"
}
`
const testMetniT3 = `package sinama
import "testing"
func TestEtiketle(t *testing.T) {
if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
t.Errorf("got %s", got)
}
if got := Etiketle(Kalem{"kalem", 0, 250}); got != "tukendi" {
t.Errorf("got %s", got)
}
if got := Etiketle(Kalem{"kalem", 2, 250}); got != "az kaldi" {
t.Errorf("got %s", got)
}
Etiketle(Kalem{"kalem", -1, 250})
}
`
// olcum.go — uc test kumesiyle uc ayri kapsama orani ve kapsanmayan blok sayisi
package main
import (
"fmt"
"os"
"path/filepath"
)
const testMetniT1 = `package sinama
import "testing"
func TestEtiketleYeterli(t *testing.T) {
if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
t.Errorf("got %s", got)
}
}
`
const testMetniT2 = `package sinama
import "testing"
func TestEtiketle(t *testing.T) {
if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
t.Errorf("got %s", got)
}
if got := Etiketle(Kalem{"kalem", 0, 250}); got != "tukendi" {
t.Errorf("got %s", got)
}
if got := Etiketle(Kalem{"kalem", 2, 250}); got != "az kaldi" {
t.Errorf("got %s", got)
}
}
`
func main() {
fmt.Println("-- kosum: uc test kumesiyle uc ayri oran --")
kumeler := []struct{ ad, metin string }{
{"T1", testMetniT1},
{"T2", testMetniT2},
{"T3", testMetniT3},
}
for _, k := range kumeler {
dizin := Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": k.metin})
cikti, _ := Cagir(dizin, nil, "test", "-coverprofile=kapsam.out", "./...")
profil, _ := os.ReadFile(filepath.Join(dizin, "kapsam.out"))
fmt.Println(k.ad, "kapsama orani:", kapsamOrani(cikti),
"kapsanmayan blok sayisi:", kapsanmayanBlok(profil))
}
}
-- kosum: uc test kumesiyle uc ayri oran -- T1 kapsama orani: 57.1% of statements kapsanmayan blok sayisi: 3 T2 kapsama orani: 85.7% of statements kapsanmayan blok sayisi: 1 T3 kapsama orani: 100.0% of statements kapsanmayan blok sayisi: 0
TS19. Vaka sayısı önceki iki derste de kaynaktan sayılabiliyordu; burada da sayılabilir
— T1 bir çağrı, T2 üç, T3 dört. Ama kapsama oranının kendisi kaynaktan hiç
okunamıyor: aynı Etiketle işlevi değişmeden kalırken, yalnız hangi test kümesinin onu
çağırdığı değiştiğinde oran 57.1%‘den 100.0%‘e çıkıyor. -coverprofile bayrağıyla
üretilen profil dosyası bu oranın hangi bloklardan geldiğini de veriyor:
kapsanmayanBlok, profildeki her satırı okuyup son sütunu sıfır olanları sayıyor — bu
sütun, o bloğun koşum boyunca kaç kez çalıştığı. T1’de üç blok hiç çalışmıyor (üç
return satırı), T3’te hiçbiri.
Kapsama aracının kendisi bir koşum bilgisi üretiyor, çünkü hangi bloğun çalışıp çalışmadığı ancak testler fiilen koşturulduktan sonra biliniyor — kaynağı okuyan biri dört durumun var olduğunu görebilir, ama hangi test kümesinin bu dört durumdan kaçını tetiklediğini kaynaktan çıkaramaz. Bu, Tablo Güdümlü Testler’de ölçülen ayrımın bu konudaki son görünümü: bir sayı (vaka sayısı) kaynakta duruyor, bir başka sayı (kapsama oranı) yalnız koşarak ortaya çıkıyor.
Üç kümenin de aynı yordamla — Kur, Cagir — koşturulduğuna dikkat etmek gerekiyor;
değişen yalnız envanter_test.go dosyasının içeriği. Cagir bu kez -coverprofile
bayrağıyla çağrılıyor ve bu bayrak go test’e yalnız bir oran değil, oranın dayandığı
ham veriyi de bir dosyaya yazmasını söylüyor. -cover bayrağı yalnız özet oranı
veriyordu (önceki derste kullanılan biçim); -coverprofile aynı bilgiyi blok blok
kaydediyor ve bu ders bu ayrıntıyı kapsanmayanBlok yordamıyla okuyor. Profil dosyasının
kendisi de bu programın çalışma dizininde geçici olarak duruyor ve hiçbir yerde yolu
basılmıyor — yalnız içeriğinden türetilen sayı okunuyor.
Sınırlayıcı Ölçüm: Yüzde Yüz Kapsama Doğruluk Göstermiyor
T3 dört durumun dördünü de çağırıyor ve son çağrısı —
Etiketle(Kalem{"kalem", -1, 250}) — dönüş değerini hiç almıyor, hiç denetlemiyor. Bu satır kapsama aracının gözünde
tam bir sınama: Etiketle çağrıldı, ilgili blok çalıştı, oran %100’e çıktı. Ama bu
çağrının doğru bir sonuç verip vermediği hiçbir yerde sınanmadı.
Bunun bir yazım hatası olmadığını vurgulamak gerekiyor — T3’ün kaynağına bakan biri
dördüncü çağrıyı görüyor, Etiketle’nin dördüncü durumunun çağrıldığını doğruluyor.
Eksik olan, bir if got := ...; got != "beklenen" kalıbının o çağrıya da eklenmemiş
olması; üçüncü göze kadar bu kalıp hep vardı, dördüncüde düşürüldü. Kapsama aracı bu
düşüşü fark etmiyor çünkü onun sorduğu soru “bu satır çalıştı mı”, “bu satırın sonucu bir
iddiaya bağlandı mı” değil.
Bu, Sahteleme ve Arayüz Ayrımı’nda ölçülen sınırla da bağlantılı: orada bir sahtenin gerçeğin bütün varsayımlarını taşımadığı gösterilmişti, burada bir test kümesinin kendisinin de kendi iddialarının bütün dalları kapsamadığı gösteriliyor — ikisi de aynı kökten: bir sınamanın “geçmesi”, sınamanın kapsamı dışında kalan hiçbir şey hakkında bir güvence taşımıyor.
TS20. Aşağıdaki ölçüm bunu somutlaştırıyor: Etiketle’nin hatalı bir sürümü — negatif
adedi de "tukendi" diye etiketleyen, "gecersiz" durumunu hiç üretmeyen bir sürüm — aynı
T3 test kümesiyle koşturuluyor.
// sinir.go — T3 tam kapsamayla, hatali bir kaynakta ne diyor
package main
import (
"fmt"
"strings"
)
const envanterBozukMetni = `package sinama
type Kalem struct {
Ad string
Adet int
Fiyat int
}
func Etiketle(k Kalem) string {
if k.Adet < 0 {
return "tukendi"
}
if k.Adet == 0 {
return "tukendi"
}
if k.Adet < 3 {
return "az kaldi"
}
return "yeterli"
}
`
func main() {
fmt.Println("-- sinirlayici olcum: T3 tam kapsamayla, hatali kaynakta ne diyor --")
dizin := Kur(map[string]string{"envanter.go": envanterBozukMetni, "envanter_test.go": testMetniT3})
cikti, _ := Cagir(dizin, nil, "test", "-cover", "-v", "./...")
gecti := strings.Contains(cikti, "--- PASS: TestEtiketle")
fmt.Println("test gecti mi:", gecti, "kapsama orani:", kapsamOrani(cikti))
}
-- sinirlayici olcum: T3 tam kapsamayla, hatali kaynakta ne diyor -- test gecti mi: true kapsama orani: 100.0% of statements
TS21. Hatalı sürümde negatif bir adet artık "gecersiz" değil "tukendi"
döndürüyor — gerçek bir hata, çünkü bu iki durum envanter paketinin geri kalanında
farklı anlamlar taşıyabilir (stoğu biten bir kalemle stok sayımı bozulmuş bir kalem aynı
şey değil). Ama T3 bu hatayı hiç yakalamıyor: test hâlâ geçiyor, kapsama oranı hâlâ
100.0%. Nedeni basit — T3’ün son satırı Etiketle’yi çağırıyor ama döndürdüğü
değerle hiçbir şey yapmıyor; kapsama aracı bu satırın çalıştığını görüyor, çalışan
satırın doğru bir değer ürettiğini hiç sormuyor.
İki koşumun (doğru kaynak, hatalı kaynak) kapsama oranı da bayt bayt aynı: ikisinde de
100.0% of statements. Kapsama aracı iki kaynak arasındaki farkı hiç görmüyor, çünkü
fark bir satırın çalışıp çalışmadığında değil, çalışan bir satırın hangi değeri
ürettiğinde. Bu, konunun her araç bilgisi için geçerli olan aynı sınırın kapsamaya
uygulanmış hâli: bir araç yalnız kendi sorduğu soruyu yanıtlıyor ve kapsama aracının
sorduğu soru hep aynı — bu satır çalıştı mı — hangi değeri ürettiği hiç sorulmuyor.
Bu, konunun altı dersi boyunca tekrar eden aynı sınırın son biçimi: Test Paketi
Temelleri’nde hiçbir şey sınamayan bir test geçiyordu, burada hiçbir şey sınamayan tek
bir çağrı, geri kalan üç iddiası sağlam bir test kümesinin kapsama oranını %100’e
taşıyor. Kapsama aracı koşulan blokları sayıyor, doğru sınanan blokları değil —
ve bu iki sayı, T3 örneğinde olduğu gibi, aynı görünüp farklı bir şey söyleyebiliyor.
Yüzde yüz kapsamalı bir test kümesi, kaynağın her satırının en az bir kez çalıştığının
garantisi; kaynağın her satırının doğru çalıştığının garantisi değil.
Bunun üretim tarafındaki karşılığı somut: bir kod tabanında kapsama oranı %100’e yaklaştıkça, ekleyen kişiler genellikle bu oranı bir tamamlanmışlık göstergesi olarak okuyor. Yukarıdaki ölçüm bu okumanın eksik olduğunu gösteriyor — oran, satırların çalıştığını söylüyor, o satırların ürettiği değerlerin doğrulandığını söylemiyor. Bir kod tabanı yüzde yüz kapsamayla da hatalı davranabiliyor, çünkü kapsama aracının kendisi bir iddianın var olup olmadığını hiç sormuyor — yalnız bir satırın çalışıp çalışmadığını sayıyor. Bu, Test Paketi Temelleri’nde kurulan “geçen bir test doğru olduğunu söylemez” iddiasının, kapsama oranına taşınmış hâli: yüksek bir kapsama oranı da doğruluğu söylemiyor, yalnız neyin çalıştırıldığını söylüyor.
Bu son ölçüm, kapsamanın kendisinin de bir gösterge olduğunu, hedef olmadığını gösteriyor: %100’e ulaşmak kolay, ulaşırken hangi soruların hâlâ yanıtsız kaldığını görmek zor. Kapsama aracı bu zorluğu çözmüyor, yalnız hangi satırların hiç çalıştırılmadığını gösteriyor — geri kalanı, bir iddianın gerçekten o satırın ürettiği değeri denetleyip denetlemediği, yazan kişinin elinde kalıyor.
Özet
- Aynı
Etiketleişlevi üç test kümesiyle sınandığında kapsama oranı üç farklı değer aldı (57.1%, 85.7%, 100.0%); vaka sayısı kaynaktan sayılabildi, kapsama oranı yalnız koşarak öğrenilebildi. - Kapsama profili hangi bloğun hiç çalışmadığını da veriyor; kapsanmayan blok sayısı test kümesi genişledikçe 3’ten 0’a düştü.
- Sınırlayıcı ölçüm: dört durumu da çağıran ama son çağrısının sonucunu denetlemeyen bir test kümesi, hatalı bir kaynakla koşturulduğunda hâlâ geçti ve hâlâ %100 kapsama verdi.
- Kapsama aracı koşulan satırları sayıyor, doğru sınandığını söylemiyor — bu ayrım konunun altı dersinde tekrar eden aynı sınırın son görünümü.
- Sahte bağımlılık ailesi, tablo güdümlü test kalıbı ve kapsama ölçümü Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu; bu ders yalnız Go’nun kendi kapsama aracını ölçtü.
Sonraki Adım
Bu konu boyunca aynı soru altı kez soruldu: bir testin kararı, bir vaka listesi, bir HTTP işleyicisinin yanıtı, bir sahte bağımlılığın sınırı, karşılaştırmalı ölçümün hangi sayısı tekrarlanabilir ve şimdi kapsama oranının neyi saydığı — hepsi kaynak, araç ya da koşum etiketiyle sınıflandı ve altı dersin her biri kendi sınırlayıcı ölçümünü koşturarak gösterdi: geçen bir test doğru olduğunu söylemiyor, vaka eklemek kapsamayı artırmıyor, test yardımcısı ağı değil işleyiciyi sınıyor, sahte gerçekleştirme gerçeğin bütün varsayımlarını taşımıyor, ayırma sayısı da neyi saydığını söylemeden karşılaştırılamıyor ve şimdi kapsama koşulan satırları sayar, doğru sınandığını söylemez. Sıradaki konu aynı soruyu belleğe soruyor: bir profil de tıpkı kapsama gibi koşan yolu görüyor ve nerede bellek ayrıldığını söylüyor — ama neden yavaş olduğunu söylemiyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.