pytest ile Birim Testi: Fixture, Parametrize ve Mock
Beceri rehberi

pytest ile Birim Testi: Fixture, Parametrize ve Mock

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 6 dk okuma

pytest, Python kodunu sade test fonksiyonları ve düz assert ifadeleriyle sınamanızı sağlar. Rehber, ilk testten fixture, parametrize, hata testi ve dış bağımlılığı değiştirmeye kadar gerçek kod örnekleriyle ilerler.

Bir bakışta

  1. Test, adı test ile başlayan sade bir fonksiyondur. pytest, test_ ile başlayan dosyalardaki test ile başlayan fonksiyonları kendisi bulur ve çalıştırır.
  2. Düz assert yeterlidir, özel metot gerekmez. Başarısız olduğunda pytest iki tarafın değerini göstererek hatayı açıklar.
  3. Fixture hazırlığı ve temizliği testten ayırır. Test, ihtiyaç duyduğu fixture'ı parametre olarak ister; pytest hazırlar ve işi bitince temizler.
  4. Parametrize aynı testi çok girdiyle çalıştırır. Her girdi ayrı bir test olarak raporlanır, hangisinin başarısız olduğu görünür.
  5. Dış bağımlılık test sırasında geçici olarak değiştirilir. monkeypatch ya da sahte nesneyle ağ, saat ve ortam değişkenleri kontrol altına alınır.

İlk test: dosya, fonksiyon ve assert

pytest'te bir test, adı test ile başlayan bir fonksiyondur. pytest, test_ ile başlayan ya da _test ile biten dosyalardaki bu fonksiyonları kendisi bulur; sınıf yazmanız ya da bir test çerçevesinden miras almanız gerekmez. Bu, testleri küçük ve okunur tutar.

Örnek bir fonksiyonu sınayalım. Bir sepet tutarına indirim oranı uygulayan kod olsun:

def indirim_uygula(tutar, oran): return tutar - tutar * oran

Test dosyası, aynı klasörde ya da tests klasöründe, adı test ile başlayan bir dosyada durur:

from fiyat import indirim_uygula

def test_indirim_tutardan_dusulur(): assert indirim_uygula(tutar_yuz, oran_yarim) == tutar_elli

Testi pytest komutuyla çalıştırırsınız. Tek bir test ya da bir isim parçasıyla eşleşen testler de seçilebilir: dosya_adi.py::test_adi biçimi tek bir testi, -k seçeneği ise isim ifadesine uyan testleri çalıştırır.

Assert başarısız olduğunda pytest, ifadenin iki tarafındaki değerleri gösterir. Bu yüzden assert_equal gibi özel metotlara ihtiyaç yoktur. Ondalık sayıları karşılaştırırken tam eşitlik yanıltıcı olabileceğinden pytest.approx kullanılır; küçük yuvarlama farkını tolere eder.

Şekil 1

Hangi pytest aracı hangi ihtiyaca cevap verir?

Test yazarken seçim rehberi

Testte ne yapmak istiyorsunuz?
  1. Her testte aynı nesneyi hazırlamakFixtureOrtak olanlar conftest.py dosyasına taşınır.
  2. Aynı davranışı çok girdiyle sınamakparametrizeHer girdi ayrı test olarak raporlanır.
  3. Hatanın fırlatıldığını doğrulamakpytest.raisesmatch ile hata mesajı da kontrol edilir.
  4. Ağ, saat ya da ortam bağımlılığını kesmekmonkeypatch veya sahte nesneDeğişiklik test bitince geri alınır.
Kaynak: pytest dokümantasyonuMindset Institute

Fixture ile hazırlık ve temizlik

Birçok test aynı kurulumu paylaşır: bir nesne oluşturmak, geçici bir dosya hazırlamak, veritabanı bağlantısı açmak. Bunu her teste kopyalamak yerine fixture kullanılır. Fixture, pytest.fixture ile işaretlenmiş bir fonksiyondur. Test, fixture'ı adıyla parametre olarak ister ve pytest hazırlanmış değeri verir.

import pytest

@pytest.fixture def sepet(): return Sepet()

def test_yeni_sepet_bostur(sepet): assert sepet.bos_mu()

Fixture başka fixture'ları da isteyebilir; böylece bağımlılıklar zincir halinde kurulur. Fixture'ın yaşam süresi kapsam ile belirlenir: varsayılan kapsam fonksiyondur, yani her test için yeniden oluşur. Kapsam sınıf, modül, paket ya da oturum olarak genişletilirse pahalı kurulumlar bir kez yapılır; bunun bedeli testler arası paylaşılan durumdur ve dikkat gerektirir.

Temizlik gerektiğinde fixture yield kullanır. Yield'a kadar olan kısım hazırlık, sonrası temizliktir ve test bitince çalışır. Böylece açılan bağlantı ya da geçici kayıt test başarısız olsa bile temizlenir.

@pytest.fixture def baglanti(): b = baglanti_ac() yield b b.kapat()

Birden çok dosyada ihtiyaç duyulan fixture'lar conftest.py dosyasına konur; aynı klasördeki ve alt klasörlerdeki testler bunları import etmeden kullanır. Fixture'ları testin içinde değil kenarında tutmak, test gövdesini yalnızca davranışa odaklı bırakır.

Parametrize ile çok girdili test

Aynı davranışı farklı girdilerle sınamak istediğinizde döngü yazmak ya da testleri kopyalamak yerine parametrize kullanılır. Dekoratör, parametre adlarını ve değer kümelerini alır; pytest her kümeyi ayrı bir test olarak çalıştırır.

@pytest.mark.parametrize("metin, beklenen", [("Ankara", "ankara"), ("İSTANBUL", "istanbul"), ("", "")]) def test_kucuk_harfe_cevir(metin, beklenen): assert kucuk_harfe(metin) == beklenen

Faydası raporlamadadır. Üç girdiden biri başarısız olursa yalnızca o giriş kırmızı görünür, parametre değerleri test kimliğinde yer alır ve diğerleri çalışmaya devam eder. Döngü kullansaydınız ilk hatada test dururdu ve kalan girdiler sınanmazdı.

Uç durumlar parametre listesinin en değerli yeridir: boş girdi, sınır değeri, beklenmedik tür, Türkçe karakter gibi. Her satıra tek bir davranış yazılır. Beklenen sonucu karmaşık bir hesapla üretmek yerine elle yazmak daha güvenlidir; yoksa test, sınadığı kodun mantığını tekrar eder.

Belirli bir girdinin bilinen bir hata olduğunu göstermek için pytest.param ile işaretleme yapılır. Birden çok parametrize dekoratörü üst üste konulursa tüm kombinasyonlar çalıştırılır; bu güçlüdür ama test sayısı hızla artar, dikkatli kullanılmalıdır.

Hata testi: pytest.raises

Kodun yanlış girdide doğru hatayı fırlatması da bir davranıştır. Bunu doğrulamak için pytest.raises bağlam yöneticisi kullanılır; blok içinde belirtilen hata oluşmazsa test başarısız olur.

def test_negatif_oran_reddedilir(): with pytest.raises(ValueError, match="negatif"): indirim_uygula(tutar_yuz, oran_negatif)

match parametresi, hata mesajında düzenli ifadeyle arama yapar. Böylece yalnızca hata türü değil mesajın içeriği de sınanır. Hata nesnesine ulaşmak gerekirse as ile bir değişkene alınır ve türü, değeri ya da izleme bilgisi incelenir.

Uyarı olarak, raises alt sınıfları da kabul eder. Tam tür gerekiyorsa hata nesnesinin türü ayrıca kontrol edilir. Başka bir sık hata, bloğun içine birden çok satır koymaktır: hangi satırın hatayı fırlattığı belirsiz olur. Blokta yalnızca hatayı üretmesi beklenen çağrı yer almalıdır.

Şekil 2

Test okunur olmalı: tek davranış, ayrı test, açık beklenen değer

Sık yapılan
Doğrusu
Tek testte birçok davranış ve döngü bulunur.
Her davranış ayrı test ya da parametre satırıdır.
Beklenen değer, kodun kendi mantığıyla hesaplanır.
Beklenen değer elle ve açıkça yazılır.
raises bloğunda birden çok satır vardır.
Blokta yalnızca hatayı üretmesi beklenen çağrı yer alır.
Kaynak: sayfa içeriğiMindset Institute

Dış bağımlılığı kesmek: monkeypatch ve sahte nesne

Birim testi hızlı ve kararlı olmalıdır. Ağ çağrısı, sistem saati, dosya sistemi ya da çevre değişkeni gibi bağımlılıklar testi yavaşlatır ve rastgele hatalara açık hale getirir. Bu bağımlılıkları test sırasında kontrollü şekilde değiştirmek gerekir.

pytest'in monkeypatch fixture'ı bunun için vardır. setattr ile bir nesnenin özelliğini ya da fonksiyonu, setenv ile çevre değişkenini değiştirir; test bitince her şey geri alınır.

def test_test_modunda_odeme_atlanir(monkeypatch): monkeypatch.setenv("ODEME_MODU", "test") monkeypatch.setattr(odeme, "kur_getir", lambda: kur_sahte) assert odeme_yap(tutar_yuz).onaylandi

Değiştirme, kodun gerçekten kullandığı adı hedeflemelidir. Eğer fonksiyon başka bir modülden içeri alınmışsa, kaynak modülü değil çağıran modüldeki ismi değiştirmek gerekir.

Çağrıların kaydedilmesi ve doğrulanması gerekiyorsa Python'un standart kütüphanesindeki unittest.mock sahte nesneleri kullanılır. Sahte nesne ne için çağrıldığını ve hangi argümanlarla çağrıldığını tutar; test bunu sınar. Dikkat edilecek nokta aşırı sahtelemedir: her şey sahte olduğunda test yalnızca sahte nesneleri sınar, gerçek davranışı değil. Kural basittir: sınır olan bağımlılıkları değiştirin, sınadığınız kodun kendi mantığını değil.

Test organizasyonu ve ilerleme yolu

Küçük projede testler tests klasöründe, kaynak kodla aynı yapıyı yansıtarak tutulur. Ortak fixture'lar conftest.py dosyasında durur. Proje büyüdükçe birim testleri hızlı koşan bir grupta, entegrasyon testleri ise veritabanı ya da servis gerektiren ayrı bir grupta toplanır. Böylece geliştirici yerelde hızlı testi sık, yavaş testi seyrek çalıştırır.

Öğrenme yolu kabaca şöyle ilerler. Önce düz fonksiyonlara test yazılır. Ardından fixture ile ortak kurulum ayrılır, parametrize ile uç durumlar çoğaltılır. Sonra hata testleri ve monkeypatch gelir. İleri aşamada kapsam ölçümü, sürekli entegrasyon hattında otomatik koşum ve hata kapıları kurulur.

Veri mühendisliğinde de aynı disiplin geçerlidir: dönüşüm fonksiyonları küçük girdilerle sınanır, beklenen çıktı elle yazılır, hat bağlantıları ise entegrasyon testlerine bırakılır. İyi bir birim testi bir davranışı sınar, hızlı çalışır ve hata verdiğinde nedeni söyler.

Sık yapılan hatalar özetle şunlardır: testlerin birbirine bağımlı olması, fixture kapsamını gereksiz geniş tutmak, yalnızca mutlu yolu sınamak, adı davranışı anlatmayan testler yazmak ve her şeyi sahte nesneyle değiştirmek.

Bu beceriyi Mindset programlarında nerede çalışırsınız?

Veri Mühendisliği için Python Test Otomasyonu programı; veri kodunda test stratejisi, dönüşüm mantığı birim testleri, entegrasyon ve sözleşme testleri ile sürekli entegrasyon ve kalite kapıları başlıklarını işler. TDD ve Unit Testing Uzmanlığı Eğitimi ise TDD temelleri, unit testing, mocking ve test izolasyonu, kapsam ve kod kalitesi analizi ile refactoring konularını dilden bağımsız bir çerçevede ele alır.

İlgili Mindset programı

Veri Mühendisliği için Python Test Otomasyonu

Sık sorulanlar

Sık sorulan sorular

pytest ile unittest arasındaki fark nedir?

unittest, standart kütüphanede gelir ve sınıf tabanlı testler ister. pytest ise düz fonksiyonlar, düz assert ve fixture sistemi ile daha az kod gerektirir. pytest, unittest testlerini de çalıştırabilir; bu yüzden mevcut bir test kümesini bozmadan pytest'e geçmek mümkündür.

Fixture ile setup metodu arasındaki fark nedir?

Setup metodu sınıfa bağlıdır ve tüm testleri etkiler. Fixture ise yalnız ihtiyaç duyan teste verilir, adı belli olduğu için testi okuyan neyin hazırlandığını görür. Fixture'lar birbirine bağlanabilir, kapsamı ayarlanabilir ve conftest.py ile paylaşılabilir.

Bir testi ya da bir grup testi nasıl çalıştırırım?

Tek bir testi dosya_adi.py::test_adi biçiminde, sınıf ve parametre dahil seçimi de aynı :: sözdizimiyle belirtirsiniz. İsim ifadesine uyan testler için -k seçeneği kullanılır; ifade büyük küçük harfe duyarsızdır ve and ile not gibi bağlaçları kabul eder.

Dosya sistemini sınarken ne kullanmalıyım?

Gerçek diske dosya yazmak yerine geçici klasör sağlayan yerleşik fixture'lar tercih edilir; test bitince temizlik pytest tarafından yapılır. Kodunuz sabit bir yola yazıyorsa o yolu monkeypatch ile test klasörüne yönlendirmek de mümkündür. Böylece testler birbirini ve yerel ortamı etkilemez.

Her şeye sahte nesne yazmak doğru mu?

Hayır. Sahte nesne yalnızca yavaş, kararsız ya da pahalı sınır bağımlılıkları için gerekir: ağ, saat, harici servis. Kendi mantığınızı sahtelerseniz test gerçek davranışı değil sahteyi sınar. Mantık gerçek, sınır sahte olmalıdır; sınır ile mantığın birlikte çalışması ise entegrasyon testiyle doğrulanır.

pytest ile Python Birim Testi Nasıl Yazılır?