Unit test, dış bağımlılığı olmayan ve aynı girdiye karşılık belirli bir çıktı üreten saf fonksiyonları doğrulamak için uygundur. Repository, veritabanı, HTTP istemcisi veya serileştirme gibi gerçek sistem sınırlarını birlikte kontrol eden bir akışta ise integration test daha doğru seçimdir.
Ancak bu seçim “bir kod parçası yalnızca tek tür testle sınanır” anlamına gelmez. Aynı sipariş servisi için iş kuralını unit test ile, veritabanına doğru sorgunun gönderilip sonucunun doğru işlendiğini integration test ile doğrulayabilirsiniz. Karar verirken bağımlılığı, testin işaret edeceği hata türünü, beklenen çalışma hızını ve bakım maliyetini birlikte değerlendirmek gerekir.
Unit Test mi, Integration Test mi? Kısa Karar
İlk karar için şu ayrımı kullanın: Test etmek istediğiniz kod yalnızca aldığı girdiyi işleyip bir çıktı mı üretiyor, yoksa başka bir sistemle konuşarak mı çalışıyor?
- Unit test: Fonksiyon veya sınıf, dış sistemlerden izole edilerek tek başına sınanır.
- Integration test: Birden fazla bileşenin gerçek veya gerçeğe çok yakın bir sistem sınırında birlikte çalışması doğrulanır.
Örneğin bir siparişte ürün fiyatlarını toplayan fonksiyon; liste, sayı ve kurallar üzerinden çalışıyor, veritabanına ya da ağa bağlanmıyorsa unit test için iyi bir adaydır. Buna karşılık sipariş servisi repository üzerinden kayıt okuyorsa, ödeme servisine HTTP isteği gönderiyorsa veya JSON verisini nesneye dönüştürüyorsa artık yalnızca tek bir fonksiyonun davranışını değil, bileşenler arasındaki bağlantıyı da test etmek gerekir.
Kararı netleştirmek için her test adayı hakkında dört soru sorun:
- Bağımlılık var mı? Veritabanı, dosya sistemi, ağ, saat, rastgelelik veya başka bir servis devreye giriyor mu?
- Test kırıldığında hangi hata işaret edilecek? İş kuralı mı bozuldu, yoksa iki bileşen arasındaki bağlantı mı çalışmıyor?
- Ne kadar hızlı çalışması gerekiyor? Her kod değişikliğinde saniyeler içinde geri bildirim mi alınmalı, yoksa daha kapsamlı bir doğrulama için daha uzun süre kabul edilebilir mi?
- Bakım maliyeti kabul edilebilir mi? Test, sistemdeki değişikliklerden gereksiz yere etkileniyor mu?
Bu dört ölçüt birbirinden bağımsız değildir. Gerçek bir veritabanına bağlanan test, daha geniş bir davranışı doğrulayabilir; fakat bağlantı ayarları, test verisi ve şema değişiklikleri nedeniyle daha fazla bakım gerektirebilir. Saf bir fonksiyonun unit testi ise genellikle daha dar kapsamlıdır ama hatanın kaynağı hakkında daha net sinyal verir.
Bağımlılık Farkı Test Türünü Nasıl Belirler?

Saf sipariş toplamı fonksiyonu ile repository kullanan bir sipariş servisini yan yana düşünmek, farkı görünür hâle getirir. Saf fonksiyon; ürün fiyatlarını, miktarları veya indirim kurallarını alır ve sonucu döndürür. Aynı girdiyi verdiğinizde aynı sonucu üretmesi beklenir. Veritabanı bağlantısına, ağ erişimine, dosyaya veya dış bir servisin yanıtına ihtiyaç duymaz.
Bu nedenle saf fonksiyon kolayca izole edilir. Test sırasında yalnızca fonksiyonun aldığı değerler ve ürettiği sonuç önemlidir. Test başarısız olduğunda genellikle iş kuralında, aritmetik işlemde veya sınır durumunun ele alınışında bir sorun ararsınız.
Repository kullanan sipariş servisi ise daha geniş bir akışa sahiptir. Servis; sipariş kaydını okumak için sorgu çalıştırabilir, veritabanından gelen alanları nesneye dönüştürebilir, eksik kayıt durumunu yorumlayabilir ve sonucu başka bir katmana aktarabilir. HTTP istemcisi kullanılıyorsa bağlantı kurulması, istek gövdesi, durum kodu, yanıt biçimi ve zaman aşımı gibi başka sınırlar da devreye girer.
Bu durumda iki farklı test amacı ortaya çıkar:
- Servisin iş kuralı, dış sistemlerden ayrılarak test edilebilir. Bu yaklaşımda repository veya HTTP istemcisi mock ile temsil edilir.
- Servisin gerçek repository, test veritabanı veya kontrollü bir HTTP sunucusuyla doğru iletişim kurduğu integration test ile doğrulanabilir.
Buradaki ayrım önemlidir: Mock kullanılan servis testi, gerçek entegrasyon testi değildir. Mock, dış bağımlılığın yerine geçen kontrollü bir nesnedir ve testin izolasyonunu sağlar. Örneğin mock repository “şu siparişi döndürdüm” diyebilir; fakat gerçek veritabanında sorgunun doğru tabloya gittiğini, kolon adlarının eşleştiğini veya dönüşümün gerçek veri biçimiyle uyumlu olduğunu kanıtlamaz.
Gerçek bir integration testin en az bir gerçek sistem sınırını doğrulaması beklenir. Bu sınır test veritabanı, kontrollü bir HTTP sunucusu, gerçek serileştirme katmanı veya uygulamanın kullandığı başka bir bileşen olabilir. Amaç, bileşenlerin yalnızca varsayımsal bir mock sözleşmesine değil, gerçek çalışma biçimine uygun şekilde bağlandığını görmektir.
| Bağımlılık türü | Doğrulanan katman | Beklenen hata sinyali |
|---|---|---|
| Hiçbir dış bağımlılık yok | Tek fonksiyon veya sınıfın iş kuralı | Hesaplama, koşul veya sınır durumu hatası |
| Mock repository veya mock HTTP istemcisi | Servis mantığı ve bağımlılıkla kurulan beklenen çağrı | İş akışı, çağrı parametresi veya hata yönetimi hatası |
| Test veritabanı | Uygulama ile veritabanı arasındaki gerçek bağlantı | Sorgu, şema, dönüşüm veya veri erişimi uyumsuzluğu |
| Kontrollü HTTP sunucusu | İstek, yanıt ve serileştirme sınırı | Durum kodu, gövde biçimi, bağlantı veya zaman aşımı uyumsuzluğu |
Saf Sipariş Toplamı Fonksiyonu İçin Unit Test Örneği
Aşağıdaki Python örneğinde sipariş toplamı yalnızca fiyat listesini işler. Negatif fiyatlar geçersiz kabul edilir, boş liste ise sıfır döndürür. Fonksiyon herhangi bir veritabanına veya servise bağlanmadığı için test girdileri tamamen kontrol edilebilir.
import pytest
def siparis_toplami(fiyatlar):
if fiyatlar is None or any(fiyat < 0 for fiyat in fiyatlar):
raise ValueError("Geçersiz fiyat")
return sum(fiyatlar)
def test_siparis_toplami_normal_durumda_toplami_doner():
assert siparis_toplami([120, 80, 50]) == 250
def test_siparis_toplami_bos_listede_sifir_doner():
assert siparis_toplami([]) == 0
def test_siparis_toplami_negatif_fiyatta_hata_verir():
with pytest.raises(ValueError):
siparis_toplami([100, -20])
Normal durumda beklenen sonuç 250 değeridir. Boş liste için sum işleminin sonucu 0 olduğundan, ayrı bir iş kuralı tanımlanmadıysa bu davranış açıkça test edilebilir. Negatif fiyat ise sessizce yanlış toplam üretmek yerine ValueError oluşturur.
Test adlarında amaç ve beklenen davranış görünür olmalıdır. Türkçe projelerde metod_durum_beklenensonuc kalıbı kullanılabilir; örneğin siparis_toplami_bos_listede_sifir_doner adı, testin hangi durumda ne beklediğini doğrudan anlatır. Buradaki biçim, ekibin mevcut adlandırma standardına uyarlanmalıdır.
Kendi saf fonksiyonunuzu test etmeye başlamadan önce şu mini kontrol listesini kullanın:
- Aynı girdiyi tekrar verdiğimde aynı sonucu üretiyor mu?
- Fonksiyonun dışarıda gözlemlenebilen bir yan etkisi var mı?
- Beklenmeyen veya sınır niteliğindeki hangi girdiler davranışı değiştirebilir?
Bu sorulara verdiğiniz cevaplar, bir sonraki adımda yalnızca unit test yazmanın yeterli olup olmadığını anlamanıza yardımcı olur. Fonksiyon dış sistemlerle konuşmaya başladığında, testin artık sadece hesaplamayı değil, sistem sınırındaki gerçek uyumu da ele alması gerekir.
Mock Kullanılan Servis Testi Neyi Kanıtlar, Neyi Gizler?
Bir servis sınıfı, sipariş toplamını hesaplamak için repository gibi bir veri erişim katmanına ihtiyaç duyuyorsa, bu servisin iş kuralını gerçek veritabanına bağlanmadan test edebilirsiniz. Bunun için repository nesnesi mock olarak hazırlanır ve servisin beklediği veri kontrollü biçimde döndürülür.
Bu testin amacı SQL sorgusunu veya veritabanı bağlantısını doğrulamak değildir. Amaç; servisin repository’den aldığı sipariş kalemlerini doğru kullandığını, toplamı doğru hesapladığını ve beklenen çağrıyı yaptığını görmektir. Bu nedenle test, gerçek entegrasyon testi değil, bağımlılığı izole eden servis testi olarak adlandırılmalıdır.
Java ile mock kullanılan servis testi
Aşağıdaki örnekte servis, sipariş kalemlerini bir repository üzerinden alır. Testte repository’nin cevabı mock ile belirlenir; böylece test veritabanına ihtiyaç duymaz.
List<OrderItem> items = List.of(
new OrderItem("Klavye", 2, 500),
new OrderItem("Fare", 1, 300)
);
OrderRepository repository = mock(OrderRepository.class);
when(repository.findItemsByOrderId(10L)).thenReturn(items);
OrderService service = new OrderService(repository);
int total = service.calculateTotal(10L);
assertEquals(1300, total);
verify(repository).findItemsByOrderId(10L);
Burada test iki davranışı birlikte kontrol eder: Servis, 10L numaralı sipariş için repository’ye doğru çağrıyı yapar ve repository’den gelen kalemleri kullanarak toplamı 1300 olarak hesaplar. Repository’nin gerçekten bu veriyi SQL ile bulup bulmadığı ise bu testin konusu değildir.
Bu test neyi kanıtlar?
- Servisin doğru sipariş kimliğiyle repository çağrısı yaptığını,
- Repository’den dönen verileri iş kuralına uygun biçimde işlediğini,
- Ürün adedi ile birim fiyatı doğru çarptığını,
- Bağımlılıktan gelen kontrollü bir cevap karşısında beklenen sonucu ürettiğini,
- Testin dış sistemlerden bağımsız ve tekrarlanabilir çalışabildiğini.
Bu test neyi gizler?
Mock, gerçek sistemin davranışını taklit eder; gerçek sistemi çalıştırmaz. Bu nedenle yukarıdaki test şu sorunları yakalayamaz:
- SQL sorgusunun hatalı yazılması,
- Tablo veya sütun adlarının Java nesneleriyle uyuşmaması,
- Gerçek veritabanı şemasının beklenenden farklı olması,
- Veritabanı bağlantısının kopması ya da erişim bilgilerinin yanlış olması,
- Ağ gecikmesi ve zaman aşımı,
- HTTP veya başka bir protokoldeki serileştirme ve ayrıştırma hataları,
- Gerçek repository’nin boş, eksik veya beklenmedik biçimde veri döndürmesi.
Dolayısıyla mock kullanılan bir servis testi hızlı, izole ve kontrollüdür; fakat sahte cevabın gerçek veri erişim katmanıyla uyumlu olduğunu kanıtlamaz. Gerçek repository ile gerçek veritabanı arasındaki uyumu ayrıca kontrol etmek istiyorsanız, veritabanı bağlantısını da içeren bir integration test yazmanız gerekir.
Bu ayrımı uygulamalı Python ve Java çalışırken netleştirmek isteyenler, canlı sınıflı online yazılım eğitimi kapsamında test senaryolarını kodun katmanlarına göre değerlendirebilir.
Dört Ölçütlü Karar Ağacı: Hangi Testi Yazmalıyım?

Test türünü seçerken “Her şey için unit test” veya “Her özellik için integration test” yaklaşımı yerine dört soruyu sırayla yanıtlamak daha sağlıklıdır. Aşağıdaki çerçeve, testin neyi kanıtlamasını istediğinizi netleştirir.
- Kod gerçek bir bağımlılıkla konuşuyor mu? Veritabanı, dosya sistemi, ağ, HTTP servisi veya harici bir API varsa doğrudan bağımlılık içeren bir davranış söz konusudur. Hiçbir dış kaynağa ihtiyaç duymayan saf hesaplama fonksiyonları için unit test genellikle yeterlidir.
- Hata kırıldığında hangi katmanı göstermeli? İş kuralındaki bir hata hızlıca görünür olsun istiyorsanız bağımlılığı mock’layarak servis davranışını izole edin. Sorgu, şema, bağlantı veya protokol uyumunu kontrol etmek istiyorsanız gerçek bağımlılığı testin içine alın.
- Test çok sık ve hızlı çalışmalı mı? Geliştirme sırasında sürekli çalıştırılacak testlerde mock veya saf unit test daha uygun olabilir. Gerçek veritabanı ve ağ kullanan testler çevresel hazırlık gerektirebildiği için ayrı bir test grubunda konumlandırılabilir.
- Gerçek bağımlılığı kurmanın bakım maliyeti kabul edilebilir mi? Test veritabanı, bağlantı ayarları, temizleme işlemleri ve örnek veri yönetimi ek bakım gerektirir. Bu maliyet, testin doğrulamak istediği entegrasyon davranışı için anlamlıysa tercih edilmelidir.
| Bağımlılık | Hata netliği | Çalışma hızı | Bakım maliyeti | Önerilen test |
|---|---|---|---|---|
| Yok, saf hesaplama | İş kuralı hatası doğrudan görünür | Çok hızlı | Düşük | Unit test |
| Var, mock ile izole edilmiş | Servis davranışı ve çağrı akışı netleşir | Hızlı | Düşük-orta | Mock’lu servis testi |
| Var, gerçek sistemle çalışıyor | Sorgu, şema, bağlantı veya protokol hatası görünür | Görece daha yavaş olabilir | Orta-yüksek | Integration test |
Sipariş toplamı örneğinde üç farklı amaç, üç farklı test doğurur:
- Saf hesaplama: Bir listedeki adet ve fiyatları toplayan fonksiyon, dış bağımlılığı olmadığı için unit test ile doğrulanır.
- Servis davranışı: Sipariş kalemlerini repository’den alıp toplamı hesaplayan servis, mock’lu test ile izole edilir.
- Gerçek uyum: Repository’nin gerçek veritabanına bağlanması, doğru sorguyu çalıştırması ve sonucu beklenen nesneye dönüştürmesi integration test ile kontrol edilir.
Bu yaklaşımda test türü kodun adına göre değil, kanıtlamak istediğiniz davranışa göre belirlenir. Aynı servis için hem mock’lu servis testi hem de gerçek repository entegrasyon testi yazılması çelişki değildir; iki test farklı hata sınıflarını görünür hâle getirir.
Test Adlandırma ve Sınır Durumlarını Seçme
İyi bir test adı, yalnızca hangi metodun çağrıldığını değil, hangi koşulda hangi sonucu beklediğinizi de anlatır. Pratik bir kalıp olarak metod_durum_beklenenSonuç biçimi kullanılabilir.
- Java:
calculateTotal_whenOrderIsEmpty_returnsZero - Python:
siparisToplami_bosListe_sifirDondurur
Bu adlandırma, test başarısız olduğunda ekrana yalnızca “calculateTotal başarısız” yazılmasından daha açıklayıcıdır. Hangi koşulun bozulduğunu ve beklenen davranışın ne olduğunu doğrudan gösterir.
Sınır durumlarını seçmek için kontrol listesi
- Veri yokluğu: Boş sipariş listesinde sonuç sıfır mı olmalı, yoksa hata mı dönmeli?
- Minimum ve maksimum değer: Tek ürün, tek adet veya izin verilen en yüksek miktar doğru işleniyor mu?
- Negatif ya da geçersiz değer: Negatif fiyat veya sıfırdan küçük adet reddedilmeli mi?
- None/null: Repository null değer döndürürse servis bunu güvenli biçimde ele alıyor mu?
- Tekrar eden kayıt: Aynı ürün iki kez geldiğinde kayıtlar birleştirilmeli mi, ayrı ayrı mı hesaplanmalı?
- Bağımlılık hatası: Repository veri erişim hatası döndürdüğünde servis hatayı iletmeli, dönüştürmeli veya alternatif sonuç üretmeli mi?
Her fonksiyon için rastgele çok sayıda test yazmak yerine, sonucu veya iş kuralını değiştiren sınırları seçin. Örneğin yalnızca iki normal fiyatı test etmek, negatif fiyatın reddedildiğini veya boş listenin nasıl ele alındığını göstermez.
Hata mesajının anlamı da test tasarımında önemlidir. Unit test yalnızca hesaplama hatasını işaret ediyorsa teşhis daha hızlı yapılır. Integration test ise veri erişimi, şema veya protokol uyumsuzluğunu ortaya çıkarır. Bu iki test birbirinin yerine geçmez; hata mesajlarının farklı katmanları göstermesi beklenir.
Kendi test dosyanızı gözden geçirirken şu kısa listeyi kullanın:
- Normal çalışan senaryoyu yazdım mı?
- Boş veya eksik veriyi denedim mi?
- Minimum, maksimum ve geçersiz değerleri değerlendirdim mi?
- Bağımlılığın hata vermesi durumunu belirledim mi?
- Test adı koşulu ve beklenen sonucu açıkça söylüyor mu?
- Bu testin unit, mock’lu servis veya integration test olma nedeni belli mi?
Unit, Mock ve Integration Testleri Birlikte Nasıl Konumlandırılır?
Unit test, mock kullanılan servis testi ve gerçek integration test aynı soruya cevap vermez. Bu nedenle amaç, tek bir test türünü her yerde kullanmak değil; yazdığınız kodun hangi davranışını doğrulamak istediğinizi belirlemektir. Unit test iş kuralını, mock’lu test bağımlılıklarla kurulan iletişimi, gerçek integration test ise birden fazla teknik parçanın birlikte çalışmasını kontrol eder.
Örneğin bir sipariş uygulamasında toplam tutarı hesaplayan saf bir fonksiyon, veritabanına veya ağa ihtiyaç duymuyorsa unit test ile sınanır. Bu test; indirim, vergi, kargo ve ürün miktarı gibi kuralların doğru uygulanıp uygulanmadığını hızlı biçimde gösterir. Buradaki hata genellikle hesaplama mantığında, koşul ifadelerinde veya sınır durumlarının ele alınışında aranır.
Aynı uygulamada OrderService sınıfı bir ödeme servisine bildirim gönderiyorsa, ödeme servisini mock’layarak servis kodunun doğru parametrelerle çağrı yapıp yapmadığını kontrol edebilirsiniz. Bu test, uygulama kodunun bağımlılığıyla nasıl iletişim kurduğunu kanıtlar; ancak gerçek ödeme servisinin erişilebilir olduğunu, ağ bağlantısının çalıştığını veya dış sistemin beklenen yanıtı verdiğini kanıtlamaz. Mock izolasyon sağlar; gerçek entegrasyonu doğrulamaz.
Gerçek integration test ise siparişin repository üzerinden kaydedilmesi, veritabanına doğru alanlarla yazılması, kayıtların yeniden okunabilmesi veya bir HTTP istemcisinin gerçek bir test servisiyle haberleşmesi gibi sınırları birlikte değerlendirir. Serileştirme, bağlantı ayarları, SQL sorguları, veri tipleri ve hata dönüşleri bu katmanda görünür hâle gelir. Bu nedenle integration testin başarısız olması, tek başına iş kuralının yanlış olduğunu göstermez; sorun repository, veritabanı şeması, istemci yapılandırması veya veri dönüşümünde olabilir.
Uçtan uca test daha geniş bir senaryoyu izler: Kullanıcı sipariş isteğini gönderir, istek uygulama katmanlarından geçer, sipariş kaydedilir, ödeme akışı başlatılır ve sonuç kullanıcıya döner. Integration test belirli teknik sınırların birlikte çalışmasına odaklanırken uçtan uca test, kullanıcı açısından baştan sona anlamlı bir iş akışını kontrol eder. Her integration test uçtan uca test değildir.
- “Hesaplama doğru mu?” sorusu için unit test yazılır.
- “Servis bağımlılığı doğru çağırıyor mu?” sorusu için mock’lu test kullanılabilir.
- “Uygulama veritabanı veya HTTP sınırıyla gerçekten uyumlu mu?” sorusu için integration test gerekir.
- “Kullanıcının sipariş verme süreci baştan sona çalışıyor mu?” sorusu için uçtan uca senaryo düşünülür.
Bu katmanları birlikte öğrenirken örnekleri durdurarak incelemek, testi yalnızca komut olarak çalıştırmak yerine hangi soruyu cevapladığını anlamayı kolaylaştırır. Kendi hızında ilerlemek isteyenler, Python ve Java proje pratiğini destekleyen video tabanlı yazılım eğitimleri içinde ders kayıtlarını tekrar izleyerek test mantığını adım adım çalışabilir.
Bu yaklaşım, test piramidini katı bir oran veya değişmez reçete olarak değil, karar vermeyi kolaylaştıran bir düşünme modeli olarak kullanır. Bir test kırıldığında önce testin hangi soruyu sorduğuna bakın: İş kuralı mı bozuldu, bağımlılık çağrısı mı değişti, yoksa gerçek teknik sınır mı uyumsuz hâle geldi? Doğru katmanı bulmak, hatayı daha hızlı teşhis etmenizi sağlar.
Sık Sorulan Sorular
Mock kullanılan bir test integration test sayılır mı?
Genellikle hayır. Mock kullanılan test, gerçek bağımlılığın yerine kontrollü bir sahte nesne koyarak uygulama kodunu izole eder. Bu test servis çağrısının yapıldığını veya doğru parametrelerin gönderildiğini gösterebilir; ancak gerçek veritabanı, ağ bağlantısı ya da dış servis entegrasyonunu doğrulamaz.
Bir fonksiyon için hem unit test hem integration test yazmak gerekir mi?
Her fonksiyon için ikisini birden yazmak gerekmez. Saf bir hesaplama fonksiyonu için unit test yeterli olabilir. Fonksiyon bir repository, veritabanı veya HTTP istemcisiyle birlikte çalışıyorsa, iş kuralını unit test ile; gerçek teknik bağlantıyı ise uygun bir integration test ile ayrıca doğrulamak anlamlıdır.
Unit test ile integration test arasındaki en önemli fark nedir?
En önemli fark test edilen kapsamdır. Unit test, küçük bir kod parçasını bağımlılıklarından izole ederek davranışını sınar. Integration test ise iki veya daha fazla gerçek bileşenin birlikte doğru çalışıp çalışmadığını kontrol eder.
Test adında metod_durum_beklenensonuç kalıbı nasıl kullanılır?
Bu kalıp testin neyi doğruladığını kısa biçimde anlatır. Örneğin toplam_indirimli_sipariste_indirimi_uygular adı; test edilen metodu, koşulu ve beklenen sonucu belirtir. Java tarafında calculateTotal_discountedOrder_returnsReducedTotal gibi bir adlandırma da aynı bilgiyi taşır.
Bir integration test çok kırılıyorsa test stratejisi nasıl gözden geçirilir?
Önce kırılmanın uygulama hatasından mı, test ortamından mı kaynaklandığını ayırın. Bağlantı ayarlarını, test verisini, veritabanı şemasını ve dış servis bağımlılıklarını kontrol edin. Test çok geniş bir akışı gereksiz yere kapsıyorsa senaryoyu daha küçük integration testlere ayırın; yalnızca iş kuralını sınayan bölümleri ise unit test seviyesine taşıyın.
İyi bir test stratejisi, test türlerini yarıştırmak yerine her birini doğru soruya yönlendirir: unit test mantığı, mock’lu test iletişimi, integration test gerçek teknik uyumu, uçtan uca test ise kullanıcı akışını görünür kılar.