Birim Testlere Giriş: Python ile Örnek Test Senaryoları

Yazılım Test ve Hata Ayıklama

Birim Testlere Giriş: Python ile Örnek Test Senaryoları

Bu rehber, Python’da birim test yazmaya pratik bir giriş sunar: pytest ile assert, test keşfi, parametrizasyon ve fixture; unittest ile TestCase yaklaşımı; ayrıca TDD (Red-Green-Refactor) ve unittest.mock ile bağımlılıkları izole etme.
Birim Testlere Giriş: Python ile Örnek Test Senaryoları

Birim test nedir ve neden önemlidir?

Birim test (unit test), kodunuzun küçük ve izole parçalarının (genellikle tek bir fonksiyonun veya metotun) beklediğiniz gibi çalıştığını doğrulayan otomatik testlerdir. Amaç “her şeyi test etmek” değil; hataları erken yakalamak, refactor (kod iyileştirme) yaparken geriye dönük kırılmaları azaltmak ve tasarım kararlarınızı daha görünür hale getirmektir.

Bu yazıda, pytest ve Python’un standart kütüphanesindeki unittest ile temel test senaryolarını, anlaşılır örneklerle göreceksiniz. Kısa bir TDD akışı ve mock kullanımı da dahil.


pytest mi, unittest mi? Kısa karşılaştırma

İki yaklaşım da doğru olabilir; seçim çoğunlukla proje ekosisteminize, ekip alışkanlıklarına ve eğitim hedefinize bağlıdır.

Konu pytest unittest
Kurulum Üçüncü taraf paket (pip ile) Standart kütüphanede gelir
Test yazma stili Genellikle fonksiyon tabanlı, kısa Genellikle sınıf tabanlı (TestCase)
Assert kullanımı Python assert ifadesi + pytest “assert rewriting” ile daha açıklayıcı hata çıktıları self.assertEqual, self.assertTrue vb. assertion metotları
Fixture / setup Güçlü fixture sistemi setUp/tearDown, ayrıca yardımcı sınıflar
Test keşfi Konvansiyonlara göre otomatik keşif (test_*.py vb.) unittest loader ile veya komut satırı ile keşif

Kaynaklar: unittest’in standart kütüphanede yer aldığı ve TestCase üzerinden test yazıldığı Python belgelerinde anlatılır: docs.python.org unittest. pytest’in “assert rewriting” davranışı ve başlangıç akışı resmi dokümantasyonda yer alır: docs.pytest.org.


Başlangıç senaryosu: Saf (pure) bir fonksiyonu test etmek

Önce dış bağımlılığı olmayan, kolay test edilebilir bir fonksiyonla başlayalım.

Örnek fonksiyon

# app/math_utils.py

def add(a, b):
    return a + b

pytest ile test (en kısa yol)

pytest’te genellikle test_ ile başlayan dosya adı ve yine test_ ile başlayan fonksiyon isimleriyle ilerlersiniz.

# tests/test_math_utils.py

from app.math_utils import add

def test_add_two_numbers():
    assert add(2, 3) == 5

Komut satırı:

python -m pip install pytest
pytest

pytest’in başlangıç örnekleri ve test keşfi mantığı resmi belgelerde yer alır: pytest docs.

unittest ile aynı test

unittest’te testler çoğu zaman unittest.TestCase sınıfından türetilir ve assertion metotları kullanılır.

# tests/test_math_utils_unittest.py

import unittest
from app.math_utils import add

class TestMathUtils(unittest.TestCase):
    def test_add_two_numbers(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == "__main__":
    unittest.main()

unittest’in TestCase, test çalıştırma ve keşif özellikleri resmi Python dokümantasyonunda anlatılır: unittest — Unit testing framework.


pytest örnekleri: Parametrizasyon ve fixture ile daha gerçekçi testler

Tek bir “mutlu yol” (happy path) testi yeterli değildir. Aynı davranışı farklı girdilerle denemek ve test ortamını düzenli kurmak için pytest’in sunduğu bazı özellikler oldukça pratiktir.

1) Parametrizasyon: Aynı testi farklı veriyle çalıştırmak

# tests/test_add_parametrized.py

import pytest
from app.math_utils import add

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add_parametrized(a, b, expected):
    assert add(a, b) == expected

Parametrizasyon yaklaşımı pytest’in temel kullanım senaryoları arasında yer alır; ayrıntılar için: pytest docs.

2) Fixture: Test için ortak kurulum (setup) oluşturmak

Fixture, testlerinizin ihtiyaç duyduğu ortak nesneleri/kurulumu tekrar etmeden paylaşmanızı sağlar. Örneğin bir “ayarlar” sözlüğü üretelim.

# tests/conftest.py

import pytest

@pytest.fixture
def settings():
    return {"currency": "USD", "precision": 2}

# tests/test_settings.py

def test_settings_has_currency(settings):
    assert settings["currency"] == "USD"

Fixture sistemi için: pytest docs.


unittest örnekleri: setUp/tearDown

unittest, daha “klasik” bir xUnit stilini takip eder. Bu stil bazı ekiplerde daha tanıdıktır ve standart kütüphane bağımlılığı istemeyen senaryolarda avantajlı olabilir.

setUp ile ortak hazırlık

# tests/test_user_unittest.py

import unittest

class TestUserFormatting(unittest.TestCase):
    def setUp(self):
        self.first = "Ada"
        self.last = "Lovelace"

    def test_full_name(self):
        full = f"{self.first} {self.last}"
        self.assertEqual(full, "Ada Lovelace")

unittest’te yaşam döngüsü metotları ve assertion listesi resmi belgelerde detaylandırılır: Python unittest docs.


Mocking: Dış bağımlılıkları izole ederek birim test yazmak

Bir “birim”i test ederken ağ çağrısı, saat/zaman, dosya sistemi veya veritabanı gibi dış bağımlılıklar testinizi yavaşlatır ve kırılgan yapabilir. Bu noktada mock yaklaşımı devreye girer. Python’un standart kütüphanesindeki unittest.mock modülü, ister unittest ister pytest kullanın, sık tercih edilen bir araçtır.

Örnek: HTTP çağrısını mock’layarak davranışı test etmek

Not: Aşağıdaki örnek, üçüncü taraf requests paketini kullanır. Çalıştırmak isterseniz python -m pip install requests gerekir. Kendi projenizde farklı bir HTTP istemcisi kullanıyorsanız, aynı patch yaklaşımını o çağrı noktasına uygulayabilirsiniz.

# app/weather.py

import requests

def fetch_status(url):
    resp = requests.get(url, timeout=2)
    return resp.status_code

# tests/test_weather.py (pytest ile)

from unittest.mock import patch, Mock
from app.weather import fetch_status

@patch("app.weather.requests.get")
def test_fetch_status_uses_requests_get(mock_get):
    mock_get.return_value = Mock(status_code=200)
    assert fetch_status("https://example.com") == 200

İpucu: patch ederken, genellikle “fonksiyonun kullandığı yerdeki ismi” patch’lemek gerekir (yani import edildiği modül yolu). unittest.mock için resmi referans: unittest.mock — mock object library.


TDD (Test Driven Development) mini örneği: Red-Green-Refactor

TDD’de amaç; önce test yazarak beklenen davranışı netleştirmek, sonra minimum kodla testi yeşile çevirmek ve en son temizlemektir. Bu yaklaşım her ekip için şart değildir; ancak küçük modüller tasarlarken faydalı olabilir.

Adım 1: “Red” – Önce başarısız olan test

Örneğin bir metin temizleme fonksiyonu istiyorsunuz: baştaki/sondaki boşlukları silsin ve boş string gelirse hata versin.

# tests/test_clean_text.py

import pytest
from app.text_utils import clean_text

def test_clean_text_strips_whitespace():
    assert clean_text(" hello ") == "hello"

def test_clean_text_rejects_empty():
    with pytest.raises(ValueError):
        clean_text(" ")

Adım 2: “Green” – Minimum kodla testi geçir

# app/text_utils.py

def clean_text(s):
    out = s.strip()
    if not out:
        raise ValueError("empty text")
    return out

Adım 3: “Refactor” – Kod temizliği ve isimlendirme

Testler yeşil kaldığı sürece, örneğin hata mesajını standartlaştırabilir, ek kenar durumları (None, tip kontrolü vb.) için yeni testler ekleyebilirsiniz. Burada önemli olan: refactor’ı “göz kararı” değil, testlerin verdiği güvenle yapmaktır.

Python testlerine giriş için daha fazla pratik örnek görmek isterseniz: Real Python – Getting Started With Testing in Python.


Test dosya yapısı ve keşif: Küçük kurallar, büyük kolaylık

Önerilen minimal yapı

  • app/: uygulama kodu
  • tests/: testler
  • pytest kullanıyorsanız tests/conftest.py: fixture’lar

İsimlendirme pratikleri

  • pytest: genelde test_*.py dosyaları ve test_* fonksiyonları otomatik keşfedilir. Ayrıntılar: pytest docs.
  • unittest: komut satırı keşfi veya loader kullanımıyla tutarlı hale getirilir. Referans: unittest docs.

Hangi çerçeveyle başlamalı? Hedefe göre öneri

Yeni başlayanlar / eğitim içerikleri

  • pytest, daha az “boilerplate” ile hızlı geri bildirim verdiği için motivasyonu artırabilir. Ayrıca başarısız assert çıktılarının daha açıklayıcı olması öğrenmeyi kolaylaştırabilir (pytest’in “assert rewriting” özelliği). Kaynak: pytest docs.
  • unittest, standart kütüphanede olduğu için “ek bağımlılık olmadan” çalışır; temel kavramları (TestCase, runner, loader) öğretmek için iyi bir temel olabilir. Kaynak: unittest docs.

Gerçek projeler / ekipler

  • Bir akademik ön baskı çalışması (TestMigrationsInPy), bazı açık kaynak depolarda unittest’ten pytest’e yapılan test göçlerini içeren bir veri seti sunar. Bu tür bir göç senaryosunu incelemek isterseniz referans olabilir: TestMigrationsInPy (arXiv).
  • Hangi çerçevenin “daha doğru” olduğundan çok, testlerin hızlı, okunabilir ve sürdürülebilir olması belirleyicidir.

Günlük hayatta işe yarayan kontrol listesi

  • Testler hızlı olsun: Ağ/veritabanı çağrısı gibi işleri birim testten ayırın; gerekiyorsa mock kullanın.
  • Her test tek bir davranışı doğrulasın: Fail olduğunda “neyin bozulduğunu” kolay anlamak için.
  • İsimleri açıklayıcı seçin: test_clean_text_rejects_empty gibi.
  • Kenar durumları ekleyin: boş giriş, negatif sayılar, beklenmeyen tipler vb.
  • Fixture’ları abartmayın: Fixture sayısı arttıkça testleri takip etmek zorlaşabilir; gerçekten tekrar eden kurulumları toplayın.
  • CI’de çalıştırın: Yerelde geçen testin her ortamda geçtiğinden emin olmak için.

Sık yapılan hatalar (ve hızlı çözümler)

1) Testler birbirine bağımlı hale geliyor

Çözüm: Ortak state’i (global değişken, paylaşılan dosya vb.) azaltın; fixture’ları fonksiyon kapsamına çekin; her testin kendi hazırlığını yapmasını sağlayın.

2) Mock yanlış yere uygulanıyor

Çözüm: patch hedefini “kullanıldığı modül yolu” olarak belirleyin. Emin değilseniz, test ettiğiniz fonksiyonun import satırlarına bakın ve oradaki isim üzerinden patch’leyin.

3) Test isimleri keşfedilmiyor

Çözüm: pytest için dosya ve fonksiyon adlarını konvansiyona uygun yazın; unittest için keşif komutunu/doğru pattern’i kullanın. Detaylar için resmi dokümantasyon: pytest, unittest.


Sonuç: Küçük başlayın, düzenli ilerleyin

Python’da birim test yazmaya başlamak için dev bir altyapıya ihtiyacınız yok: bir fonksiyon, bir test dosyası ve düzenli bir çalışma rutini çoğu zaman yeterlidir. Hızlı bir başlangıç istiyorsanız pytest ile birkaç test yazın; standart kütüphane yaklaşımını anlamak istiyorsanız unittest ile aynı senaryoyu kurun. İki yöntemi de tanımak, farklı projelerde daha rahat hareket etmenizi sağlar.