İçeriğe geç
academia.sh

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ı Etiketle iş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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat