Kod yazmadan önce davranışı test senaryosu gibi düşünmek, bir fonksiyonu yazmaya başlamadan önce "bu fonksiyon hangi girdide ne döndürmeli, hangi durumda hata vermeli" sorusunu net cümlelerle cevaplamak anlamına gelir. Bu yaklaşım, kodu yazdıktan sonra test etmek yerine, davranışı önce tanımlayıp kodu o tanıma göre şekillendirmeyi önerir. TDD (test driven development) zihniyetinin özü tam olarak budur: önce ne olması gerektiğini söylemek, sonra onu gerçekleştirmek. Bu yazıda bu düşünme biçiminin ne zaman işe yaradığını, ne zaman gereksiz yük getirdiğini ve pytest ile nasıl uygulanacağını pratik bir dille ele alacağız.
Test Senaryosu Gibi Düşünmek Ne Anlama Gelir?
Bir fonksiyon yazmadan önce zihinde "bu fonksiyon ne yapmalı, hangi girdide ne döndürmeli" sorusunu netleştirmek, kodu yazarken elinizin neyi hedeflediğini bilmesini sağlar. Test senaryosu gibi düşünmek, aslında kod yazmadan önce davranışı somut örneklerle tarif etme alışkanlığıdır: "3 ve 5 verildiğinde 8 dönmeli", "boş liste verildiğinde hata fırlatmalı" gibi cümleler kurmak, henüz tek satır kod yazmadan fonksiyonun sınırlarını çizer.
Burada önemli bir noktayı baştan netleştirmek gerekir: bu bir dogma değil, bir düşünme disiplinidir. Her satır kod için resmi bir test dosyası açmanız, her projede katı bir TDD döngüsü uygulamanız gerektiği anlamına gelmez. Amaç, davranışı kafanızda netleştirmeden klavyeye sarılmamayı alışkanlık haline getirmektir. Bazı geliştiriciler bunu gerçekten test dosyaları yazarak yapar, bazıları sadece yorum satırında veya kağıt üzerinde senaryo listeleyerek yapar; önemli olan araç değil, zihniyettir.
Bu yazı, temel Python veya Java sözdizimini zaten bilen, döngü kurabilen, fonksiyon tanımlayabilen orta seviye bir öğrenciyi hedefliyor. Dolayısıyla burada def veya class anahtar kelimelerinin ne işe yaradığını değil, bu sözdizimini kullanırken davranışı nasıl tasarlayacağınızı konuşacağız. Sözdizimi bilmek ile bir fonksiyonun ne yapması gerektiğine karar vermek farklı beceridir; ikincisi genellikle ihmal edilir ve kodun büyük kısmının hatalı çıkmasının asıl nedeni de budur. Bir değişkenin nasıl tanımlanacağını bilen biri, o değişkenin hangi durumlarda hangi değeri alması gerektiğine dair net bir cümle kuramayabilir. Test senaryosu gibi düşünmek, tam olarak bu boşluğu kapatan bir alışkanlıktır ve zamanla otomatikleşir: deneyimli geliştiriciler bir fonksiyon ismi duyduklarında zihinlerinde otomatik olarak "peki ya sınır durumlar?" sorusunu sorar.
Test-Önce mi Kod-Önce mi? İki Yaklaşımın Mantığı
Kod-önce yaklaşım, özellikle hızlı prototipleme ve keşifsel kodlama sırasında oldukça doğal işler. Bir fikri denerken, veri setini ilk kez görürken veya bir kütüphanenin nasıl davrandığını anlamaya çalışırken, önce birkaç satır kod yazıp sonucu gözlemlemek çoğu zaman en hızlı yoldur. Küçük betiklerde, tek seferlik veri dönüştürme scriptlerinde veya bir API'nin davranışını keşfederken test yazmak için durup düşünmek, akışı gereksiz yere yavaşlatabilir. Burada öncelik, hızlı geri bildirim almak ve problemi anlamaktır; kod henüz "kalıcı" bir yapı değildir.
Test-önce yaklaşım ise farklı bir ihtiyaca cevap verir: davranışı kod yazmadan önce netleştirmek ve tasarımı bu netlik üzerine kurmak. Bir fonksiyonun ne yapması gerektiğini önce test olarak yazmak, geliştiriciyi "bu fonksiyon aslında ne işe yarıyor" sorusuyla erken yüzleştirir. Bu, özellikle birden fazla parçadan oluşan, uzun ömürlü olması beklenen veya başkalarıyla paylaşılacak kod tabanlarında değerlidir; çünkü tasarım hataları kod yazıldıktan sonra değil, daha en baştan görünür hale gelir.
Bu iki yaklaşımdan birini "doğru", diğerini "yanlış" olarak etiketlemek gerçekçi değildir. Kod-önce yaklaşım keşif ve hız gerektiren bağlamlarda, test-önce yaklaşım ise netlik ve uzun vadeli güvenilirlik gerektiren bağlamlarda kendini gösterir. Deneyimli bir geliştirici, karşılaştığı probleme göre bu iki modu bilinçli biçimde değiştirir; bir öğrenme sürecinde de aynı esneklik geçerlidir. Önemli olan, hangi modda çalıştığınızı fark etmek ve o moda uygun beklentiyle ilerlemektir. Bir sonraki bölümde, test-önce düşüncenin pratikte nasıl somut adımlara dönüştüğünü göreceğiz.
Beklenen Davranışı Cümleyle İfade Etme Alışkanlığı

Bir fonksiyonun davranışını koda dökmeden önce sade bir cümleyle ifade etmek, test senaryosu gibi düşünmenin en pratik uygulamasıdır. Bu alışkanlığı adım adım şu şekilde kurabilirsiniz:
- Fonksiyonun amacını tek, net bir cümleyle yazın: "Bu fonksiyon bir sayı listesi alır ve ortalamasını döndürür."
- Somut girdi ve çıktı örnekleri belirleyin: "[2, 4, 6] verildiğinde 4.0 dönmeli."
- Sınır durumları düşünün: boş liste verilirse ne olmalı, negatif sayı içeren liste verilirse ne olmalı, tek elemanlı liste nasıl davranmalı?
- Bu cümleleri doğrudan test fonksiyonu isimlerine çevirin:
test_bos_liste_hata_firlatir,test_negatif_sayilarla_ortalama_hesaplanirgibi. - Testi çalıştırıp önce kırmızı (başarısız) sonucu, sonra kodu yazdıktan sonra yeşile geçişi izleyin; bu geçiş, davranışın gerçekten beklendiği gibi çalıştığının somut kanıtıdır.
Bu beş adımlık alışkanlık, özellikle Python öğrenirken pratik bir değer taşır çünkü Python'ın kısa ve okunabilir sözdizimi, bir cümleyi neredeyse birebir koda çevirmeyi kolaylaştırır. Bir öğrenci "boş liste verilirse hata fırlatmalı" cümlesini kurduğunda, bunu pytest.raises ile test etmek birkaç dakika sürer; bu kısa döngü, davranış tasarlama ile kod yazma arasındaki mesafeyi kısaltır. birebir Python dersleri kapsamında bu tür alışkanlıklar, öğrencinin sözdizimini bildiği noktadan sonra asıl zorlandığı "ne yazmalıyım" sorusuna doğrudan çözüm sunduğu için özellikle vurgulanır. Cümleyle ifade etme alışkanlığı zamanla otomatikleşir ve geliştirici, kod yazmadan önce zihninde kısa bir test listesi oluşturmayı doğal bir refleks haline getirir.
Pytest ile Önce Test, Sonra Kod: Uygulamalı Örnek
Teoriyi bir kenara bırakıp doğrudan uygulamaya geçelim. Diyelim ki bir sayı listesinde ikinci en büyük değeri bulan bir fonksiyon yazacaksınız. Klasik yaklaşımda önce fonksiyonu yazar, sonra elle birkaç liste deneyerek "çalışıyor gibi görünüyor" derdiniz. Test-önce yaklaşımında sıra tersine döner: önce fonksiyonun ne döndürmesi gerektiğini pytest ile ifade edersiniz, fonksiyon henüz yoktur bile.
Aşağıdaki örnekte önce testi yazıyoruz, ardından testi geçecek şekilde fonksiyonu tamamlıyoruz. Dikkat edilmesi gereken nokta, test fonksiyonunun beklenen davranışı önceden netleştirmiş olması: tekrarlı sayılar, negatif sayılar ve tek elemanlı liste gibi uç durumlar test yazılırken akla gelir, kod yazılırken değil.
def ikinci_en_buyuk(sayilar):
benzersiz = sorted(set(sayilar), reverse=True)
if len(benzersiz) < 2:
raise ValueError("En az iki farklı sayı gerekli")
return benzersiz[1]
def test_ikinci_en_buyuk_normal_liste():
assert ikinci_en_buyuk([4, 1, 7, 3, 7]) == 4
def test_ikinci_en_buyuk_negatif_sayilar():
assert ikinci_en_buyuk([-5, -1, -3]) == -3
def test_ikinci_en_buyuk_yetersiz_eleman():
import pytest
with pytest.raises(ValueError):
ikinci_en_buyuk([9])
Burada test_ikinci_en_buyuk_yetersiz_eleman fonksiyonu, "peki listede tek eleman varsa ne olacak?" sorusunu kodu yazmadan önce sormaya zorlar. Bu tür uç durum sorularını sonradan hatırlamak yerine baştan test satırına dökmek, fonksiyonun sınırlarını daha net çizmenizi sağlar. Kendi seviyenizi ölçmek isterseniz Python bilgi testi üzerinden bu tür senaryo bazlı sorularla pratik yapabilirsiniz.
Java tarafında mantık aynıdır; JUnit'te de @Test ile işaretlenmiş bir metotta önce beklenen davranış assertEquals veya assertThrows gibi ifadelerle yazılır, sonra metot buna göre tamamlanır. Dil değişse de zihniyet değişmez: önce "ne olmalı" sorusuna cevap, sonra "nasıl olmalı" sorusuna kod.
Hata Ayıklama Süresine Somut Etkisi

Test-önce alışkanlığının en somut faydası, hatayı üretim ortamında ya da kullanıcı şikayetiyle değil, yazım anında yakalamaktır. Yukarıdaki ikinci_en_buyuk örneğinde, eğer fonksiyonu sorted(sayilar, reverse=True)[1] şeklinde yazsaydınız — yani tekrarlı sayıları elemeden ikinci sırayı alsaydınız — test hemen başarısız olurdu. Testsiz bir senaryoda bu hata muhtemelen haftalar sonra, üretimde "neden yanlış sonuç dönüyor" şikayetiyle fark edilirdi.
Bu fark, hata ayıklama sürecinin doğasını değiştirir. Test olmadan hata ayıklamak genellikle geriye dönük bir soruşturmadır: hata nerede oluştu, hangi girdi tetikledi, kod ne zaman bozuldu? Test önce yazıldığında ise hata anlık ve yerelleşmiş olur — hangi satırı değiştirdiğinizde hangi testin kırıldığını doğrudan görürsünüz. Sınır değer hataları (off-by-one, boş liste, sıfıra bölme) özellikle bu şekilde çok daha erken yakalanır çünkü test yazarken zaten "peki liste boşsa?" diye düşünmüş olursunuz.
Bunu sihirli bir çözüm gibi sunmamak gerekir. Test yazmak, kodun mantıksal olarak doğru tasarlanmasının yerine geçmez; kötü tasarlanmış bir fonksiyon için yazılan testler de kötü olabilir. Fayda, disiplinden değil, alışkanlığın getirdiği erken geri bildirimden gelir. Bire bir ilerleyen bir öğrenme ortamında bu geri bildirim döngüsünü gözlemlemek isteyenler için 1-1 özel ders sürecinde bu tür örnekler üzerinden pratik yapılabilir.
Zaman kazancı biriktikçe belli olur. Tek bir fonksiyon için test yazmak birkaç dakika ekler; ama o birkaç dakika, ileride aynı hatayı üç farklı yerde arayarak geçirilecek saatlerin önüne geçebilir. Bu, her projede garanti bir kazanç değildir — küçük betiklerde fark neredeyse hissedilmezken, büyüyen ve tekrar kullanılan kod tabanlarında fark katlanarak artar.
Her Fonksiyona Test Yazmak Gerekir mi? Gerçekçi Sınırlar
Burada netleşmesi gereken bir nokta var: her satıra, her yardımcı fonksiyona test yazmak gerekmez ve bunu beklemek gerçekçi değildir. TDD zihniyetini benimsemek, her kod parçasını test zırhına sarmak anlamına gelmez; amaç, nerede test yazmanın gerçekten değer kattığını ayırt edebilmektir.
Genel ölçüt şudur: bir fonksiyon ne kadar kritikse, ne kadar sık tekrar kullanılıyorsa ve girdi çeşitliliği ne kadar fazlaysa, test yazmak o kadar mantıklıdır. Buna karşılık tek seferlik, atıp geçilecek bir betik veya basit bir ekrana yazdırma işlemi için test yazmak, harcanan zamana değmeyebilir.
| Durum | Test yazmak mantıklı mı | Neden |
|---|---|---|
| Kritik iş mantığı (ödeme, indirim, puanlama hesaplama) | Evet | Hata doğrudan kullanıcıyı veya veriyi etkiler, sınır değerler kritiktir |
| Tek seferlik betik (bir kereye mahsus veri temizleme) | Hayır | Kod tekrar çalıştırılmayacak, bakım maliyeti yok |
| Tekrar kullanılan yardımcı fonksiyon (birden çok modülde çağrılan) | Evet | Bir bozulma tüm çağıran yerleri etkiler, erken uyarı değerlidir |
| Basit yazdırma veya loglama ifadesi | Hayır | Mantık içermez, hata riski neredeyse yoktur |
Bu tabloyu bir kural kitabı olarak değil, bir öncelik sıralaması olarak görmek daha doğru olur. Yeni başlayan bir geliştirici için önemli olan, "her şeye test yazmalıyım" baskısı hissetmek değil, "bu fonksiyon bozulursa kim etkilenir" sorusunu sorabilmektir. Zamanla bu değerlendirmeyi otomatik yapmaya başladığınızda, test yazma kararı da doğal bir refleks haline gelir.
Bu Zihniyet Gerçek Projelerde Neden İşe Yarar?
Bir öğrenme ortamında yazdığınız küçük fonksiyonlarla gerçek bir üretim projesindeki kod arasında ölçek farkı vardır, ama mantık aynı kalır. Örneğin Django veya Flask ile bir API endpoint'i geliştirirken, kodu yazmadan önce "bu endpoint'e geçersiz bir kullanıcı ID'si gönderilirse hangi HTTP durum kodu ve hangi mesaj dönmeli?" sorusunu netleştirmek, geliştirme sürecinin en kritik anıdır. Bu soruyu baştan cevaplamadan yazılan kod, genellikle "çalışıyor gibi görünen" ama kenar durumlarda tutarsız davranan bir yapıya dönüşür. Aynı mantık Spring ile geliştirilen bir Java servisinde de geçerlidir: bir controller metodunun döndüreceği JSON yapısını, hata durumunda fırlatılacak exception tipini önceden tanımlamak, kodu yazan kişiye net bir hedef verir.
Burada altı çizilmesi gereken nokta şu: bu bir çerçeve veya dil özelliği değil, genel bir yazılım geliştirme pratiğidir. Django, Flask, Spring, FastAPI ya da başka herhangi bir araçla çalışıyor olun, davranışı önce tanımlama alışkanlığı aracın kendisinden bağımsız çalışır. Ekip halinde çalışılan projelerde bu netlik ayrıca iletişim maliyetini düşürür; bir geliştirici "bu fonksiyon ne zaman None döner?" diye sormak yerine, önceden yazılmış test senaryosuna bakarak cevabı bulabilir. Testler, kod hakkında güncel kalan bir tür canlı dokümantasyona dönüşür — yorum satırları eskiyip yanlış bilgi verebilirken, çalışan bir test her zaman gerçek davranışı yansıtır.
Bu becerinin temelleri, Python ve Java'nın sözdizimini öğrenmekten çok, problem çözme alışkanlığıyla ilgilidir. Bir dili yeni öğrenen ve bu tür pratik yaklaşımları birebir uygulamalı görmek isteyenler için birebir Java özel dersleri kapsamında, gerçek kod örnekleri üzerinden davranış tanımlama ve test yazma alışkanlığı adım adım işlenebilir. Önemli olan, bu alışkanlığı hangi dilde öğrendiğiniz değil, onu düşünme biçiminize ne kadar erken yerleştirdiğinizdir.
Bir Algoritmik Düşünme Alışkanlığı Olarak Test Kurma
Beklenen davranışı kod yazmadan önce tanımlamak, aslında algoritmik düşünmenin görünmeyen bir parçasıdır. Bir problemi çözmeye başlamadan önce girdiyi, çıktıyı ve aradaki dönüşüm kurallarını netleştirmek zorundasınızdır — bu, bir test senaryosu yazmakla neredeyse birebir aynı zihinsel adımdır. "Bu fonksiyona ne veriyorum, ne bekliyorum, hangi durumlarda farklı davranmalı?" sorusu, hem bir algoritma tasarlarken hem de bir test yazarken sorulan sorudur. Bu yüzden test-önce düşünmeyi bir araç veya kütüphane becerisi olarak değil, problem çözme disiplininin bir uzantısı olarak görmek daha doğru bir çerçevedir.
Bu tür bir düşünme alışkanlığı zamanla bilinçli bir çabadan çıkıp otomatik bir refleks haline gelir. Başlangıçta her fonksiyon için "önce ne bekliyorum?" diye durup düşünmeniz gerekirken, ilerleyen zamanda bu soru kod yazma sürecinin doğal bir parçası olur; hatta bazen hiç yazılı bir test olmadan da zihinsel olarak sınır durumları taramaya başlarsınız. Bu noktaya gelmeden önce, algoritmik düşünme becerinizin nerede olduğunu görmek isterseniz ücretsiz algoritma bilgi testi ile mevcut seviyenizi ölçebilir, hangi konularda daha çok pratik yapmanız gerektiğine dair somut bir fikir edinebilirsiniz.
Sonuç olarak, kod yazmadan önce davranışı bir test senaryosu gibi düşünmek; ne tek başına bir araç bilgisi, ne de zorunlu bir metodolojidir. Zamanla kazanılan, sizi daha az hata yapan ve daha net düşünen bir geliştiriciye dönüştüren bir alışkanlıktır. Bu alışkanlığı küçük adımlarla, gerçek kod üzerinde tekrar ede ede inşa etmek, uzun vadede en kalıcı sonucu verir.
Sık Sorulan Sorular
TDD her projede zorunlu bir yöntem midir?
Hayır. Test-önce yaklaşım özellikle karmaşık iş kuralları içeren veya uzun ömürlü kod tabanlarında değer katar, ancak her küçük betik veya deneme amaçlı kod için zorunlu değildir. Projenin ölçeğine ve ekibin çalışma tarzına göre esnek biçimde uygulanabilir.
Pytest ile test yazmaya nasıl başlanır?
En basit yol, test edilecek fonksiyonun beklenen girdi-çıktı davranışını assert ifadeleriyle yazan, adı test_ ile başlayan bir fonksiyon oluşturmaktır. Bu fonksiyon test_ önekiyle başlayan bir dosyada tutulur ve terminalden pytest komutuyla çalıştırılır.
Java'da JUnit ile benzer bir yaklaşım uygulanabilir mi?
Evet. JUnit, Java ekosisteminde pytest'e benzer bir mantıkla çalışır; beklenen davranışı önce bir test metodunda tanımlayıp ardından o davranışı karşılayan sınıfı veya metodu yazma alışkanlığı aynı şekilde uygulanabilir.
Her fonksiyona test yazmak gerekir mi?
Gerekmez. Basit, tek satırlık veya iş mantığı taşımayan yardımcı fonksiyonlar için test yazmak genellikle zaman kaybıdır. Test yazmanın değerli olduğu yerler, karar noktası içeren, sınır durumları olan ve hatalı çalıştığında fark edilmesi zor olan fonksiyonlardır.
Test-önce düşünmek hata ayıklama süresini nasıl kısaltır?
Beklenen davranış önceden net biçimde tanımlandığı için, kod beklenenden farklı çalıştığında hatanın nerede olduğu daha hızlı anlaşılır. Test çalıştığında hangi senaryonun bozulduğu açıkça görülür, bu da rastgele kod satırlarını tek tek kontrol etme ihtiyacını azaltır.
TDD ile unit test yazmak arasındaki fark nedir?
Unit test yazmak, kodu tamamladıktan sonra da yapılabilen bir doğrulama adımıdır. TDD ise testin koddan önce yazılmasını ve kodun bu teste uyacak şekilde geliştirilmesini öngören bir sıralama ve düşünme biçimidir; ikisi de aynı test araçlarını kullanabilir ama sıralama mantığı farklıdır.
Kod yazmadan önce davranışı netleştirme alışkanlığı, hem küçük ödevlerde hem de büyük projelerde zamanla kendini kanıtlayan bir düşünme biçimidir. Bu alışkanlığı Python veya Java üzerinden birebir uygulamalı şekilde geliştirmek isteyenler 1-1 özel ders seçeneğiyle kendi hızlarında ve kendi proje örnekleri üzerinden ilerleyebilir.