Unit Testing: Google Test (gtest)
Bir köprü inşa eden mühendis, köprüyü trafiğe açmadan önce her çelik kirişi, her cıvatayı, her kaynak noktasını test eder. Yük testleri yapar, rüzgar simülasyonları çalıştırır, deprem senaryolarını dener. Hiçbir mühendis "bence sağlam" deyip köprüyü açmaz. Ama yazılımda öyle yapıyoruz — kodu yazıyoruz, bir iki kez elle deniyoruz, "çalışıyor gibi" deyip production'a atıyoruz. Sonra gece 3'te telefon çalıyor.
Unit testing, kodunun en küçük parçalarını (fonksiyonlar, sınıflar) otomatik olarak doğrulama işlemidir. Elle test etmek yerine, test kodun senin için kontrol eder — her build'de, her commit'te, her gece. Bu derste C++ dünyasının en yaygın test framework'ü olan Google Test (gtest) ile tanışacağız, test nasıl yazılır öğreneceğiz ve Google Mock ile bağımlılıkları nasıl taklit edeceğimizi göreceğiz.
Neden Test Yazarız?
Elle Test Etmenin Sınırları
Diyelim ki bir hesapla() fonksiyonu yazdın. Birkaç değerle denedin, doğru sonuç verdi. Ama:
Negatif sayılarla denedin mi?
Sıfıra bölme durumunu test ettin mi?
Çok büyük sayılarla overflow olur mu?
Birisi 3 ay sonra fonksiyonu değiştirdiğinde, eski davranışın bozulmadığını kim kontrol edecek?
Elle test etmek tekrarlanamaz, eksik ve zaman kaybıdır. Otomatik testler ise bir kez yazılır, binlerce kez çalıştırılır.
Test Yazmanın Faydaları
Regresyon koruması: Yeni özellik eklerken eski şeylerin bozulmadığını garanti eder
Canlı dokümantasyon: Testler, kodun nasıl kullanılması gerektiğini gösterir
Cesaretle refactor: Testlerin varsa, kodu güvenle yeniden yapılandırabilirsin
Daha iyi tasarım: Test yazılabilir kod, doğal olarak daha modüler olur
TDD Kısaca
Test-Driven Development (TDD), önce testi yazıp sonra kodu yazma disiplinidir:
Red: Başarısız bir test yaz (henüz kod yok)
Green: Testi geçirecek minimum kodu yaz
Refactor: Kodu temizle, testlerin hâlâ geçtiğinden emin ol
TDD'yi her zaman uygulaman gerekmez ama disiplini öğrenmek tasarım anlayışını geliştirir.
Google Test Kurulum
CMake ile Entegrasyon
Google Test'i projeye eklemenin en temiz yolu FetchContent:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
# Google Test'i indir ve ekle
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
# Ana kütüphane (test edilecek kod)
add_library(mylib src/hesap_makinesi.cpp)
target_include_directories(mylib PUBLIC include/)
# Test çalıştırılabilir dosyası
enable_testing()
add_executable(testler
tests/hesap_makinesi_test.cpp
tests/string_utils_test.cpp
)
target_link_libraries(testler
mylib
GTest::gtest_main # main() fonksiyonunu gtest sağlar
GTest::gmock # Google Mock
)
include(GoogleTest)
gtest_discover_tests(testler)Proje yapısı:
projem/
├── CMakeLists.txt
├── include/
│ └── hesap_makinesi.h
├── src/
│ └── hesap_makinesi.cpp
└── tests/
└── hesap_makinesi_test.cppBuild ve test çalıştırma:
mkdir build && cd build
cmake ..
cmake --build .
ctest --output-on-failureİlk Testleri Yazmak: TEST Makrosu
Test Edilecek Kod
// include/hesap_makinesi.h
#pragma once
#include <stdexcept>
#include <cmath>
class HesapMakinesi {
public:
double topla(double a, double b) const { return a + b; }
double cikar(double a, double b) const { return a - b; }
double carp(double a, double b) const { return a * b; }
double bol(double a, double b) const {
if (b == 0.0) {
throw std::invalid_argument("Sifira bolme hatasi");
}
return a / b;
}
double usAl(double taban, int us) const {
return std::pow(taban, us);
}
bool asal(int n) const {
if (n < 2) return false;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) return false;
}
return true;
}
};TEST Makrosu
TEST(TestSuiteName, TestName) en temel test makrosudur. İlk parametre test grubunu (suite), ikincisi spesifik testi adlandırır:
// tests/hesap_makinesi_test.cpp
#include <gtest/gtest.h>
#include "hesap_makinesi.h"
// === Test Suite: Toplama ===
TEST(ToplamaTest, IkiPozitifSayi) {
HesapMakinesi hesap;
EXPECT_DOUBLE_EQ(hesap.topla(2.0, 3.0), 5.0);
}
TEST(ToplamaTest, NegatifSayilar) {
HesapMakinesi hesap;
EXPECT_DOUBLE_EQ(hesap.topla(-2.0, -3.0), -5.0);
}
TEST(ToplamaTest, SifirIle) {
HesapMakinesi hesap;
EXPECT_DOUBLE_EQ(hesap.topla(42.0, 0.0), 42.0);
}
// === Test Suite: Bolme ===
TEST(BolmeTest, NormalBolme) {
HesapMakinesi hesap;
EXPECT_DOUBLE_EQ(hesap.bol(10.0, 2.0), 5.0);
}
TEST(BolmeTest, SifiraBolmeException) {
HesapMakinesi hesap;
EXPECT_THROW(hesap.bol(10.0, 0.0), std::invalid_argument);
}
TEST(BolmeTest, OndalikSonuc) {
HesapMakinesi hesap;
EXPECT_NEAR(hesap.bol(10.0, 3.0), 3.3333, 0.001);
}
// === Test Suite: Asal ===
TEST(AsalTest, KucukSayilar) {
HesapMakinesi hesap;
EXPECT_FALSE(hesap.asal(0));
EXPECT_FALSE(hesap.asal(1));
EXPECT_TRUE(hesap.asal(2));
EXPECT_TRUE(hesap.asal(3));
}
TEST(AsalTest, BuyukAsallar) {
HesapMakinesi hesap;
EXPECT_TRUE(hesap.asal(97));
EXPECT_TRUE(hesap.asal(7919));
EXPECT_FALSE(hesap.asal(100));
}
TEST(AsalTest, NegatifSayilar) {
HesapMakinesi hesap;
EXPECT_FALSE(hesap.asal(-5));
EXPECT_FALSE(hesap.asal(-1));
}Assertion'lar: EXPECT vs ASSERT
Google Test'te iki assertion ailesi var:
| Prefix | Davranış | Ne Zaman |
|---|---|---|
EXPECT_* | Başarısız olursa test devam eder | Çoğu durumda bunu kullan |
ASSERT_* | Başarısız olursa test durur | Sonraki satırlar bu sonuca bağlıysa |
TEST(VectorTest, ElemanErisimi) {
std::vector<int> v = {10, 20, 30};
// ASSERT: başarısız olursa devam etmenin anlamı yok
ASSERT_EQ(v.size(), 3u); // Boyut 3 değilse v[2]'ye erişmek tehlikeli
// EXPECT: hepsi bağımsız kontrol
EXPECT_EQ(v[0], 10);
EXPECT_EQ(v[1], 20);
EXPECT_EQ(v[2], 30);
}Yaygın Assertion'lar
// Eşitlik
EXPECT_EQ(gercek, beklenen); // gercek == beklenen
EXPECT_NE(a, b); // a != b
// Karşılaştırma
EXPECT_LT(a, b); // a < b
EXPECT_LE(a, b); // a <= b
EXPECT_GT(a, b); // a > b
EXPECT_GE(a, b); // a >= b
// Boolean
EXPECT_TRUE(ifade);
EXPECT_FALSE(ifade);
// String
EXPECT_STREQ(cstr1, cstr2); // C-string eşitlik
EXPECT_STRNE(cstr1, cstr2); // C-string farklılık
EXPECT_STRCASEEQ(s1, s2); // Büyük/küçük harf duyarsız
// Floating point (kayan nokta karşılaştırma hassas)
EXPECT_FLOAT_EQ(a, b); // ~4 ULP tolerans
EXPECT_DOUBLE_EQ(a, b); // ~4 ULP tolerans
EXPECT_NEAR(a, b, tolerans); // |a-b| < tolerans
// Exception
EXPECT_THROW(ifade, ExceptionTipi); // Belirli exception fırlatmalı
EXPECT_ANY_THROW(ifade); // Herhangi exception fırlatmalı
EXPECT_NO_THROW(ifade); // Exception fırlatMAMALI⚠️ Floating point karşılaştırmada `EXPECT_EQ` kullanma! Kayan nokta aritmetiği yüzünden
0.1 + 0.2 != 0.3olabilir. Her zamanEXPECT_DOUBLE_EQveyaEXPECT_NEARkullan.
Test Fixture: Ortak Kurulum (TEST_F)
Her test fonksiyonunda aynı nesneyi oluşturmak tekrar. Test fixture ile ortak kurulum bir kez yazılır, her test için otomatik çalıştırılır. Java'daki @BeforeEach / @AfterEach karşılığıdır.
#include <gtest/gtest.h>
#include <vector>
#include <string>
#include <algorithm>
// Test edilecek sınıf
class Envanter {
public:
void urunEkle(const std::string& isim, int miktar) {
urunler_.push_back({isim, miktar});
}
int toplamUrun() const { return static_cast<int>(urunler_.size()); }
int stokBul(const std::string& isim) const {
for (const auto& u : urunler_) {
if (u.isim == isim) return u.miktar;
}
return -1; // bulunamadı
}
bool urunSil(const std::string& isim) {
auto it = std::remove_if(urunler_.begin(), urunler_.end(),
[&](const auto& u) { return u.isim == isim; });
if (it == urunler_.end()) return false;
urunler_.erase(it, urunler_.end());
return true;
}
private:
struct Urun { std::string isim; int miktar; };
std::vector<Urun> urunler_;
};
// Fixture sınıfı — ::testing::Test'ten türer
class EnvanterTest : public ::testing::Test {
protected:
// Her testten ÖNCE çalışır (Java @BeforeEach)
void SetUp() override {
envanter.urunEkle("Laptop", 50);
envanter.urunEkle("Mouse", 200);
envanter.urunEkle("Klavye", 150);
}
// Her testten SONRA çalışır (Java @AfterEach)
void TearDown() override {
// Gerekli temizlik (bu örnekte yok)
}
Envanter envanter; // Her test taze bir kopya alır
};
// TEST_F kullan — F = Fixture
TEST_F(EnvanterTest, BaslangictaUcUrunVar) {
EXPECT_EQ(envanter.toplamUrun(), 3);
}
TEST_F(EnvanterTest, StokSorgulama) {
EXPECT_EQ(envanter.stokBul("Laptop"), 50);
EXPECT_EQ(envanter.stokBul("Mouse"), 200);
EXPECT_EQ(envanter.stokBul("Tablet"), -1); // yok
}
TEST_F(EnvanterTest, UrunSilme) {
ASSERT_TRUE(envanter.urunSil("Mouse"));
EXPECT_EQ(envanter.toplamUrun(), 2);
EXPECT_EQ(envanter.stokBul("Mouse"), -1); // artık yok
}
TEST_F(EnvanterTest, OlmayanUrunSilme) {
EXPECT_FALSE(envanter.urunSil("Tablet"));
EXPECT_EQ(envanter.toplamUrun(), 3); // değişmedi
}
TEST_F(EnvanterTest, YeniUrunEkleme) {
envanter.urunEkle("Monitor", 30);
EXPECT_EQ(envanter.toplamUrun(), 4);
EXPECT_EQ(envanter.stokBul("Monitor"), 30);
}Her TEST_F çalıştığında: yeni bir EnvanterTest nesnesi oluşturulur → SetUp() çağrılır → test çalışır → TearDown() çağrılır → nesne yıkılır. Testler birbirini asla etkilemez.
Parameterized Tests (TEST_P)
Aynı test mantığını farklı verilerle çalıştırmak istediğinde her seferinde yeni test yazmak yerine, parametrik testler kullanırsın:
#include <gtest/gtest.h>
#include <tuple>
#include <cmath>
#include <string>
// Test verisi: {girdi, beklenen sonuç}
struct AsalTestVerisi {
int sayi;
bool beklenen;
std::string aciklama;
};
// Fixture: TestWithParam<ParametreTipi>
class AsalParametrikTest : public ::testing::TestWithParam<AsalTestVerisi> {
protected:
HesapMakinesi hesap;
};
// TEST_P: parametrik test
TEST_P(AsalParametrikTest, AsalKontrol) {
auto [sayi, beklenen, aciklama] = GetParam();
EXPECT_EQ(hesap.asal(sayi), beklenen)
<< "Basarisiz: " << aciklama;
}
// Test verilerini tanımla ve bağla
INSTANTIATE_TEST_SUITE_P(
AsalSayilar, // Instance adı
AsalParametrikTest, // Fixture sınıfı
::testing::Values(
AsalTestVerisi{2, true, "En kucuk asal"},
AsalTestVerisi{3, true, "Kucuk asal"},
AsalTestVerisi{4, false, "2*2 asal degil"},
AsalTestVerisi{17, true, "Orta asal"},
AsalTestVerisi{0, false, "Sifir asal degil"},
AsalTestVerisi{1, false, "Bir asal degil"},
AsalTestVerisi{-7, false, "Negatif asal degil"},
AsalTestVerisi{7919, true, "Buyuk asal"},
AsalTestVerisi{7920, false, "Buyuk asal olmayan"}
)
);Bu 9 test vakası tek bir TEST_P ile yazıldı. Yeni vaka eklemek için sadece Values() listesine bir satır eklersin. Google Test her birini ayrı test olarak raporlar.
Daha basit durumlar için ::testing::Values ile doğrudan tuple da kullanılabilir:
class CarpimTest : public ::testing::TestWithParam<std::tuple<double, double, double>> {
protected:
HesapMakinesi hesap;
};
TEST_P(CarpimTest, CarpimDogrulugu) {
auto [a, b, beklenen] = GetParam();
EXPECT_DOUBLE_EQ(hesap.carp(a, b), beklenen);
}
INSTANTIATE_TEST_SUITE_P(
CarpimVerileri, CarpimTest,
::testing::Values(
std::make_tuple(2.0, 3.0, 6.0),
std::make_tuple(-1.0, 5.0, -5.0),
std::make_tuple(0.0, 999.0, 0.0),
std::make_tuple(0.1, 0.2, 0.02)
)
);Google Mock: Bağımlılıkları Taklit Etmek
Problem
Test etmek istediğin sınıf, bir veritabanına, ağ servisine veya dosya sistemine bağımlı. Bu bağımlılıkları gerçekten çalıştırmak testleri yavaşlatır, kırılgan yapar ve tekrarlanamaz kılar. Mock nesneler gerçek bağımlılıkların yerini alır ve beklediğin davranışı simüle eder.
Temel Kullanım
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <memory>
#include <string>
#include <vector>
// Arayüz — bağımlılık
class VeritabaniArayuz {
public:
virtual ~VeritabaniArayuz() = default;
virtual bool baglan(const std::string& connStr) = 0;
virtual std::vector<std::string> sorgula(const std::string& sql) = 0;
virtual bool ekle(const std::string& tablo, const std::string& veri) = 0;
virtual int satirSayisi(const std::string& tablo) = 0;
};
// Mock sınıfı — MOCK_METHOD ile
class MockVeritabani : public VeritabaniArayuz {
public:
MOCK_METHOD(bool, baglan, (const std::string& connStr), (override));
MOCK_METHOD(std::vector<std::string>, sorgula, (const std::string& sql), (override));
MOCK_METHOD(bool, ekle, (const std::string& tablo, const std::string& veri), (override));
MOCK_METHOD(int, satirSayisi, (const std::string& tablo), (override));
};
// Test edilecek sınıf — veritabanına bağımlı
class KullaniciServisi {
public:
explicit KullaniciServisi(std::shared_ptr<VeritabaniArayuz> db)
: db_(std::move(db)) {}
bool kullaniciEkle(const std::string& isim) {
if (!db_->baglan("production_db")) return false;
return db_->ekle("kullanicilar", isim);
}
std::vector<std::string> tumKullanicilar() {
db_->baglan("production_db");
return db_->sorgula("SELECT * FROM kullanicilar");
}
int kullaniciSayisi() {
return db_->satirSayisi("kullanicilar");
}
private:
std::shared_ptr<VeritabaniArayuz> db_;
};
// === TESTLER ===
using ::testing::Return;
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::AtLeast;
class KullaniciServisiTest : public ::testing::Test {
protected:
void SetUp() override {
mockDb = std::make_shared<MockVeritabani>();
servis = std::make_unique<KullaniciServisi>(mockDb);
}
std::shared_ptr<MockVeritabani> mockDb;
std::unique_ptr<KullaniciServisi> servis;
};
TEST_F(KullaniciServisiTest, KullaniciEklemeBaşarili) {
// Beklentileri ayarla: mock ne yapacak?
EXPECT_CALL(*mockDb, baglan("production_db"))
.WillOnce(Return(true));
EXPECT_CALL(*mockDb, ekle("kullanicilar", "Tolga"))
.WillOnce(Return(true));
// Test et
EXPECT_TRUE(servis->kullaniciEkle("Tolga"));
}
TEST_F(KullaniciServisiTest, BaglantiBasarisizOlursa) {
EXPECT_CALL(*mockDb, baglan(_)) // herhangi parametre
.WillOnce(Return(false));
// baglan false dönerse ekle çağrılMAMALI
EXPECT_CALL(*mockDb, ekle(_, _)).Times(0);
EXPECT_FALSE(servis->kullaniciEkle("Tolga"));
}
TEST_F(KullaniciServisiTest, TumKullanicilariGetir) {
std::vector<std::string> beklenen = {"Tolga", "Ayse", "Mehmet"};
EXPECT_CALL(*mockDb, baglan(_))
.WillOnce(Return(true));
EXPECT_CALL(*mockDb, sorgula(HasSubstr("SELECT")))
.WillOnce(Return(beklenen));
auto sonuc = servis->tumKullanicilar();
ASSERT_EQ(sonuc.size(), 3u);
EXPECT_EQ(sonuc[0], "Tolga");
}
TEST_F(KullaniciServisiTest, KullaniciSayisi) {
EXPECT_CALL(*mockDb, satirSayisi("kullanicilar"))
.WillOnce(Return(42));
EXPECT_EQ(servis->kullaniciSayisi(), 42);
}EXPECT_CALL Detayları
// Kaç kez çağrılacağını belirle
EXPECT_CALL(mock, fonksiyon(_))
.Times(3); // Tam 3 kez
EXPECT_CALL(mock, fonksiyon(_))
.Times(AtLeast(1)); // En az 1 kez
EXPECT_CALL(mock, fonksiyon(_))
.Times(AtMost(5)); // En fazla 5 kez
// Farklı çağrılarda farklı sonuç
EXPECT_CALL(mock, baglan(_))
.WillOnce(Return(false)) // İlk çağrıda false
.WillOnce(Return(true)) // İkinci çağrıda true
.WillRepeatedly(Return(true)); // Sonrası hep true
// Varsayılan davranış (beklenti olmadan)
ON_CALL(mock, satirSayisi(_))
.WillByDefault(Return(0));Yaygın Matcher'lar
using namespace ::testing;
_ // Herhangi değer
Eq(5) // == 5
Ne(0) // != 0
Lt(10) // < 10
Gt(0) // > 0
HasSubstr("SELECT") // String içinde arama
StartsWith("http") // String başlangıcı
IsEmpty() // Boş container
SizeIs(3) // Container boyutu
Contains("admin") // Container eleman içerir💡 Mock kullanmanın anahtarı: Test edilecek sınıfın bağımlılıklarını arayüz (interface) üzerinden almasını sağla. Constructor injection ile bağımlılığı dışarıdan ver. Bu sayede test sırasında mock, production'da gerçek implementasyonu kullanırsın.
Test Organizasyonu
İsimlendirme Kuralları
İyi test isimleri, testin ne yaptığını okumadan anlatır:
// ❌ Kötü isimler
TEST(Test1, Test_A) { ... }
TEST(Hesap, Bol1) { ... }
// ✅ İyi isimler — TestSuite_Senaryo formatı
TEST(BolmeTest, SifiraBolmeExceptionFirlatir) { ... }
TEST(BolmeTest, PozitifSayilariDogurBoler) { ... }
TEST(BolmeTest, NegatifSayilarlaCalısir) { ... }
// ✅ Alternatif: Should/When formatı
TEST(KullaniciServisi, EklemeBasariliOldugunddaTrueDoner) { ... }
TEST(KullaniciServisi, BaglantiKoptuğundaFalseDoner) { ... }Dosya Yapısı
tests/
├── CMakeLists.txt
├── hesap_makinesi_test.cpp // HesapMakinesi sınıfının testleri
├── envanter_test.cpp // Envanter sınıfının testleri
├── kullanici_servisi_test.cpp // KullaniciServisi + mock
├── integration/
│ └── veritabani_test.cpp // Gerçek DB ile entegrasyon testleri
└── fixtures/
└── test_data.json // Test verileriHer kaynak dosya için bir test dosyası. Test dosyası adı _test.cpp ile biter. Entegrasyon testleri ayrı klasörde.
Test Filtreleme
# Sadece belirli testleri çalıştır
./testler --gtest_filter="BolmeTest.*" # BolmeTest suite'indeki tüm testler
./testler --gtest_filter="*Exception*" # Adında Exception geçenler
./testler --gtest_filter="AsalTest.*:BolmeTest.*" # İki suite birden
./testler --gtest_filter="-*Yavas*" # Yavaş testleri hariç tut
# Verbose çıktı
./testler --gtest_print_time=1
# Testleri rastgele sırada çalıştır (sıra bağımlılığı yakala)
./testler --gtest_shuffle
# Başarısız testleri tekrar çalıştır
./testler --gtest_repeat=3 --gtest_break_on_failureCI/CD'de Test Çalıştırma: CTest
CMake'in test çalıştırıcısı CTest, Google Test ile sorunsuz entegre olur:
# Build sonrası
cd build
ctest # Tüm testleri çalıştır
ctest --output-on-failure # Sadece başarısız testlerin çıktısını göster
ctest -j$(nproc) # Paralel çalıştır
ctest -R "BolmeTest" # Regex ile filtrele
ctest --test-dir build # Build dizinini belirtGitHub Actions Örneği
# .github/workflows/test.yml
name: C++ Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build -j$(nproc)
- name: Test
run: ctest --test-dir build --output-on-failure -j$(nproc)Her push'ta ve pull request'te testler otomatik çalışır. Başarısız test varsa CI pipeline kırmızı olur — merge edilmez.
Gerçek Dünya Örneği: Banka Hesabı Test Suite
Birden fazla test tekniğini bir arada kullanan bütünleşik bir örnek:
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <memory>
#include <string>
#include <stdexcept>
// === Arayüzler ===
class BildirimServisi {
public:
virtual ~BildirimServisi() = default;
virtual void bildirimGonder(const std::string& mesaj) = 0;
};
class DenetimLogu {
public:
virtual ~DenetimLogu() = default;
virtual void islemKaydet(const std::string& tur, double miktar) = 0;
};
// === Ana Sınıf ===
class BankaHesabi {
public:
BankaHesabi(std::string sahip, double bakiye,
std::shared_ptr<BildirimServisi> bildirim,
std::shared_ptr<DenetimLogu> denetim)
: sahip_(std::move(sahip)), bakiye_(bakiye)
, bildirim_(std::move(bildirim)), denetim_(std::move(denetim)) {}
void paraYatir(double miktar) {
if (miktar <= 0) throw std::invalid_argument("Miktar pozitif olmali");
bakiye_ += miktar;
denetim_->islemKaydet("YATIRMA", miktar);
if (miktar >= 10000) {
bildirim_->bildirimGonder("Buyuk yatirma: " + std::to_string(miktar));
}
}
void paraCek(double miktar) {
if (miktar <= 0) throw std::invalid_argument("Miktar pozitif olmali");
if (miktar > bakiye_) throw std::runtime_error("Yetersiz bakiye");
bakiye_ -= miktar;
denetim_->islemKaydet("CEKME", miktar);
}
double bakiye() const { return bakiye_; }
const std::string& sahip() const { return sahip_; }
private:
std::string sahip_;
double bakiye_;
std::shared_ptr<BildirimServisi> bildirim_;
std::shared_ptr<DenetimLogu> denetim_;
};
// === Mock'lar ===
class MockBildirim : public BildirimServisi {
public:
MOCK_METHOD(void, bildirimGonder, (const std::string&), (override));
};
class MockDenetim : public DenetimLogu {
public:
MOCK_METHOD(void, islemKaydet, (const std::string&, double), (override));
};
// === Fixture ===
class BankaHesabiTest : public ::testing::Test {
protected:
void SetUp() override {
mockBildirim = std::make_shared<MockBildirim>();
mockDenetim = std::make_shared<MockDenetim>();
hesap = std::make_unique<BankaHesabi>(
"Tolga", 1000.0, mockBildirim, mockDenetim);
}
std::shared_ptr<MockBildirim> mockBildirim;
std::shared_ptr<MockDenetim> mockDenetim;
std::unique_ptr<BankaHesabi> hesap;
};
// === Testler ===
using ::testing::_;
using ::testing::HasSubstr;
TEST_F(BankaHesabiTest, ParaYatirmaBakiyeArtirir) {
EXPECT_CALL(*mockDenetim, islemKaydet("YATIRMA", 500.0));
hesap->paraYatir(500.0);
EXPECT_DOUBLE_EQ(hesap->bakiye(), 1500.0);
}
TEST_F(BankaHesabiTest, BuyukYatirmaBildirimGonderir) {
EXPECT_CALL(*mockDenetim, islemKaydet(_, _));
EXPECT_CALL(*mockBildirim, bildirimGonder(HasSubstr("Buyuk yatirma")));
hesap->paraYatir(15000.0);
}
TEST_F(BankaHesabiTest, KucukYatirmaBildirimGondermez) {
EXPECT_CALL(*mockDenetim, islemKaydet(_, _));
EXPECT_CALL(*mockBildirim, bildirimGonder(_)).Times(0); // ÇAĞRILMAMALI
hesap->paraYatir(500.0);
}
TEST_F(BankaHesabiTest, YetersizBakiyeException) {
EXPECT_THROW(hesap->paraCek(5000.0), std::runtime_error);
EXPECT_DOUBLE_EQ(hesap->bakiye(), 1000.0); // bakiye değişmedi
}
TEST_F(BankaHesabiTest, NegatifMiktarRedEder) {
EXPECT_THROW(hesap->paraYatir(-100.0), std::invalid_argument);
EXPECT_THROW(hesap->paraCek(0.0), std::invalid_argument);
}
TEST_F(BankaHesabiTest, ParaCekmeBakiyeDusurur) {
EXPECT_CALL(*mockDenetim, islemKaydet("CEKME", 300.0));
hesap->paraCek(300.0);
EXPECT_DOUBLE_EQ(hesap->bakiye(), 700.0);
}Bu örnekte:
TEST_F ile fixture kullanılıyor (ortak SetUp)
Mock nesnelerle gerçek servisler simüle ediliyor
EXPECT_CALL ile fonksiyonların doğru parametrelerle çağrıldığı doğrulanıyor
EXPECT_THROW ile exception davranışı test ediliyor
Her test bağımsız, tekrarlanabilir ve hızlı
Yaygın Hatalar
Test sırası bağımlılığı: Testler birbirinin durumuna bağımlı olmamalı. Her test taze bir fixture almalı.
Aşırı mock'lama: Her şeyi mock'lama. Sadece dış bağımlılıkları (DB, ağ, dosya sistemi) mock'la. İç sınıfları mock'lamak testleri kırılgan yapar.
Implementation testi: Fonksiyonun *ne* yaptığını test et, *nasıl* yaptığını değil. İç detayı test edersen, refactor'da testler kırılır.
Tek assertion per test zorunluluğu: Bir testte birden fazla assertion olabilir — ama tek bir davranışı test etmeli. "Para yatırma" testinde hem bakiye kontrolü hem denetim kaydı kontrol edilebilir.
Özet
Unit testing kodun doğruluğunu otomatik olarak doğrular. Elle test etmek ölçeklenmez — testler senin için kontrol eder.
Google Test C++'ın en yaygın test framework'üdür. CMake + FetchContent ile kolayca entegre olur.
TEST makrosu basit testler, TEST_F fixture ile ortak kurulum, TEST_P parametrik testler için kullanılır.
EXPECT_* başarısızlıkta devam eder (çoğu durumda bunu kullan), ASSERT_* durur (sonraki satırlar buna bağlıysa).
Google Mock ile bağımlılıkları taklit edersin.
MOCK_METHODile mock sınıf,EXPECT_CALLile beklenti, matcher'larla parametre kontrolü.CTest ile CI/CD pipeline'ında testleri otomatik çalıştırırsın. Her commit'te testler geçmezse merge yasak.
Test yazılabilir kod = iyi tasarlanmış kod. Interface'ler üzerinden bağımlılık al, constructor injection kullan.
AI Asistan
Sorularını yanıtlamaya hazır