1. Birim Testine Giriş

Birim testi, programdaki en küçük test edilebilir birimi, örneğin bir fonksiyon veya bir methodu Go dilinde kontrol etmek ve doğrulamak anlamına gelir. Birim testi, kodun beklenildiği gibi çalıştığını sağlar ve geliştiricilere mevcut işlevselliği yanlışlıkla bozmadan kod üzerinde değişiklik yapma imkanı tanır.

Golang projesinde birim testinin önemi inkar edilemez. İlk olarak, kod kalitesini arttırabilir ve geliştiricilere kod üzerinde değişiklik yapma konusunda daha fazla güven verir. İkinci olarak, birim testi, kod için belgeler olarak hizmet edebilir ve beklenen davranışını açıklar. Ayrıca, birim testlerini sürekli entegrasyon ortamında otomatik olarak çalıştırmak, yeni hataları hızlı bir şekilde keşfederek yazılımın kararlılığını arttırabilir.

2. testing Paketi Kullanarak Temel Testler Yapma

Go dilinin standart kütüphanesi, testlerin yazılması ve çalıştırılması için araçlar ve işlevsellik sağlayan testing paketini içerir.

2.1 İlk Test Durumunuzu Oluşturma

Bir test fonksiyonu yazmak için, _test.go uzantılı bir dosya oluşturmanız gerekir. Örneğin, kaynak kod dosyanızın adı calculator.go ise, test dosyanızın adı calculator_test.go olmalıdır.

Sonrasında test fonksiyonunu oluşturma zamanı geldi. Bir test fonksiyonu, testing paketini içe aktarmalı ve belirli bir formata uymalıdır. İşte basit bir örnek:

// calculator_test.go
package calculator

import (
	"testing"
	"fmt"
)

// Toplama işlemini test et
func TestAdd(t *testing.T) {
	result := Add(1, 2)
	expected := 3

if result != expected {
		t.Errorf("Beklenen %v, ancak elde edilen %v", expected, result)
	}
}

Bu örnekte, TestAdd, hayali bir Add fonksiyonunu test eden bir test fonksiyonudur. Eğer Add fonksiyonunun sonucu beklenen sonuçla eşleşirse, test başarılı olacak; aksi takdirde t.Errorf çağrılacaktır ve test başarısız olacaktır.

2.2 Test Fonksiyonlarının Adlandırma Kurallarını ve İmzasını Anlama

Test fonksiyonları, Test ile başlamalıdır, ardından küçük harf olmayan herhangi bir dize gelmelidir ve sadece parametreleri, testing.T nin bir işaretçisi olmalıdır. Örnekte görüldüğü gibi, TestAdd doğru adlandırma kurallarını ve imzasını takip eder.

2.3 Test Durumlarının Çalıştırılması

Test durumlarınızı komut satırı aracılığıyla çalıştırabilirsiniz. Belirli bir test durumu için aşağıdaki komutu çalıştırın:

go test -v // Mevcut dizindeki testleri çalıştır ve detaylı çıktıyı görüntüle

Belirli bir test durumunu çalıştırmak istiyorsanız, -run bayrağını bir düzenli ifade ile kullanabilirsiniz:

go test -v -run TestAdd // Yalnızca TestAdd test fonksiyonunu çalıştır

go test komutu otomatik olarak tüm _test.go dosyalarını bulacak ve kriterleri karşılayan her test fonksiyonunu çalıştıracaktır. Eğer tüm testler başarılı olursa, komut satırında PASS benzeri bir mesaj göreceksiniz; eğer herhangi bir test başarısız olursa, FAIL ile birlikte ilgili hata mesajını göreceksiniz.

3. Test Durumlarının Yazılması

3.1 t.Errorf ve t.Fatalf Kullanarak Hataları Bildirme

Go dilinde, test çerçevesi hataları bildirmek için çeşitli yöntemler sağlar. En yaygın kullanılan fonksiyonlar Errorf ve Fatalf'tir, her ikisi de testing.T nesnesinin yöntemleridir. Errorf, testte hataları bildirmek için kullanılır ancak mevcut test durumunu durdurmaz, Fatalf ise mevcut testi hemen durdurur. Test gereksinimlerine bağlı olarak uygun yöntemi seçmek önemlidir.

Errorf kullanım örneği:

func TestAdd(t *testing.T) {
    got := Add(1, 2)
    want := 3
    if got != want {
        t.Errorf("Add(1, 2) = %d; want %d", got, want)
    }
}

Eğer bir hatayı tespit ettiğinizde testi hemen durdurmak istiyorsanız, Fatalf'ı kullanabilirsiniz:

func TestSubtract(t *testing.T) {
    got := Subtract(5, 3)
    if got != 2 {
        t.Fatalf("Subtract(5, 3) = %d; want 2", got)
    }
}

Genel olarak, hata sonrasında sonraki kodun doğru bir şekilde çalışmasına engel olacaksa veya test başarısızlığı önceden doğrulanabiliyorsa, Fatalf'in kullanılması önerilir. Aksi takdirde, daha kapsamlı bir test sonucu elde etmek için Errorf kullanılması önerilir.

3.2 Alt Testleri Düzenleme ve Alt Testleri Çalıştırma

Go'da alt testleri düzenlemek için t.Run kullanabiliriz, bu da test kodunu daha yapılandırılmış bir şekilde yazmamıza yardımcı olur. Alt testler kendi Kurulum ve Son Temizlik işlevlerine sahip olabilir ve tek tek çalıştırılabilir, bu da harika bir esneklik sağlar. Bu özellik özellikle karmaşık testlerin veya parametreli testlerin yapılması için çok faydalıdır.

Alt test t.Run kullanım örneği:

func TestCarpma(t *testing.T) {
    testcases := []struct {
        name           string
        a, b, expected int
    }{
        {"2x3", 2, 3, 6},
        {"-1x-1", -1, -1, 1},
        {"0x4", 0, 4, 0},
    }

    for _, tc := range testcases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Carpma(tc.a, tc.b); got != tc.expected {
                t.Errorf("Carpma(%d, %d) = %d; beklenen %d", tc.a, tc.b, got, tc.expected)
            }
        })
    }
}

Eğer "2x3" adındaki alt testi tek başına çalıştırmak istiyorsak, aşağıdaki komutu komut satırında çalıştırabiliriz:

go test -run TestCarpma/2x3

Lütfen alt test adlarının harf duyarlı olduğunu unutmayın.

4. Testten Önce ve Sonra Hazırlık Çalışmaları

4.1 Kurulum ve Son Temizlik

Testler yaparken genellikle testler için bazı başlangıç durumlarını hazırlamamız gerekir (örneğin, veritabanı bağlantısı, dosya oluşturma vb.), ve benzer şekilde, testler tamamlandıktan sonra bazı temizlik çalışmaları yapmamız gerekir. Go'da genellikle Kurulum ve Son Temizlik'i doğrudan test fonksiyonlarında gerçekleştiririz ve t.Cleanup işlevi, temizlik geri çağrı işlevlerini kaydetme yeteneği sağlar.

İşte basit bir örnek:

func TestVeritabani(t *testing.T) {
    db, err := VeritabaniKurulumu()
    if err != nil {
        t.Fatalf("kurulum başarısız: %v", err)
    }

    // Test tamamlandığında veritabanı bağlantısının kapatılmasını sağlamak için bir temizlik geri çağrısı kaydet
    t.Cleanup(func() {
        if err := db.Kapat(); err != nil {
            t.Errorf("veritabanı kapatılamadı: %v", err)
        }
    })

    // Testi gerçekleştir...
}

TestVeritabani fonksiyonunda, önce test ortamını kurmak için VeritabaniKurulumu fonksiyonunu çağırıyoruz. Sonra, t.Cleanup()'ı kullanarak test tamamlandıktan sonra temizlik çalışmalarını gerçekleştirmek üzere bir fonksiyon kaydediyoruz, bu örnekte, veritabanı bağlantısını kapatmak. Bu şekilde, test başarılı olup olmasın, kaynakların doğru bir şekilde serbest bırakıldığından emin olabiliriz.

5. Test Verimliliğini Artırma

Test verimliliğini artırmak, geliştirmeyi daha hızlı döngüye almak, problemleri hızlıca bulmak ve kod kalitesini sağlamak için önemlidir. Aşağıda, test kapsamı, tablo odaklı testler ve test verimliliğini artırmak için mock kullanımı konusunu tartışacağız.

5.1 Test Kapsamı ve İlgili Araçlar

go test aracı, bize test durumlarının hangi kısımlarını kapsadığını anlamamıza yardımcı olan oldukça faydalı bir test kapsamı özelliği sağlar, bu da test durumları tarafından kapsanmayan kod alanlarını keşfetmemizi sağlar.

go test -cover komutunu kullanarak mevcut test kapsamı yüzdesini görebiliriz:

go test -cover

Hangi satırların çalıştırıldığını ve hangilerinin çalıştırılmadığını daha detaylı anlamak istiyorsanız, kapsama veri dosyası oluşturan -coverprofile parametresini kullanabilirsiniz. Ardından, detaylı bir test kapsamı raporu oluşturmak için go tool cover komutunu kullanabilirsiniz.

go test -coverprofile=kapsama.out
go tool cover -html=kapsama.out

Yukarıdaki komut, web tabanlı bir rapor açacak ve hangi kod satırlarının test edildiğini hangilerinin test edilmediğini görsel olarak gösterecektir. Yeşil, test edilmiş kodu temsil ederken, kırmızı test edilmemiş kod satırlarını temsil eder.

5.2 Mock Kullanımı

Test etme sürecinde genellikle dış bağımlılıkları taklit etmemiz gereken durumlarla karşılaşırız. Mock'lar, bu bağımlılıkları taklit ederek test ortamında belirli dış servislere veya kaynaklara bağlı kalmaktan kaçınmamıza yardımcı olabilir.

Go topluluğunda testify/mock ve gomock gibi birçok mock aracı bulunmaktadır. Bu araçlar genellikle mock objeler oluşturmak ve kullanmak için bir dizi API sağlar.

İşte testify/mock'un temel kullanımına ilişkin bir örnek. İlk yapılması gereken şey, bir arayüz ve onun mock versiyonunu tanımlamaktır:

type DataService interface {
    FetchData() (int, error)
}

type MockDataService struct {
    mock.Mock
}

func (m *MockDataService) FetchData() (int, error) {
    args := m.Called()
    return args.Int(0), args.Error(1)
}

Test etme sürecinde, MockDataService'yi gerçek veri servisinin yerine kullanabiliriz:

func TestSomething(t *testing.T) {
    mockDataSvc := new(MockDataService)
    mockDataSvc.On("FetchData").Return(42, nil) // Beklenen davranışı yapılandırma

    result, err := mockDataSvc.FetchData() // Mock objeyi kullanma
    assert.NoError(t, err)
    assert.Equal(t, 42, result)

    mockDataSvc.AssertExpectations(t) // Beklenen davranışın gerçekleşip gerçekleşmediğini doğrulama
}

Yukarıdaki yöntemle, test etmenin dış servislere, veritabanı çağrılarına vb. bağlı kalmaktan kaçınabilir ve bu testin daha hızlı çalışmasını sağlayabiliriz. Bu, testlerimizi daha istikrarlı ve güvenilir hale getirebilir.

6. Gelişmiş Test Teknikleri

Go birim testinin temellerini öğrendikten sonra, test etme verimliliğini artırmaya ve daha sağlam yazılımlar oluşturmaya yardımcı olan bazı daha gelişmiş test tekniklerini keşfedebiliriz.

6.1 Özel Fonksiyonların Test Edilmesi

Golang'da, özel fonksiyonlar genellikle erişimi olmayan fonksiyonları ifade eder, yani adı küçük harfle başlayan fonksiyonlar. Genellikle kodun kullanılabilirliğini yansıttığı için genellikle genel arayüzleri test etmeyi tercih ederiz. Ancak özel fonksiyonları doğrudan test etmenin de anlamı olduğu durumlar vardır; örneğin, özel fonksiyonun karmaşık mantığı varsa ve birden fazla genel fonksiyon tarafından çağrılıyorsa.

Özel fonksiyonların test edilmesi, dış paketten erişilemeyeceği için genel fonksiyonları test etmekten farklıdır. Yaygın bir teknik, aynı pakete test kodunu yazmak ve bu şekilde özel fonksiyona erişime izin vermektir.

İşte basit bir örnek:

// calculator.go
package calculator

func add(a, b int) int {
    return a + b
}

Buna karşılık gelen test dosyası aşağıdaki gibidir:

// calculator_test.go
package calculator

import "testing"

func TestAdd(t *testing.T) {
    expected := 4
    actual := add(2, 2)
    if actual != expected {
        t.Errorf("beklenen %d, alınan %d", expected, actual)
    }
}

Test dosyasını aynı pakete yerleştirerek add fonksiyonunu doğrudan test edebiliriz.

6.2 Ortak Test Kalıpları ve En İyi Uygulamalar

Golang birim testi, test işini kolaylaştıran ve kodun netliğini ve sürdürülebilirliğini korumaya yardımcı olan bazı ortak kalıplara sahiptir.

  1. Tablo Tabanlı Testler

    Tablo tabanlı test, test girdilerini ve beklenen çıktılarını düzenlemenin bir yöntemidir. Bir dizi test durumu tanımlayarak bunları döngü ile test etmek, bu yöntemi yeni test durumları eklemeyi çok kolay hale getirir ve kodu okunaklı ve sürdürülebilir hale getirir.

    // calculator_test.go
    package calculator

    import "testing"

    func TestAddTableDriven(t *testing.T) {
        var tests = []struct {
            a, b   int
            want   int
        }{
            {1, 2, 3},
            {2, 2, 4},
            {5, -1, 4},
        }

        for _, tt := range tests {
            testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
            t.Run(testname, func(t *testing.T) {
                ans := add(tt.a, tt.b)
                if tt.want != ans {
                    t.Errorf("alınan %d, beklenen %d", ans, tt.want)
                }
            })
        }
    }
  1. Mock'ların Kullanımı için Testler

    Mock'lar, çeşitli işlevlerin test edilmesi için bağımlılıkların yerine geçme tekniğidir. Golang'da, mock'ları uygulamanın temel yolu arayüzlerdir. Arayüzleri kullanarak, mock bir uygulama oluşturulabilir ve ardından testlerde kullanılabilir.