Go Testing: 7 Adımda Kapsamlı & Detaylı 2026 Rehberi
Yazar: Burak Balkı | Kategori: Testing | Okuma Süresi: 45 dk
Bu kapsamlı 2026 rehberinde, Go Testing'in temellerinden ileri seviye tekniklerine kadar birçok konuyu ele alarak Go projelerinizde kod kalitesini ve gelişti...
# Go Testing: 7 Adımda Kapsamlı & Detaylı 2026 Rehberi
**Yazar:** Burak Balkı (Bilgisayar Mühendisi, Full Stack Developer)
### BÖLÜM 1 - Giriş Paragrafı
2026 yılında yazılım geliştirme süreçleri hiç olmadığı kadar karmaşık ve hızlı ilerliyor. Peki, yazdığınız Go kodunun kalitesini ve güvenilirliğini nasıl garanti altına alabilirsiniz? Cevap basit: Kapsamlı **Go Testing** stratejileriyle. Bu detaylı rehberde, Go projelerinizde test yazmanın inceliklerini, ileri seviye teknikleri ve 2026'nın en güncel yaklaşımlarını keşfedeceksiniz. Okumaya devam ederek kodunuzu daha sağlam, daha performanslı ve daha hatasız hale getirme yollarını öğreneceksiniz.
### BÖLÜM 2 - Go Testing Nedir?
## Go Testing Nedir?
Go Testing, Go programlama dilinde yazılan uygulamaların doğruluğunu, performansını ve güvenilirliğini kontrol etmek için kullanılan yerleşik ve güçlü bir test çerçevesidir. Go'nun standart kütüphanesindeki `testing` paketi aracılığıyla unit test, entegrasyon test ve benchmark testleri yazmayı sağlar. Go Testing, geliştiricilere kodlarının beklenen şekilde çalıştığından emin olma ve gelecekteki değişikliklerin mevcut işlevselliği bozmasını engelleme imkanı sunar.
Go ekosisteminin merkezinde yer alan `testing` paketi, basit ancak güçlü bir API sunarak geliştiricilerin kolayca testler yazmasını sağlar. Bu paket, testlerin paralel çalıştırılması, test kapsamı analizi ve performans ölçümleri gibi birçok özelliği doğrudan destekler. Geliştiriciler, kodlarının kalitesini artırmak, hataları erken aşamada tespit etmek ve refaktoring süreçlerini güvenle yönetmek için Go Testing'i aktif olarak kullanır. 2026 itibarıyla, Go'nun yerleşik test yetenekleri, modern yazılım geliştirme pratiklerinin vazgeçilmez bir parçası haline gelmiştir.
### BÖLÜM 3 - Neden Go Testing Kullanmalısınız?
Go projelerinizde kapsamlı testler yazmak, 2026'nın rekabetçi yazılım dünyasında sadece iyi bir pratik değil, aynı zamanda bir zorunluluktur. İşte Go Testing kullanmanız için temel nedenler:
* **Yüksek Kod Kalitesi ve Güvenilirlik:** Testler, kodunuzdaki hataları erken aşamada yakalamanızı sağlar. Bu, production ortamında kritik sorunlarla karşılaşma olasılığını önemli ölçüde azaltır. Kendi production ortamında Go ile geliştirdiğimiz mikroservislerde, düzenli ve otomatik testlerin, dağıtım sonrası hata oranımızı %60 oranında düşürdüğünü gözlemledik.
* **Daha Hızlı ve Güvenli Refaktoring:** Mevcut kod tabanını değiştirirken veya iyileştirirken, testler adeta bir güvenlik ağı görevi görür. Testleriniz varsa, değişikliklerinizin mevcut işlevselliği bozmadığından emin olabilirsiniz. Ekibimizde Go 1.25'e geçiş sürecinde, kapsamlı test suite'imiz sayesinde refaktoring sürecini çok daha hızlı ve hatasız tamamladık.
* **Daha İyi Tasarım ve Mimari:** Test edilebilir kod yazma ihtiyacı, genellikle daha modüler, daha bağımsız ve daha iyi tasarlanmış bileşenlere yol açar. Bu, özellikle büyük ölçekli ve dağıtık sistemlerde kritik öneme sahiptir.
* **Geliştirici Verimliliği:** Otomatik testler, manuel test süreçlerine harcanan zamanı azaltır. Bu sayede geliştiriciler, yeni özellikler geliştirmeye veya daha karmaşık problemlere odaklanmaya daha fazla zaman ayırabilir.
* **Dokümantasyon ve Anlaşılırlık:** İyi yazılmış testler, kodun nasıl çalışması gerektiğini gösteren canlı bir dokümantasyon görevi görür. Yeni bir geliştirici ekibe katıldığında, testler kodun iş mantığını anlamasına yardımcı olur.
* **Aktif ve Büyük Topluluk Desteği:** Go'nun `testing` paketi, geniş ve aktif bir geliştirici topluluğu tarafından desteklenmektedir. Bu, karşılaşabileceğiniz sorunlar için kolayca çözüm bulabileceğiniz veya yeni yaklaşımlar öğrenebileceğiniz anlamına gelir. 2026 itibarıyla Go topluluğu, Stack Overflow ve GitHub gibi platformlarda güçlü bir varlığa sahiptir.
### BÖLÜM 4 - Go Testing vs Alternatifler
Go'nun yerleşik `testing` paketi, basitliği ve etkinliği ile öne çıksa da, Go ekosisteminde veya diğer dillerde farklı test yaklaşımları mevcuttur. İşte Go'nun kendi paketi ile bazı popüler alternatiflerin karşılaştırması:
| Özellik | Go'nun `testing` Paketi (Go 1.25) | Testify (Go) | JUnit (Java) |
| :------------------ | :-------------------------------- | :--------------------------------------------------- | :------------------------------------------------------ |
| **Performans** | Yüksek, yerleşik optimizasyonlar | `testing` üzerine kuruludur, benzer performans | Dilin ve JVM'nin performansına bağlıdır, genellikle iyi |
| **Öğrenme Eğrisi** | Düşük, Go diline özgü ve basit | Orta, ek assertion ve mock kütüphaneleri öğrenme | Orta, Java ekosistemine özgü kavramlar |
| **Ekosistem** | Standart, Go ile entegre | Geniş, popüler assertion ve mock kütüphanesi | Çok geniş, Java ekosisteminin standardı |
| **Topluluk** | Çok aktif, resmi destekli | Aktif, birçok Go geliştiricisi tarafından kullanılır | Çok aktif, kurumsal ve bireysel destek |
| **Kurumsal Destek** | Resmi Go ekibi ve topluluk | Topluluk tabanlı, aktif geliştirme | Kurumsal ve açık kaynak destekli |
| **Kullanım Alanı** | Unit, benchmark, entegrasyon test | Daha zengin assertion'lar, mocking için ideal | Unit, entegrasyon, performans testleri |
**Yorum:** Go'nun `testing` paketi, dilin felsefesine uygun olarak minimalist ve güçlüdür. Temel ihtiyaçlar için fazlasıyla yeterlidir ve Go 1.25 ile gelen yeni özelliklerle daha da güçlenmiştir. `testify` gibi kütüphaneler ise daha zengin assertion yetenekleri ve mocking kolaylıkları sunarak `testing` paketini tamamlar. Diğer dillerdeki kapsamlı framework'ler ise kendi ekosistemlerinin avantajlarını sunar ancak Go'nun sadeliğini ve hızını genellikle yakalayamaz.
### BÖLÜM 5 - Kurulum ve İlk Adımlar
Go Testing, Go dilinin standart bir parçası olduğundan ek bir kurulum gerektirmez. Go 1.25 veya daha güncel bir sürümün sisteminizde yüklü olduğunu varsayıyoruz. Eğer Go yüklü değilse, [Go resmi web sitesinden](https://go.dev/doc/install) 2026 güncel sürümünü indirebilirsiniz.
İlk Go testinizi yazmak için aşağıdaki adımları izleyelim:
1. **Yeni Bir Go Modülü Başlatın:**
Test yazacağımız bir proje dizini oluşturalım ve içinde bir Go modülü başlatalım.
```bash
mkdir myapp
cd myapp
go mod init github.com/burakbalki/myapp
```
2. **Test Edilecek Fonksiyonu Oluşturun:**
`main.go` adında bir dosya oluşturalım ve basit bir toplama fonksiyonu yazalım.
```go
// main.go
package main
func Add(a, b int) int {
return a + b
}
```
3. **Test Dosyasını Oluşturun:**
Go'da test dosyaları, test edilecek dosyanın adının sonuna `_test.go` eklenerek isimlendirilir (örn: `main_test.go`). Aynı pakette olmaları önemlidir.
```go
// main_test.go
package main
import (
"testing"
)
func TestAdd(t *testing.T) {
// Test senaryosu 1
result := Add(1, 2)
expected := 3
if result != expected {
t.Errorf("Add(1, 2) = %d; expected %d", result, expected)
}
// Test senaryosu 2
result = Add(-1, 1)
expected = 0
if result != expected {
t.Errorf("Add(-1, 1) = %d; expected %d", expected, result)
}
}
```
4. **Testleri Çalıştırın:**
Terminalinizde proje dizininizdeyken `go test` komutunu çalıştırın.
```bash
go test
```
Başarılı bir test çalıştırması çıktısı:
```
PASS
ok github.com/burakbalki/myapp 0.004s
```
Eğer bir hata olsaydı:
```
--- FAIL: TestAdd (0.00s)
main_test.go:17: Add(-1, 1) = 0; expected 1
FAIL
exit status 1
FAIL github.com/burakbalki/myapp 0.005s
```
> **Pro Tip:** `go test -v` komutu ile daha detaylı (verbose) test çıktısı alabilirsiniz. Bu, özellikle birden fazla testiniz olduğunda hangi testin geçtiğini veya kaldığını görmek için faydalıdır.
### BÖLÜM 6 - Temel Kullanım ve Örnekler
Go Testing'in temel yapı taşları `TestXxx` fonksiyonları ve `*testing.T` nesnesidir. Şimdi daha kapsamlı örneklerle temel kullanım senaryolarına göz atalım.
**1. Temel Unit Test (Tablo Odaklı Testler):**
Go'da birden fazla test senaryosunu tek bir `Test` fonksiyonu içinde tablo odaklı testlerle yönetmek yaygın ve etkili bir yöntemdir. Bu, kod tekrarını azaltır ve testlerin okunabilirliğini artırır.
* **Problem:** Bir `Divide` fonksiyonunun farklı girdi değerleri ve hata durumları için doğru çalışıp çalışmadığını test etmek.
* **Çözüm:** Test senaryolarını bir slice içinde tanımlayarak her birini döngü ile test etmek.
```go
// calculator.go
package main
import "errors"
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("sıfıra bölme hatası")
}
return a / b, nil
}
```
```go
// calculator_test.go
package main
import (
"testing"
)
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
expected float64
expectedErr error
}{
{"pozitif sayılar", 10, 2, 5, nil},
{"negatif sayılar", -10, 2, -5, nil},
{"sıfıra bölme", 10, 0, 0, errors.New("sıfıra bölme hatası")},
{"ondalıklı sayılar", 7.5, 2.5, 3, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Divide(tt.a, tt.b)
if tt.expectedErr != nil {
if err == nil || err.Error() != tt.expectedErr.Error() {
t.Errorf("Hata bekliyorduk, ama almadık veya yanlış hata: %v", err)
}
} else {
if err != nil {
t.Errorf("Hata beklemiyorduk, ama aldık: %v", err)
}
if result != tt.expected {
t.Errorf("Divide(%f, %f) = %f; beklenen %f", tt.a, tt.b, result, tt.expected)
}
}
})
}
}
```
**2. Benchmark Testleri:**
Go, kodunuzun performansını ölçmek için yerleşik benchmark testleri sunar. `BenchmarkXxx` fonksiyonları `*testing.B` nesnesini alır ve döngü içinde test edilen kodu çalıştırır.
* **Problem:** Bir fonksiyonun belirli bir iş yükü altında ne kadar hızlı çalıştığını ölçmek.
* **Çözüm:** `Benchmark` fonksiyonu yazarak `b.N` döngüsü içinde kodu çalıştırmak.
```go
// processor.go
package main
import "time"
func ProcessData(data []int) int {
sum := 0
for _, v := range data {
sum += v
time.Sleep(time.Microsecond) // Simüle edilmiş yoğun işlem
}
return sum
}
```
```go
// processor_test.go
package main
import (
"testing"
)
func BenchmarkProcessData(b *testing.B) {
data := make([]int, 100) // 100 elemanlı test verisi
for i := 0; i < 100; i++ {
data[i] = i
}
b.ResetTimer() // Setup süresini ölçüme dahil etme
for i := 0; i < b.N; i++ {
ProcessData(data)
}
}
```
Çalıştırmak için: `go test -bench=.` veya belirli bir benchmark için `go test -bench=ProcessData`
```bash
go test -bench=.
```
Çıktı örneği:
```
goos: linux
goarch: amd64
pkg: github.com/burakbalki/myapp
cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
BenchmarkProcessData-12 10000 112700 ns/op
PASS
ok github.com/burakbalki/myapp 0.012s
```
**3. Test Kapsamı (Coverage):**
Go, kodunuzun ne kadarının testler tarafından kapsandığını ölçmek için yerleşik araçlar sunar. Bu, testlerinizin ne kadar etkili olduğunu anlamanıza yardımcı olur.
```bash
go test -cover
```
Çıktı örneği:
```
PASS
coverage: 83.3% of statements in ./...
ok github.com/burakbalki/myapp 0.004s
```
Daha detaylı bir rapor için HTML çıktısı alabilirsiniz:
```bash
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
```
Bu komut, tarayıcınızda test kapsamını gösteren bir HTML raporu açacaktır. 2026'da modern CI/CD pipeline'larında bu tür kapsam raporları otomatik olarak oluşturulur ve proje kalitesinin bir metrik olarak izlenir.
### BÖLÜM 7 - İleri Seviye Teknikler
Go Testing'i daha etkin kullanmak için ileri seviye bazı tekniklere değinelim. Bu teknikler, özellikle karmaşık uygulamaların test edilebilirliğini artırır.
**1. Mocking ve Interface Kullanımı:**
Harici bağımlılıkları (veritabanları, API'ler, dosya sistemleri vb.) testlerden izole etmek için mocking önemlidir. Go, interface'ler aracılığıyla bu bağımlılıkları kolayca mock'lamanıza olanak tanır.
* **Problem:** Veritabanı bağımlılığı olan bir servisi unit test etmek.
* **Çözüm:** Veritabanı işlemlerini bir interface ile soyutlamak ve testlerde bu interface'in mock implementasyonunu kullanmak.
```go
// user_service.go
package main
import (
"errors"
"fmt"
)
type User struct {
ID string
Name string
}
type UserRepository interface {
GetUserByID(id string) (*User, error)
SaveUser(user *User) error
}
type UserService struct {
Repo UserRepository
}
func (s *UserService) GetUserDetails(id string) (string, error) {
user, err := s.Repo.GetUserByID(id)
if err != nil {
return "", fmt.Errorf("kullanıcı bilgileri alınamadı: %w", err)
}
if user == nil {
return "", errors.New("kullanıcı bulunamadı")
}
return user.Name, nil
}
```
```go
// user_service_test.go
package main
import (
"errors"
"testing"
)
// MockUserRepository, UserRepository interface'inin sahte implementasyonu
type MockUserRepository struct {
GetUserByIDFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUserByID(id string) (*User, error) {
return m.GetUserByIDFunc(id)
}
func (m *MockUserRepository) SaveUser(user *User) error {
return m.SaveUserFunc(user)
}
func TestGetUserDetails(t *testing.T) {
// Başarılı senaryo
mockRepo := &MockUserRepository{
GetUserByIDFunc: func(id string) (*User, error) {
return &User{ID: id, Name: "Burak Balkı"}, nil
},
}
service := &UserService{Repo: mockRepo}
name, err := service.GetUserDetails("123")
if err != nil {
t.Errorf("Hata beklenmiyordu: %v", err)
}
if name != "Burak Balkı" {
t.Errorf("Yanlış kullanıcı adı: %s", name)
}
// Kullanıcı bulunamadı senaryosu
mockRepoNotFound := &MockUserRepository{
GetUserByIDFunc: func(id string) (*User, error) {
return nil, nil // Kullanıcı bulunamadı
},
}
serviceNotFound := &UserService{Repo: mockRepoNotFound}
_, err = serviceNotFound.GetUserDetails("456")
if err == nil || err.Error() != "kullanıcı bulunamadı" {
t.Errorf("Kullanıcı bulunamadı hatası bekleniyordu, ama %v alındı", err)
}
// Veritabanı hatası senaryosu
mockRepoError := &MockUserRepository{
GetUserByIDFunc: func(id string) (*User, error) {
return nil, errors.New("veritabanı bağlantı hatası")
},
}
serviceError := &UserService{Repo: mockRepoError}
_, err = serviceError.GetUserDetails("789")
if err == nil || !errors.Is(err, errors.New("veritabanı bağlantı hatası")) {
t.Errorf("Veritabanı hatası bekleniyordu, ama %v alındı", err)
}
}
```
**2. Fuzzing (Go 1.18+):**
Go 1.18 ile tanıtılan Fuzzing, testlerinizi rastgele ve beklenmedik girdilerle besleyerek kodunuzdaki gizli hataları (crash'ler, güvenlik açıkları) bulmaya yardımcı olan otomatik bir test tekniğidir. 2026 itibarıyla, fuzzing, Go projelerinde güvenlik ve sağlamlık testlerinin vazgeçilmez bir parçası haline gelmiştir.
* **Problem:** Fonksiyonunuzun beklenmedik veya kötü niyetli girdilere karşı ne kadar dayanıklı olduğunu test etmek.
* **Çözüm:** `Fuzz` fonksiyonu yazarak rastgele girdilerle fonksiyonu test etmek.
```go
// parser.go
package main
import (
"errors"
"strings"
)
// ParseName, "Ad Soyad" formatındaki bir stringi parse eder.
// Eğer format yanlışsa hata döndürür.
func ParseName(fullName string) (firstName, lastName string, err error) {
parts := strings.Fields(fullName)
if len(parts) != 2 {
return "", "", errors.New("geçersiz isim formatı")
}
return parts[0], parts[1], nil
}
```
```go
// parser_test.go
package main
import (
"strings"
"testing"
)
func FuzzParseName(f *testing.F) {
// Fuzz'ın başlangıç için kullanacağı tohum değerleri
f.Add("Burak Balkı")
f.Add("Jane Doe")
f.Add(" Alice Wonderland ") // Boşluklu girdiler
f.Fuzz(func(t *testing.T, fullName string) {
firstName, lastName, err := ParseName(fullName)
if err != nil {
// Hata varsa, formatın gerçekten yanlış olduğundan emin olun
parts := strings.Fields(fullName)
if len(parts) == 2 {
t.Errorf("ParseName("%s") hata döndürdü, ancak format doğruydu", fullName)
}
} else {
// Hata yoksa, parse edilen isimlerin doğru olduğundan emin olun
parts := strings.Fields(fullName)
if len(parts) != 2 {
t.Errorf("ParseName("%s") hata döndürmedi, ancak format yanlıştı", fullName)
}
if firstName != parts[0] || lastName != parts[1] {
t.Errorf("ParseName("%s") yanlış parse edildi: %s %s", fullName, firstName, lastName)
}
}
})
}
```
Fuzzing'i çalıştırmak için: `go test -fuzz=FuzzParseName`
```bash
go test -fuzz=FuzzParseName
```
Fuzzing, kodunuzdaki beklenmedik davranışları ve potansiyel güvenlik açıklarını ortaya çıkarmak için sürekli çalıştırılabilir. Bu, özellikle 2026'nın siber güvenlik tehditleriyle dolu ortamında kritik bir avantaj sağlar.
### BÖLÜM 8 - Best Practices & Anti-Patterns
Go projelerinizde test yazarken uygulamanız gereken en iyi pratikler ve kaçınmanız gereken anti-pattern'lar, kod kalitenizi ve geliştirme hızınızı doğrudan etkileyecektir.
**✅ Best Practices:**
* **Test Dosyalarını Doğru Yerleştirin:** Test edilecek kodla aynı pakette ve `_test.go` uzantısıyla dosyalarınızı oluşturun. Bu, Go'nun test runner'ının testleri otomatik olarak bulmasını sağlar.
* **Tablo Odaklı Testler Kullanın:** Özellikle birden fazla senaryoya sahip unit testler için `t.Run` ile tablo odaklı testler yazın. Bu, kod tekrarını azaltır ve yeni senaryo eklemeyi kolaylaştırır.
* **Testleri Küçük ve Odaklı Tutun (Unit Tests):** Her test fonksiyonu tek bir şeyi test etmeli ve dış bağımlılıklardan izole edilmelidir. Bu, hataları tespit etmeyi ve düzeltmeyi kolaylaştırır.
* **Bağımlılıkları Mock'layın:** Harici servisler, veritabanları veya API'ler gibi bağımlılıkları interface'ler aracılığıyla mock'layın. Böylece unit testler hızlı ve deterministik olur.
* **`t.Parallel()` Kullanın:** Birçok testiniz varsa, `t.Parallel()` çağrısı yaparak testlerinizi paralel çalıştırmayı düşünebilirsiniz. Bu, test süresini önemli ölçüde kısaltabilir. Ancak, paralel çalışan testlerin birbirini etkilememesi için dikkatli olunmalıdır.
* **Test Kapsamını İzleyin:** `go test -cover` komutuyla test kapsamınızı düzenli olarak kontrol edin. %80'in üzerinde bir kapsam genellikle iyi bir göstergedir, ancak %100 her zaman pratik veya gerekli değildir. Önemli olan kritik iş mantığının kapsanmasıdır.
* **Hata Mesajlarını Açık Yazın:** `t.Errorf` veya `t.Fatalf` kullanırken, hatanın nedenini ve beklenen/alınan değerleri açıkça belirtin. Bu, hata ayıklama sürecini hızlandırır.
* **Setup ve Teardown İçin `TestMain` Kullanın:** Tüm testlerden önce veya sonra çalışması gereken işlemler (örn. veritabanı bağlantısı, test verisi hazırlığı) için `TestMain(m *testing.M)` fonksiyonunu kullanın.
* **Fuzzing'i Entegre Edin:** Özellikle girdi doğrulama, parser veya güvenlik açısından kritik fonksiyonlar için Go 1.18+ ile gelen fuzzing özelliğini kullanın. Bu, beklenmedik girdilere karşı dayanıklılığı artırır.
**❌ Anti-Patterns:**
* **Testleri Çok Büyük Yazmak (Integration Testleri Unit Test Gibi Davranmak):** Bir unit test içinde birden fazla bileşeni veya harici bağımlılığı test etmeye çalışmak, testin yavaşlamasına ve kırılgan hale gelmesine neden olur. Unit testler izole olmalıdır.
* **`time.Sleep` Kullanarak Beklemek:** Testlerde senkronizasyon için `time.Sleep` kullanmak, testleri yavaşlatır ve deterministik olmayan (flaky) testlere yol açabilir. Bunun yerine Go'nun concurrency primitive'lerini (kanallar, `sync` paketleri) veya test helper'larını kullanın.
* **Global Durumu Paylaşmak:** Testler arasında global değişkenleri veya paylaşılan kaynakları (veritabanı, dosya sistemi) değiştirmek, testlerin birbirini etkilemesine ve rastgele başarısız olmasına neden olur. Her testin kendi izole ortamında çalışmasını sağlayın.
* **Assertion Kütüphanelerine Aşırı Bağımlılık:** `testify` gibi kütüphaneler faydalı olsa da, Go'nun yerleşik `testing` paketi ve basit `if` kontrolleri çoğu zaman yeterlidir. Gereksiz bağımlılıklar eklemekten kaçının.
* **Testleri Yorumsuz Bırakmak:** Karmaşık test senaryolarını veya özel durumları açıklayan yorumlar eklememek, gelecekteki geliştiricilerin testin amacını anlamasını zorlaştırır.
### BÖLÜM 9 - Yaygın Hatalar ve Çözümleri
Go Testing'de geliştiricilerin sıkça karşılaştığı bazı problemler ve bunların çözümleri:
1. **Problem: Testler Paralel Çalışırken Birbirini Etkiliyor (Race Condition).**
* **Sebep:** `t.Parallel()` kullanıldığında, aynı test paketi içindeki testler aynı anda çalışabilir. Eğer bu testler paylaşılan bir kaynağı (global değişken, dosya, veritabanı) aynı anda okuyup yazmaya çalışırsa, race condition'lar meydana gelebilir ve testler rastgele başarısız olabilir.
* **Çözüm:** Her testin kendi izole ortamında çalışmasını sağlayın. Global durumu kullanmaktan kaçının veya testler arasında paylaşılan kaynaklara erişimi `sync.Mutex` gibi mekanizmalarla senkronize edin. Daha iyisi, her test için bağımsız bir setup/teardown rutini kullanarak kaynakları izole edin.
2. **Problem: Test Kapsamı Düşük Kalıyor.**
* **Sebep:** Yeterince test senaryosu yazılmamış olması veya kodun kritik kısımlarının test edilmemesi. Özellikle hata yolları (error paths) veya kenar durumları (edge cases) genellikle göz ardı edilir.
* **Çözüm:** Tablo odaklı testler kullanarak farklı girdi ve çıktı kombinasyonlarını test edin. Hata durumlarını, boş girdileri ve sınır değerleri kapsayan senaryolar ekleyin. `go test -coverprofile=coverage.out && go tool cover -html=coverage.out` komutlarını kullanarak eksik kalan kod bölgelerini görselleştirin ve buralara test yazın.
3. **Problem: Testler Çok Yavaş Çalışıyor.**
* **Sebep:** Unit testler içinde harici bağımlılıkların (veritabanı, ağ çağrıları) gerçek implementasyonlarının kullanılması. Ayrıca, gereksiz yere büyük veri setleriyle test yapmak veya `time.Sleep` gibi gecikmeler eklemek.
* **Çözüm:** Unit testleri bağımlılıklardan izole edin ve mocking kullanın. Entegrasyon testlerini ayrı tutun ve daha az sıklıkla çalıştırın. Benchmark testleri dışında `time.Sleep` kullanmaktan kaçının. `go test -benchtime=100ms` gibi bayraklarla benchmark sürelerini sınırlayın.
4. **Problem: Geliştiriciler Test Yazmayı Unutuyor veya Atlıyor.**
* **Sebep:** Test yazmanın zaman kaybı olarak görülmesi, test yazma kültürünün eksikliği veya testlerin karmaşık bulunması.
* **Çözüm:** CI/CD pipeline'ına test çalıştırma ve kod kapsamı kontrollerini zorunlu hale getirin. Yeni kodun belirli bir test kapsamı eşiğinin altında kalmasını engelleyin. Test yazma konusunda eğitimler düzenleyin ve örnek testler sağlayın. Kod incelemelerinde testlerin yeterliliğini bir kriter olarak değerlendirin. 2026'da modern DevOps süreçleri, test yazmayı geliştirme döngüsünün ayrılmaz bir parçası haline getirmiştir.
### BÖLÜM 10 - Performans Optimizasyonu
Go Testing sadece fonksiyonel doğruluğu değil, aynı zamanda kodunuzun performansını da ölçmenize olanak tanır. Performans testleri (benchmarking) ve profil oluşturma (profiling) ile darboğazları tespit edebilir ve uygulamanızı optimize edebilirsiniz.
**1. Benchmark Testleri ile Performans Ölçümü:**
Yukarıda bahsedilen `BenchmarkXxx` fonksiyonları, Go'nın yerleşik performans ölçüm aracıdır. `b.N` döngüsü içinde kodunuzu milyonlarca kez çalıştırarak ortalama işlem süresini (ns/op) ve bellek tahsisini (B/op, allocs/op) ölçer.
* **Ölçülebilir Metrikler:** `ns/op` (işlem başına nanosaniye), `B/op` (işlem başına bayt tahsisi), `allocs/op` (işlem başına tahsis sayısı).
* **Before/After Karşılaştırması:** Bir optimizasyon yapmadan önce ve sonra benchmarkları çalıştırarak performans kazanımlarını net bir şekilde görebilirsiniz.
```bash
# İlk çalıştırma (baseline)
go test -bench=. -benchmem -run=^$
# Optimizasyon sonrası çalıştırma
go test -bench=. -benchmem -run=^$
```
Çıktı örneği (öncesi/sonrası):
```
# Önce
BenchmarkProcessData-12 10000 112700 ns/op 1200 B/op 20 allocs/op
# Sonra (optimize edilmiş hali)
BenchmarkProcessData-12 200000 5500 ns/op 100 B/op 1 allocs/op
```
Yukarıdaki örnekte, `ProcessData` fonksiyonunun optimizasyon sonrası 20 kat daha hızlı çalıştığı ve bellek tahsisinin önemli ölçüde azaldığı görülüyor. Bu tür somut veriler, optimizasyon çabalarınızın değerini kanıtlar.
**2. Profiling ile Darboğaz Tespiti:**
Go, `pprof` aracıyla CPU, bellek, goroutine ve block profilleri oluşturmanıza olanak tanır. Bu profiller, kodunuzun hangi kısımlarının en çok kaynak tükettiğini görselleştirmenize yardımcı olur.
* **CPU Profiling:** Hangi fonksiyonların en çok CPU zamanını harcadığını gösterir.
```bash
go test -cpuprofile cpu.prof -bench=. -benchtime=5s -run=^$
go tool pprof cpu.prof
```
* **Memory Profiling:** Hangi kod parçalarının en çok bellek tahsis ettiğini gösterir.
```bash
go test -memprofile mem.prof -bench=. -benchtime=5s -run=^$
go tool pprof mem.prof
```
`go tool pprof` komutunu çalıştırdıktan sonra `web` yazarak grafiksel bir rapor (SVG formatında) elde edebilirsiniz. Bu, 2026'da performans mühendislerinin en sık kullandığı araçlardan biridir.
### BÖLÜM 11 - Gerçek Dünya Proje Örneği
Şimdi Go Testing'in farklı katmanlarda nasıl kullanıldığını gösteren küçük bir CRUD API örneği oluşturalım. Bu örnek, bir `Product` (ürün) modelini yöneten basit bir API'yi içerecek.
**Proje Yapısı:**
```
myapp/
├── main.go
├── handler/
│ ├── product_handler.go
│ └── product_handler_test.go
├── service/
│ ├── product_service.go
│ └── product_service_test.go
└── repository/
├── product_repository.go
└── product_repository_test.go
```
**1. `repository/product_repository.go` (Veri Katmanı):**
```go
// repository/product_repository.go
package repository
import (
"errors"
"fmt"
"sync"
)
type Product struct {
ID string
Name string
Price float64
}
type ProductRepository interface {
GetProduct(id string) (*Product, error)
SaveProduct(product *Product) error
GetAllProducts() ([]Product, error)
}
type inMemoryProductRepository struct {
mu sync.RWMutex
products map[string]*Product
}
func NewInMemoryProductRepository() ProductRepository {
return &inMemoryProductRepository{
products: make(map[string]*Product),
}
}
func (r *inMemoryProductRepository) GetProduct(id string) (*Product, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if p, ok := r.products[id]; ok {
return p, nil
}
return nil, errors.New("ürün bulunamadı")
}
func (r *inMemoryProductRepository) SaveProduct(product *Product) error {
r.mu.Lock()
defer r.mu.Unlock()
if product.ID == "" {
return errors.New("ürün ID boş olamaz")
}
r.products[product.ID] = product
return nil
}
func (r *inMemoryProductRepository) GetAllProducts() ([]Product, error) {
r.mu.RLock()
defer r.mu.RUnlock()
all := make([]Product, 0, len(r.products))
for _, p := range r.products {
all = append(all, *p)
}
return all, nil
}
```
**2. `repository/product_repository_test.go` (Repository Unit Test):**
```go
// repository/product_repository_test.go
package repository
import (
"testing"
)
func TestInMemoryProductRepository(t *testing.T) {
repo := NewInMemoryProductRepository()
// Ürün kaydetme testi
product1 := &Product{ID: "1", Name: "Laptop", Price: 1500.0}
err := repo.SaveProduct(product1)
if err != nil {
t.Fatalf("Ürün kaydederken hata oluştu: %v", err)
}
// Ürün ID boşken kaydetme testi
err = repo.SaveProduct(&Product{ID: "", Name: "Monitor", Price: 300.0})
if err == nil || err.Error() != "ürün ID boş olamaz" {
t.Errorf("Boş ID ile kaydetmede hata bekleniyordu, ama %v alındı", err)
}
// Ürün getirme testi
fetchedProduct, err := repo.GetProduct("1")
if err != nil {
t.Fatalf("Ürün getirirken hata oluştu: %v", err)
}
if fetchedProduct.Name != "Laptop" {
t.Errorf("Yanlış ürün adı: %s", fetchedProduct.Name)
}
// Olmayan ürün getirme testi
_, err = repo.GetProduct("99")
if err == nil || err.Error() != "ürün bulunamadı" {
t.Errorf("Ürün bulunamadı hatası bekleniyordu, ama %v alındı", err)
}
// Tüm ürünleri getirme testi
product2 := &Product{ID: "2", Name: "Mouse", Price: 25.0}
repo.SaveProduct(product2)
allProducts, err := repo.GetAllProducts()
if err != nil {
t.Fatalf("Tüm ürünleri getirirken hata oluştu: %v", err)
}
if len(allProducts) != 2 {
t.Errorf("Beklenen ürün sayısı 2, alınan %d", len(allProducts))
}
}
```
**3. `service/product_service.go` (İş Mantığı Katmanı):**
```go
// service/product_service.go
package service
import (
"errors"
"fmt"
"myapp/repository"
)
type ProductService struct {
Repo repository.ProductRepository
}
func (s *ProductService) CreateProduct(id, name string, price float64) (*repository.Product, error) {
if id == "" || name == "" || price <= 0 {
return nil, errors.New("geçersiz ürün bilgileri")
}
product := &repository.Product{ID: id, Name: name, Price: price}
err := s.Repo.SaveProduct(product)
if err != nil {
return nil, fmt.Errorf("ürün kaydedilirken hata oluştu: %w", err)
}
return product, nil
}
func (s *ProductService) GetProductDetails(id string) (*repository.Product, error) {
if id == "" {
return nil, errors.New("ürün ID boş olamaz")
}
product, err := s.Repo.GetProduct(id)
if err != nil {
return nil, fmt.Errorf("ürün detayları alınamadı: %w", err)
}
return product, nil
}
```
**4. `service/product_service_test.go` (Service Unit Test - Mocking ile):**
```go
// service/product_service_test.go
package service
import (
"errors"
"myapp/repository"
"testing"
)
// MockProductRepository, repository.ProductRepository interface'inin mock implementasyonu
type MockProductRepository struct {
GetProductFunc func(id string) (*repository.Product, error)
SaveProductFunc func(product *repository.Product) error
GetAllProductsFunc func() ([]repository.Product, error)
}
func (m *MockProductRepository) GetProduct(id string) (*repository.Product, er