Python’da alay etme: unittest.mock, patch ve Testleri Yalan Yapan Hatalar
Mocks, kodu gerçek bir ağ, veritabanı veya saat olmadan test etmenize olanak tanır. Dikkatsizce kullanıldığında bozuk kodun geçmesine de izin verirler. Unittest.mock’un her örnek çalıştırmada nasıl iyi kullanılacağı aşağıda açıklanmıştır.
- Bir sahte, API istemcisi gibi gerçek bir nesnenin yerine geçer, böylece testler hızlı ve çevrimdışı çalışır.
- Sabit bir cevap için
return_value‘i, hatalar veya sıralama içinside_effect‘yi kullanın. - Adın tanımlandığı yeri değil, kullanıldığı yeri yamalayın. Bu en yaygın hatadır.
- Yazım hatalarının ve yanlış argümanların sessizce iletilmesi yerine başarısız olması için
specveyaautospec‘yi kullanın.
İşleviniz bir hava durumu API’sini çağırır. Testiniz internete, API anahtarına veya yarının hava durumuna bağlı olmamalıdır. Python’un yerleşik unittest.mock özelliği, bu çağrıyı tam olarak seçtiğiniz şeyi döndüren bir stand-in ile değiştirmenize olanak tanır.
Küçük bir weather modül ve dokuz test yazdık; bunların tümü Python 3.13’ün en küçük testiyle çalıştırıldı. Dokuzunun tamamı geçti ve aşağıdaki basılı çıktılar bu çalışmadan geliyor.
Neden alay edesiniz ki?
- Hız: ağ çağrısı veya veritabanı kurulumu yok.
- Güvenilirlik: üçüncü taraf bir hizmetin çalışmaması nedeniyle testler başarısız olmaz.
- Kontrol: isteğe bağlı olarak zaman aşımlarını, hataları ve tek verileri simüle edebilirsiniz.
- İzolasyon: başka birinin sunucusunu değil, kendi mantığınızı test edersiniz.
Taklitler esas olarak birim testlerine aittir. Entegrasyon testleri yine de gerçek veya gerçekçi bağımlılıklarla konuşmalıdır.
Sahte temeller
from unittest.mock import Mock
api = Mock()
api.get_price.return_value = 499
print(api.get_price("pen")) # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list) # [call('pen')]
print(api.anything_at_all()) # <Mock name='mock.anything_at_all()' ...>
Bir Mock her aramayı kaydeder, böylece nasıl kullanıldığını kontrol edebilirsiniz. Ancak son satıra dikkat edin. Sade bir Mock, istediğiniz herhangi bir özelliği icat eder. Bu esneklik kullanışlıdır ve göreceğimiz gibi aynı zamanda tehlikelidir.
MagicMock aynı fikirdir ancak aynı zamanda __len__ ve __iter__ gibi “sihirli” yöntemleri de destekler. patch() varsayılan olarak MagicMock’u kullanır.
yan etki: hatalar ve diziler
side_effect her çağrıda ne olacağını kontrol eder. Bir arızayı simüle etmek için bunu bir istisnaya ayarlayın:
api.fetch.side_effect = ConnectionError("down")
api.fetch() # raises ConnectionError
Ardışık aramalarda farklı sonuçlar döndürmek için bunu bir listeye ayarlayın. Bu, yeniden deneme mantığını test etmek için mükemmeldir:
def fetch_with_retry(api, attempts=3):
for _ in range(attempts):
try:
return api.fetch()
except ConnectionError:
continue
raise ConnectionError("all attempts failed")
api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api)) # {'ok': True}
print(api.fetch.call_count) # 2
İlk çağrı başarısız oldu, ikincisi başarılı oldu ve test, işlevin tam olarak bir kez yeniden denendiğini kanıtladı. Bunu gerçek bir hizmete karşı simüle etmek neredeyse imkansız olacaktır.
patch(): bir şeyi geçici olarak değiştirin
Çoğunlukla bağımlılık aktarılmaz. Modülün içine aktarılır. patch() test süresince onu değiştirir ve daha sonra geri yükler:
# weather.py
from http_client import get as requests_get
def report(city):
data = requests_get(f"https://api.example.com/weather?city={city}")
return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch
with patch("weather.requests_get") as fake_get:
fake_get.return_value = {"temp_c": 31}
print(weather.report("Pune")) # Pune: 31°C
fake_get.assert_called_once_with(
"https://api.example.com/weather?city=Pune")
patch‘i bunun gibi bir içerik yöneticisi olarak veya sahteyi argüman olarak ileten bir dekoratör olarak kullanabilirsiniz.
Tanımlandığı yer değil, kullanıldığı yer yama
Bu neredeyse herkesin bir kez yaptığı hatadır. Gerçek get işlevi http_client‘de yaşıyor, bu yüzden onu oraya yamamak doğal geliyor:
with patch("http_client.get") as fake_get:
fake_get.return_value = {"temp_c": 99}
weather.report("Pune")
# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune
Gerçek fonksiyon çalıştı. weather.py, from http_client import get as requests_get‘yi yaptığında, orijinal fonksiyona işaret ederek kendi adını yarattı. http_client.get yaması farklı bir adı değiştirdi, bu yüzden weather sahtesini hiç görmedi. Bizim http_client.get kasıtlı olarak artırıldı, dolayısıyla hata çok yüksek oldu. Gerçek bir projede sessizce canlı bir API çağırabilir.
Kural: test edilen modüldeki adı "module_under_test.name" olarak yazın. Eğer modül import http_client‘ü yaptıysa ve http_client.get()‘ü çağırıyorsa, arama sırasında gerçekleştiği için "http_client.get"‘in yamalanması doğrudur.
spec ve autospec: taklitleri katı hale getirin
Sade bir Mock, yazım hataları dahil her şeyi kabul eder. Bir spesifikasyonla yalnızca gerçek sınıfın sahip olduğu niteliklere izin verilir:
api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'
loose = Mock()
loose.get_temprature("Pune") # returns a Mock, no error at all
Gevşek sahte, yanlış yazım yöntemini memnuniyetle kabul etti, böylece onu kullanan bir test, gerçek kod çökse bile başarılı olabilir. autospec daha da ileri giderek yöntem imzalarını kontrol eder:
with patch("weather.WeatherClient", autospec=True) as Fake:
client = Fake.return_value
client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments
Kendi sınıflarınızla alay ederken autospec=True veya create_autospec()‘yi tercih edin. Gerçek kod değiştikçe alayların dürüst olmasını sağlar.
Tarih ve saatlerle alay etmek
Bugünün tarihine bağlı olan kodun test edilmesi zordur. Saati küçük bir işleve sarın ve şunu ekleyin:
def today():
return datetime.date.today()
def is_weekend():
return today().weekday() >= 5
@patch("weather.today")
def test_date(self, fake_today):
fake_today.return_value = datetime.date(2026, 11, 7)
print(weather.is_weekend()) # True (a Saturday)
Yerleşik türler özniteliklerinin ayarlanmasına izin vermediğinden datetime.date.today‘e doğrudan yama uygulayamazsınız. Freegun ve time machine gibi kütüphaneler bunu daha kapsamlı bir şekilde ele alıyor.
Alaylarla ilgili iddialar
| Yöntem | Çekler |
|---|---|
assert_called() |
En az bir kez aradı |
assert_called_once() |
Tam olarak bir kez aradı |
assert_called_with(...) |
Son çağrıda bu argümanlar kullanıldı |
assert_called_once_with(...) |
Bu argümanlarla tam olarak bir çağrı |
assert_any_call(...) |
Bu bağımsız değişkenleri kullanan herhangi bir çağrı |
assert_not_called() |
Hiç aramadım |
Bir iddia başarısız olduğunda mesaj açıktır. Hiçbir zaman çalışmayan bir yöntem üzerinde assert_called_once()‘i çağırdık:
AssertionError: Expected 'save' to have been called once. Called 0 times.
Daha eski Python sürümleri, assret_called_once gibi yanlış yazılmış onaylama yöntemlerinin sessizce geçmesine izin verir. Modern versiyonlar, assert ile başlayan isimlerde veya bunun yaygın yazım hatalarında hataya neden olur, ancak spesifikasyon hala daha güçlü bir korumadır.
pytest ve alaylar
Yukarıdaki her şey pytest’te de çalışıyor. Pek çok pytest kullanıcısı, basit değiştirmeler için yerleşik monkeypatch fikstürünü veya yamaları otomatik olarak geri alan bir mocker fikstürü sağlayan pytest-mock eklentisini de kullanır. Hepsi için “kullanıldığı yerdeki yama” kuralı geçerlidir. Fikstürler ve kurulum için pytest eğitimimize bakın.
Ne zaman alay edilmemeli
- Test ettiğiniz şeyle alay etmeyin. Eğer taklit tüm işi yaparsa, test hiçbir şeyi kanıtlamaz.
- Basit değer nesneleriyle alay etmekten kaçının. Gerçeği kullanın.
- Her şeyle alay etmeyin. Alaylarla dolu testler, davranış doğru olsa bile kod yeniden düzenlendiğinde sıklıkla bozulur.
- Bazı gerçek entegrasyon testleri yapın. Mock’lar, bir hizmetle ilgili varsayımlarınızı kodlar ve bu varsayımlar yanlış olabilir.
Sahte yollar, arkalarında herhangi bir gerçek davranış olmadan çalıştığından, sahte yollar kod kapsamını da kolaylıkla genişletir.
Yaygın alay hataları
- Yanlış adın yamalanması. Yukarıda ele alındı ve hâlâ “taklitlerim göz ardı ediliyor”un bir numaralı nedeni.
- Dönüş_değerini ayarlamayı unutmak. Test edilen kod daha sonra veri yerine bir Mock nesnesi alır. Örneğin, yalnızca bir sonucun doğru olup olmadığını kontrol ettiğinizde, işlevinizden doğrudan geçebilir ve kazara bir test geçişi yapabilir.
- Sonuç yerine sahte iddiada bulunmak.
save()‘nin çağrıldığını kontrol etmek, neyin kaydedildiğini kontrol etmekten daha zayıftır.assert_called_once_with(...)‘ü gerçek argümanlarla kullanın veya daha iyisi fonksiyonun sonucunu iddia edin. - Yamaları sızdırıyor.
stop()olmadanpatcher.start()‘yi çağırmak, sahteyi daha sonraki testler için yerinde bırakır. İçerik yöneticisini, dekoratörü veyaaddCleanup(patcher.stop)‘ü kullanın. - Yığılmış dekoratörler yanlış sırada. Birkaç
@patchdekoratörde, en alttaki ilk argüman olarak iletilir. Bunları sessizce karıştırmak yanlış modeli yapılandırır. - Birçok testte sahip olmadığınız bir kitaplıkla alay etmek. Bunu kendinize ait küçük bir sınıfa sarın ve bunun yerine bununla dalga geçin. Testleriniz daha sonra kütüphanenin API’sini değil ihtiyaçlarınızı açıklar.
Alaylar, taslaklar ve sahteler
Bu kelimeler genellikle gevşek bir şekilde kullanılır, ancak farklılıklar testleri tartışırken yardımcı olur. Bir taslak yalnızca hazır yanıtları döndürür. Bir sahte ayrıca, nasıl kullanıldığını doğrulayabilmeniz için çağrıları da kaydeder. Sahte, veritabanı yerine bellek içi depo gibi çalışan, basitleştirilmiş bir uygulamadır. Python’un Mock saplama veya taklit görevi görebilir. Sahteler, kendi yazdığınız sıradan sınıflardır ve genellikle yeniden düzenlemede daha iyi hayatta kalan testlere yol açarlar çünkü kesin çağrılardan ziyade davranışı kontrol ederler.
İyi bir genel kural, sisteminizin kenarlarıyla alay etmektir: ağ, saat, rastgele sayılar, dosya sistemi ve ücretli üçüncü taraf hizmetler. Bu kenarların içinde gerçek nesneleri tercih edin. Bu şekilde oluşturulan testler hızlı kalır ve gerçekte yazdığınız kodu kullanmaya devam eder.
Son sözler
Alay etmek hassas bir araçtır: yavaş veya öngörülemeyen bağımlılıkları değiştirin, ne döndürdüklerini kontrol edin ve kodunuzun bunları nasıl kullandığını kontrol edin. İki alışkanlık çoğu sorunu önler. Test edilen modüldeki adı yamalayın ve otomatik belirtimi kullanın, böylece taklitleriniz gerçek nesnenin reddedeceği çağrıları reddeder.
Sık sorulan sorular
Python’da alay etmek nedir?
Mocking, API istemcisi veya veritabanı gibi gerçek bir nesneyi, test sırasında kontrol ettiğiniz sahte bir nesneyle değiştirir. Python bunu Unittest.mock modülüne dahil eder.
Mock ve MagicMock arasındaki fark nedir?
MagicMock, __len__, __iter__ ve __enter__ gibi sihirli yöntemleri de destekleyen bir Mock’tur. patch() varsayılan olarak bir MagicMock oluşturur.
Yamam neden çalışmıyor?
Genellikle nesnenin kullanıldığı yer yerine tanımlandığı yeri yamaladığınız için. Test edilen modülün içindeki adı yamalayın.
dönüş_değeri ile yan_etki arasındaki fark nedir?
return_value sabit bir sonuç ayarlar. side_fect bir istisna oluşturabilir, listeden değerleri sırayla döndürebilir veya bir işlevi çağırabilir.
Autospec ne işe yarar?
Gerçek nesneden bir örnek oluşturur, bu nedenle eksik özellikler ve yanlış argümanlar, tıpkı gerçek kodda olduğu gibi hatalara neden olur.
Öğrenmeye devam et
Test, WordPress veya SEO konusunda yardıma mı ihtiyacınız var?
Ekiplerin daha iyi yazılımlar sunmasına ve sitelerini büyütmesine yardımcı oluyoruz. Bize ne üzerinde çalıştığınızı söyleyin, biz de bir sonraki adım için net bir öneride bulunalım.
Daha fazla kılavuz test, yapay zeka ve modern geliştirme
Bu makaleyi beğendiniz mi? Bir konu seçin ve doğrudan en yeni eğitimlerimize geçin.
Yapay Zeka Halüsinasyonları Açıklandı: Dil Modelleri Neden İşleri Düzeltiyor ve Nasıl Azaltılabilir?
Yapay zeka halüsinasyonları açıkladı: ne olduklarını, dil modellerinin bunları neden ürettiğini, yaygın türlerini, temellendirme ve yönlendirmelerle…
Makaleyi okuyun →DevOpsAçıklanan Ortam Değişkenleri: .env Dosyaları, Sırlar ve Yapılandırma Doğru Yapıldı
Çalıştırdığımız Python örnekleriyle açıklanan ortam değişkenleri: bunları okumak ve ayarlamak, neden her değerin bir dize olduğu, .env dosyaları, sü…
Makaleyi okuyun →Yazılım geliştirmeAPI Hız Sınırlamasının Açıklaması: Algoritmalar, Başlıklar ve 429 Hatalarının Ele Alınması
API hızı sınırlaması, çalıştırdığımız bir simülasyonla açıklanmıştır: belirteç kümesi, sabit pencere, kayan pencere, 429 yanıt, hız sınırı başlıklar…
Makaleyi okuyun →Test otomasyonuPython'da alay etme: Unittest.mock, patch ve Testleri Yalan Yapan Hatalar
Unittest.mock ile Python'da alay etme, test edildi: Sahte ve dönüş_değeri, yan_etki, bir adın kullanıldığı yerde yama uygulama, spesifikasyon ve oto…
Makaleyi okuyun →Yazılım testiKod Kapsamının Açıklaması: Hat ve Şube Kapsamı ve %100'ün Gerçek Anlamı
Kod kapsamı gerçek bir kapsama.py çalışmasıyla açıklanmıştır: hat, şube ve durum kapsamı, bir raporun okunması, %100 kapsamın neden hâlâ hataları ka…
Makaleyi okuyun →Veritabanları ve SQLSQL'de DELETE vs TRUNCATE vs DROP: Farklılıklar, Geri Alma ve Her Biri Ne Zaman Kullanılmalı
DELETE vs TRUNCATE vs DROP test edilmiş SQL ile açıklandı: her birinin neyi kaldırdığı, geri aldığı, tetiklediği, kimlik sıfırlaması, hızı ve Postgr…
Makaleyi okuyun →Veritabanları ve SQLAçıklanan SQL Görünümleri: Örneklerle Tuzakları Oluşturun, Kullanın ve Önleyin
Çalıştırdığımız sorgularla açıklanan SQL görünümleri: görünüm oluşturma, canlı sonuçlar, sütunları gizleme, performans, güncellenebilir ve gerçekleş…
Makaleyi okuyun →Yazılım geliştirmeTypeScript Arayüzü ve Tür: Gerçek Farklılıklar, Derleyiciyle Test Edildi
TypeScript arayüzü ve türü, gerçek derleyiciyle kontrol edildi: her ikisinin de yapabileceği, yalnızca birinin yapabileceği, bildirim birleştirme, g…
Makaleyi okuyun →Yazılım geliştirmePython *args ve **kwargs'ın Açıklaması: Açık Örneklerle Esnek Fonksiyonlar
Python *args ve **kwargs çalıştırdığımız kodla açıklanmıştır: paketleme ve paketten çıkarma, parametre sırası, yalnızca anahtar kelime ve yalnızca k…
Makaleyi okuyun →Yazılım geliştirmePython Lambda İşlevleri: Söz Dizimi, Gerçek Kullanımlar ve Ne Zaman Kullanılmaması Gerektiği
Çalıştırdığımız kodla açıklanan Python lambda işlevleri: sözdizimi, anahtarla sıralama, harita ve filtre, kapatmalar, geç bağlama tuzağı ve normal b…
Makaleyi okuyun →Yapay zekâYapay Zeka Ajanları Nelerdir? Çalıştırabileceğiniz Basit Bir Aracı Döngüsüyle Nasıl Çalışırlar?
Yapay zeka ajanları nedir? Aracı döngüsüne, araçlara, belleğe ve korkuluklara ilişkin basit bir kılavuz, çalıştırdığımız küçük bir Python döngüsü, g…
Makaleyi okuyun →DevOpsİzleme ve Gözlemlenebilirlik: Günlükler, Metrikler ve İzlerle Gerçek Fark
İzleme ve gözlemlenebilirliğin açıklanması: bilinen ve bilinmeyen arızalar, günlükler, ölçümler ve izler, ödeme olayının gözden geçirilmesi, altın s…
Makaleyi okuyun →DevOpsDocker Birimleri ve Bağlama Bağlantıları: Konteyner Verilerini Doğru Şekilde Kalıcılaştırın
Docker birimleri ile bağlama bağlamaları arasındaki karşılaştırma açıklandı: kapsayıcı verilerinin neden kaybolduğu, adlandırılmış birimler, bağlama…
Makaleyi okuyun →Test otomasyonuTDD ve BDD: Temel Farklılıklar, Her İki Şekilde Geliştirilmiş Bir Özellik ve Her Birinin Ne Zaman Kullanılacağı
TDD ve BDD, her iki yönde de oluşturulmuş bir indirim özelliğiyle açıklandı: çalıştırdığımız kodla kırmızı-yeşil-refactor, Verilen-Ne Zaman-O Zaman …
Makaleyi okuyun →Yazılım geliştirmeGit merge vs rebase: farklar, örnekler ve hangisi ne zaman kullanılır
Git merge vs rebase; gerçek log çıktısı, karşılaştırma tablosu, rebase'in altın kuralı ve güvenli bir ekip iş akışıyla anlatılıyor.
Makaleyi okuyun →Yazılım geliştirmePython Liste Üreteci (List Comprehension): Sözdizimi, Örnekler ve Kaçınılacak Durumlar
Python list comprehension anlatımı: sözdizimi, filtreleme, if-else, iç içe döngüler, ölçülmüş süreler ve düz for döngüsünün daha iyi olduğu durumlar.
Makaleyi okuyun →Veritabanları ve SQLSQL JOIN Örneklerle Açıklama (Inner, Left, Right, Full)
SQL JOIN'leri örnek tablolar ve sorgularla adım adım öğrenin: INNER, LEFT, RIGHT, FULL, CROSS ve SELF; yaygın hatalar ve performans ipuçları.
Makaleyi okuyun →