Test Yazma: unittest ve pytest
Bir cambaz düşünün. Sahneye çıkıyor, ip üzerinde yürüyor, havada taklalar atıyor. Ama altında her zaman bir güvenlik ağı var. Düşerse ölmez, kalkar tekrar dener. İşte yazılımda testler tam olarak bu güvenlik ağıdır.
Kod yazıyorsun, çalışıyor, harika. Ama sonra bir değişiklik yapıyorsun ve bamm — eski bir şey bozuluyor. Testlerin varsa, bu bozulmayı anında fark edersin. Yoksa... müşteri fark eder. Ve bu hiç hoş olmaz.
Bu derste Python'da test yazmanın temellerini öğreneceğiz. unittest ile başlayıp, pytest'e geçeceğiz. Mocking, coverage ve TDD kavramlarına da göz atacağız.
Neden Test Yazarız?
"Kodumu çalıştırıyorum, çalışıyor işte. Neden ayrıca test yazayım ki?"
Bu soruyu her geliştirici hayatında en az bir kez sorar. Cevap basit: bugün çalışıyor olması, yarın da çalışacağı anlamına gelmez.
Testlerin Sağladığı Faydalar
Güvenlik ağı: Yeni özellik eklerken eski kodun bozulup bozulmadığını anında görürsün
Dokümantasyon: Testler, kodun nasıl kullanılacağını gösterir
Cesaret: Refactoring yaparken "acaba bir şey bozulur mu?" korkusu olmaz
Hız: Manuel test etmek saatler alır, otomatik testler saniyeler
Düşün ki bir e-ticaret sitesi yazıyorsun. Sepet modülünde bir değişiklik yaptın. Ödeme hâlâ çalışıyor mu? İndirim hesaplaması doğru mu? Kargo ücreti hâlâ ekleniyor mu? Bunları her seferinde elle kontrol etmek... işkence.
# Test olmadan hayat
def add_to_cart(cart, item):
cart.append(item)
return cart
# "Çalışıyor gibi" deyip geçmek...
result = add_to_cart([], "laptop")
print(result) # ['laptop'] — tamam çalışıyor
# Ama ya edge case'ler?
# None geçsek? Aynı ürünü iki kez eklersek?
# Stokta yoksa? Fiyat negatifse?Test yazmak "ekstra iş" değil, gelecekteki senin için yatırım. 6 ay sonra o koda döndüğünde, testler sana "her şey yolunda" ya da "burada bir sorun var" diyecek.
unittest — Python'ın Built-in Test Framework'ü
Python'la birlikte gelen unittest modülü, Java'nın JUnit'inden esinlenmiştir. Biraz verbose (uzun yazımlı) olsa da, standart kütüphanede olması büyük avantaj.
İlk Test Sınıfımız
import unittest
def multiply(a, b):
return a * b
class TestMultiply(unittest.TestCase):
def test_positive_numbers(self):
self.assertEqual(multiply(3, 4), 12)
def test_negative_numbers(self):
self.assertEqual(multiply(-2, 5), -10)
def test_zero(self):
self.assertEqual(multiply(7, 0), 0)
def test_floats(self):
self.assertAlmostEqual(multiply(0.1, 0.2), 0.02)
if __name__ == "__main__":
unittest.main()Bunu test_multiply.py olarak kaydedip terminalde çalıştırıyorsun:
python -m unittest test_multiply.pyÇıktı:
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OKHer nokta bir başarılı test. 4 nokta = 4 test geçti. Hepsi OK!
Temel Assert Metodları
unittest.TestCase sınıfı bir sürü assert metodu sunar. En çok kullanılanlar:
import unittest
class TestAssertMethods(unittest.TestCase):
def test_equality(self):
self.assertEqual(1 + 1, 2) # Eşit mi?
self.assertNotEqual(1 + 1, 3) # Eşit değil mi?
def test_truthiness(self):
self.assertTrue(10 > 5) # True mu?
self.assertFalse(10 < 5) # False mu?
def test_identity(self):
self.assertIsNone(None) # None mu?
self.assertIsNotNone("hello") # None değil mi?
def test_membership(self):
self.assertIn(3, [1, 2, 3]) # İçinde mi?
self.assertNotIn(4, [1, 2, 3]) # İçinde değil mi?
def test_types(self):
self.assertIsInstance(42, int) # Bu türde mi?
def test_exceptions(self):
with self.assertRaises(ValueError): # Hata fırlatıyor mu?
int("not_a_number")💡 İpucu:
assertEqualyerine düzassertde kullanabilirsin amaunittest'in assert metodları başarısız olduğunda çok daha açıklayıcı hata mesajı verir.assertEqual(result, 42)başarısız olursa "AssertionError: 15 != 42" der. Düzassertsadece "AssertionError" der.
setUp ve tearDown
Her testten önce ve sonra çalışacak hazırlık/temizlik kodları yazabilirsin:
import unittest
import os
class TestFileOperations(unittest.TestCase):
def setUp(self):
"""Her testten ÖNCE çalışır"""
self.test_file = "test_data.txt"
with open(self.test_file, "w") as f:
f.write("test content")
print(f" setUp: {self.test_file} oluşturuldu")
def tearDown(self):
"""Her testten SONRA çalışır"""
if os.path.exists(self.test_file):
os.remove(self.test_file)
print(f" tearDown: {self.test_file} silindi")
def test_file_exists(self):
self.assertTrue(os.path.exists(self.test_file))
def test_file_content(self):
with open(self.test_file) as f:
content = f.read()
self.assertEqual(content, "test content")
def test_file_size(self):
size = os.path.getsize(self.test_file)
self.assertGreater(size, 0)setUp her test metodu için ayrı ayrı çalışır. Yani testler birbirinden bağımsız kalır. Bu çok önemli — testler arası bağımlılık, test yazmanın en büyük günahıdır.
Ayrıca setUpClass ve tearDownClass var. Bunlar tüm sınıf için bir kez çalışır:
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Tüm testlerden ÖNCE bir kez çalışır"""
cls.db = create_test_database()
print("Veritabanı oluşturuldu")
@classmethod
def tearDownClass(cls):
"""Tüm testlerden SONRA bir kez çalışır"""
cls.db.destroy()
print("Veritabanı silindi")
def setUp(self):
"""Her testten önce"""
self.db.begin_transaction()
def tearDown(self):
"""Her testten sonra"""
self.db.rollback()Test Dosya Yapısı
Test dosyalarının bir düzeni olmalı. Genel kabul görmüş yapı:
my_project/
├── my_project/
│ ├── __init__.py
│ ├── calculator.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_calculator.py
│ └── test_utils.py
└── setup.pyKurallar basit:
Test dosyaları
test_ile başlarTest sınıfları
Testile başlarTest metodları
test_ile başlar
Bu isimlendirme kurallarına uyarsan, test runner'lar testlerini otomatik olarak bulur.
pytest — Modern Python Testing
unittest iş görür ama biraz uzun yazımlıdır. pytest ise Python dünyasının en popüler test framework'üdür. Daha az kod, daha okunabilir, daha güçlü.
Kurulum
pip install pytestİlk pytest Testi
# test_basic.py
def add(a, b):
return a + b
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
def test_add_zero():
assert add(5, 0) == 5Fark ettin mi? Sınıf yok, self.assertEqual yok. Sadece düz assert. Bu kadar basit!
Çalıştırmak için:
pytest test_basic.py -vtest_basic.py::test_add_positive PASSED
test_basic.py::test_add_negative PASSED
test_basic.py::test_add_zero PASSED
========================= 3 passed in 0.02s =========================-v (verbose) bayrağı her testin adını gösterir. Onsuz sadece nokta/F görürsün.
pytest'in Sihirli assert'ü
pytest'in en güzel özelliği: düz assert kullanırsın ama hata mesajları harika:
def test_list_comparison():
expected = [1, 2, 3, 4, 5]
actual = [1, 2, 3, 4, 6]
assert actual == expectedÇıktı:
E assert [1, 2, 3, 4, 6] == [1, 2, 3, 4, 5]
E At index 4 diff: 6 != 5pytest assert'i yeniden yazarak (rewriting) detaylı hata mesajı üretir. Bu unittest'in en büyük dezavantajlarından birini ortadan kaldırır.
Exception Test Etme
import pytest
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero!")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError) as exc_info:
divide(10, 0)
assert "Cannot divide by zero" in str(exc_info.value)
def test_divide_normal():
assert divide(10, 2) == 5.0Fixtures — Test Hazırlığının Zarif Yolu
unittest'te setUp/tearDown vardı. pytest'te fixture var. Çok daha esnek:
import pytest
@pytest.fixture
def sample_list():
"""Her test için taze bir liste döner"""
return [1, 2, 3, 4, 5]
@pytest.fixture
def empty_dict():
return {}
def test_list_length(sample_list):
assert len(sample_list) == 5
def test_list_sum(sample_list):
assert sum(sample_list) == 15
def test_dict_is_empty(empty_dict):
assert len(empty_dict) == 0Fixture'ı parametre olarak yazdığın an, pytest onu otomatik olarak çağırıp sonucunu sana verir. Sihir gibi!
Fixture'da Setup ve Teardown
yield kullanarak hem hazırlık hem temizlik yapabilirsin:
import pytest
import sqlite3
@pytest.fixture
def db_connection():
# Setup kısmı
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'Ali')")
conn.commit()
yield conn # Testi bu bağlantıyla çalıştır
# Teardown kısmı
conn.close()
def test_user_count(db_connection):
cursor = db_connection.execute("SELECT COUNT(*) FROM users")
count = cursor.fetchone()[0]
assert count == 1
def test_user_name(db_connection):
cursor = db_connection.execute("SELECT name FROM users WHERE id=1")
name = cursor.fetchone()[0]
assert name == "Ali"conftest.py — Paylaşılan Fixture'lar
Birden fazla test dosyasında aynı fixture'ı kullanmak istiyorsan, conftest.py dosyasına koy:
# tests/conftest.py
import pytest
@pytest.fixture
def api_client():
"""Tüm test dosyaları bu fixture'ı kullanabilir"""
client = TestClient()
client.login("test_user", "test_pass")
yield client
client.logout()
@pytest.fixture
def sample_user():
return {
"name": "Ahmet",
"email": "ahmet@example.com",
"age": 25
}conftest.py dosyasını import etmene gerek yok. pytest onu otomatik bulur ve içindeki fixture'ları tüm testlere sunar.
parametrize — Aynı Testi Farklı Verilerle Çalıştır
Bazen aynı testi farklı inputlarla çalıştırmak istersin. Kopyala-yapıştır yapma:
import pytest
def is_palindrome(s):
s = s.lower().replace(" ", "")
return s == s[::-1]
@pytest.mark.parametrize("text, expected", [
("racecar", True),
("hello", False),
("A man a plan a canal Panama", True),
("", True),
("ab", False),
("aba", True),
])
def test_palindrome(text, expected):
assert is_palindrome(text) == expectedÇıktı:
test_palindrome[racecar-True] PASSED
test_palindrome[hello-False] PASSED
test_palindrome[A man a plan a canal Panama-True] PASSED
test_palindrome[-True] PASSED
test_palindrome[ab-False] PASSED
test_palindrome[aba-True] PASSEDTek bir fonksiyonla 6 test! Her parametre kombinasyonu ayrı bir test olarak çalışır.
Test Türleri
Testler kapsamlarına göre üçe ayrılır. Bir piramit gibi düşün:
/\
/ \ E2E Tests (az ama kritik)
/----\
/ \ Integration Tests (orta)
/--------\
/ \ Unit Tests (çok ve hızlı)
/____________\Unit Test
Tek bir fonksiyon veya metodun doğru çalışıp çalışmadığını test eder. Dışa bağımlılık yok, çok hızlı çalışır.
def test_calculate_tax():
assert calculate_tax(100, rate=0.18) == 18.0
def test_format_name():
assert format_name("ahmet", "yılmaz") == "Ahmet Yılmaz"Integration Test
Birden fazla parçanın birlikte çalışıp çalışmadığını test eder. Veritabanı, API gibi dış bağımlılıklar olabilir.
def test_user_registration(db_connection):
user_service = UserService(db_connection)
user = user_service.register("ali@example.com", "password123")
# Veritabanına kayıt oldu mu?
saved_user = user_service.get_by_email("ali@example.com")
assert saved_user is not None
assert saved_user.email == "ali@example.com"E2E (End-to-End) Test
Tüm sistemi baştan sona test eder. Kullanıcı gibi davranır. Yavaş ama gerçekçi.
# Selenium veya Playwright ile
def test_login_flow(browser):
browser.go_to("/login")
browser.fill("email", "user@example.com")
browser.fill("password", "secret123")
browser.click("Login")
assert browser.current_url == "/dashboard"
assert browser.text_contains("Welcome back!")⚠️ Dikkat: Test piramidini tersine çevirme! Çoğunluk unit test olmalı (hızlı, güvenilir). Integration testler orta düzeyde. E2E testler az ama kritik noktalar için. E2E testlere çok yaslanırsan, test suite'in yavaş ve kırılgan olur.
Mocking — Dış Bağımlılıkları Taklit Etme
Test yazarken bazen dış dünyaya bağımlılık olur: API çağrıları, veritabanı, dosya sistemi, zaman... Bunları gerçekten çağırmak istemezsin çünkü:
Yavaş olur
İnternet gerektirir
Yan etkileri olur
Sonuçlar değişken olabilir
İşte burada mock devreye girer. "Taklit nesne" oluşturursun.
unittest.mock ile Mocking
from unittest.mock import patch, MagicMock
import requests
def get_user_name(user_id):
"""API'den kullanıcı adını getirir"""
response = requests.get(f"https://api.example.com/users/{user_id}")
if response.status_code == 200:
return response.json()["name"]
return None
# Gerçek API'yi çağırmadan test et
@patch("requests.get")
def test_get_user_name(mock_get):
# Mock'un ne döneceğini ayarla
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"name": "Ahmet", "id": 1}
mock_get.return_value = mock_response
# Fonksiyonu çağır
result = get_user_name(1)
# Doğrulamalar
assert result == "Ahmet"
mock_get.assert_called_once_with("https://api.example.com/users/1")@patch dekoratörü requests.get'i bir mock ile değiştirir. Gerçek HTTP isteği yapılmaz.
MagicMock Detaylı
from unittest.mock import MagicMock
# MagicMock her şeyi kabul eder
mock = MagicMock()
mock.some_method(1, 2, 3) # Hata yok
mock.any_attribute.nested.deep # Hata yok
# Davranış tanımla
mock.calculate.return_value = 42
assert mock.calculate() == 42
# Side effect tanımla
mock.validate.side_effect = ValueError("Invalid!")
# mock.validate() # ValueError fırlatır
# Çağrı kontrolü
mock.process("data")
mock.process.assert_called_once_with("data")patch ile Context Manager
from unittest.mock import patch
from datetime import datetime
def get_greeting():
hour = datetime.now().hour
if hour < 12:
return "Günaydın!"
elif hour < 18:
return "İyi günler!"
return "İyi akşamlar!"
def test_morning_greeting():
with patch("datetime.datetime") as mock_dt:
mock_dt.now.return_value.hour = 9
# Not: datetime mock'lamak biraz tricky olabilir
# Gerçek projede freezegun kütüphanesi önerilir
def test_evening_greeting():
with patch("datetime.datetime") as mock_dt:
mock_dt.now.return_value.hour = 20💡 İpucu: Çok fazla mock kullanmak, testlerin "gerçek" olmaktan çıkması demektir. Mock sayısı arttıkça testin güvenilirliği azalır. Eğer bir fonksiyonu test etmek için 5+ mock gerekiyorsa, belki fonksiyon çok fazla şey yapıyordur. Refactoring zamanı!
Coverage — Ne Kadar Kodun Test Edilmiş?
Test yazdın ama ne kadar kodun test edilmiş? İşte coverage (kapsam) bu soruya cevap verir.
pytest-cov Kurulumu
pip install pytest-covKullanım
pytest --cov=my_project tests/Çıktı:
---------- coverage: ... ----------
Name Stmts Miss Cover
-------------------------------------------
my_project/__init__.py 0 0 100%
my_project/calculator.py 20 4 80%
my_project/utils.py 15 8 47%
-------------------------------------------
TOTAL 35 12 66%HTML Rapor
pytest --cov=my_project --cov-report=html tests/Bu komut htmlcov/ dizininde güzel bir HTML rapor oluşturur. Tarayıcıda açınca, hangi satırların test edilip hangilerinin edilmediğini satır satır görürsün. Yeşil satırlar test edilmiş, kırmızılar edilmemiş.
Hedef Coverage Oranı
pytest --cov=my_project --cov-fail-under=80 tests/--cov-fail-under=80 sayesinde coverage %80'in altındaysa testler başarısız sayılır. CI/CD pipeline'ında bunu kullanmak, kaliteyi garanti altına alır.
# pytest.ini veya pyproject.toml ile de ayarlanabilir
# pyproject.toml
"""
[tool.pytest.ini_options]
addopts = "--cov=my_project --cov-fail-under=80"
[tool.coverage.run]
omit = ["tests/*", "setup.py"]
"""⚠️ Dikkat: %100 coverage hedeflemek genellikle zaman kaybıdır. %80-90 arası iyi bir hedeftir. Önemli olan rakam değil, kritik iş mantığının test edilmiş olmasıdır. Getter/setter gibi trivial kodları test etmeye gerek yok.
TDD — Test Driven Development
TDD bir geliştirme yaklaşımıdır. Önce testi yaz, sonra kodu yaz. Üç adımda döner:
Red → Green → Refactor
Red 🔴: Başarısız bir test yaz (çünkü daha kod yok)
Green 🟢: Testi geçirecek minimum kodu yaz
Refactor 🔵: Kodu iyileştir, testler hâlâ geçiyor mu kontrol et
# Adım 1: RED — Test yaz, başarısız olsun
def test_fizzbuzz():
assert fizzbuzz(3) == "Fizz"
assert fizzbuzz(5) == "Buzz"
assert fizzbuzz(15) == "FizzBuzz"
assert fizzbuzz(7) == "7"
# Bu noktada fizzbuzz fonksiyonu yok, test FAIL eder
# Adım 2: GREEN — Minimum kod yaz
def fizzbuzz(n):
if n % 15 == 0:
return "FizzBuzz"
if n % 3 == 0:
return "Fizz"
if n % 5 == 0:
return "Buzz"
return str(n)
# Testler geçiyor!
# Adım 3: REFACTOR — İyileştir
def fizzbuzz(n):
result = ""
if n % 3 == 0:
result += "Fizz"
if n % 5 == 0:
result += "Buzz"
return result or str(n)
# Testler hâlâ geçiyor mu? Evet! Güzel.TDD'yi her zaman uygulamak zorunda değilsin. Ama karmaşık iş mantığı yazarken, edge case'leri düşünmeni sağlar ve gereksiz kod yazmaktan korur.
Pratik: Calculator Test Suite
Şimdi öğrendiklerimizi birleştirelim. Bir Calculator sınıfı için kapsamlı bir test suite yazacağız.
Calculator Sınıfı
# calculator.py
class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
result = a + b
self._record(f"{a} + {b} = {result}")
return result
def subtract(self, a, b):
result = a - b
self._record(f"{a} - {b} = {result}")
return result
def multiply(self, a, b):
result = a * b
self._record(f"{a} * {b} = {result}")
return result
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero!")
result = a / b
self._record(f"{a} / {b} = {result}")
return result
def _record(self, operation):
self.history.append(operation)
def get_history(self):
return self.history.copy()
def clear_history(self):
self.history.clear()Test Suite (pytest ile)
# test_calculator.py
import pytest
from calculator import Calculator
@pytest.fixture
def calc():
"""Her test için temiz bir Calculator instance'ı"""
return Calculator()
# === Toplama Testleri ===
class TestAdd:
def test_positive_numbers(self, calc):
assert calc.add(2, 3) == 5
def test_negative_numbers(self, calc):
assert calc.add(-1, -1) == -2
def test_mixed_numbers(self, calc):
assert calc.add(-5, 3) == -2
def test_floats(self, calc):
result = calc.add(0.1, 0.2)
assert abs(result - 0.3) < 1e-9
def test_large_numbers(self, calc):
assert calc.add(10**18, 10**18) == 2 * 10**18
# === Çıkarma Testleri ===
class TestSubtract:
def test_basic(self, calc):
assert calc.subtract(10, 3) == 7
def test_negative_result(self, calc):
assert calc.subtract(3, 10) == -7
# === Bölme Testleri ===
class TestDivide:
def test_basic(self, calc):
assert calc.divide(10, 2) == 5.0
def test_float_result(self, calc):
assert calc.divide(7, 2) == 3.5
def test_divide_by_zero(self, calc):
with pytest.raises(ValueError, match="Cannot divide by zero"):
calc.divide(10, 0)
# === Çarpma Testleri ===
class TestMultiply:
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 6),
(0, 100, 0),
(-2, 3, -6),
(-2, -3, 6),
(0.5, 4, 2.0),
])
def test_multiply(self, calc, a, b, expected):
assert calc.multiply(a, b) == expected
# === History Testleri ===
class TestHistory:
def test_empty_history(self, calc):
assert calc.get_history() == []
def test_history_records(self, calc):
calc.add(1, 2)
calc.multiply(3, 4)
history = calc.get_history()
assert len(history) == 2
assert "1 + 2 = 3" in history[0]
def test_clear_history(self, calc):
calc.add(1, 2)
calc.clear_history()
assert calc.get_history() == []
def test_history_is_copy(self, calc):
"""get_history orijinal listeyi değil kopyasını döner"""
calc.add(1, 2)
history = calc.get_history()
history.clear() # Kopyayı temizle
assert len(calc.get_history()) == 1 # Orijinal etkilenmezTestleri Çalıştırma
# Tüm testleri çalıştır
pytest test_calculator.py -v
# Sadece bölme testlerini çalıştır
pytest test_calculator.py::TestDivide -v
# Belirli bir testi çalıştır
pytest test_calculator.py::TestAdd::test_positive_numbers -v
# Coverage ile çalıştır
pytest test_calculator.py --cov=calculator --cov-report=term-missing--cov-report=term-missing eklersen, test edilmemiş satır numaralarını da görürsün:
Name Stmts Miss Cover Missing
----------------------------------------------
calculator.py 25 0 100%
----------------------------------------------%100 coverage! 🎉
Bonus: pytest Marker'ları
Testleri kategorize etmek için marker kullanabilirsin:
import pytest
@pytest.mark.slow
def test_heavy_computation():
"""Bu test uzun sürer"""
result = process_million_records()
assert result > 0
@pytest.mark.integration
def test_database_connection():
"""Veritabanı gerektirir"""
db = connect_to_db()
assert db.is_connected()
@pytest.mark.skip(reason="Henüz implement edilmedi")
def test_future_feature():
pass
@pytest.mark.skipif(
sys.platform == "win32",
reason="Windows'ta çalışmaz"
)
def test_unix_only():
pass# Sadece yavaş testleri çalıştır
pytest -m slow
# Yavaş testleri hariç tut
pytest -m "not slow"
# Integration testlerini çalıştır
pytest -m integrationTest Yazma Best Practices
Arrange-Act-Assert (AAA) pattern'ini kullan:
def test_user_full_name():
# Arrange — Hazırla
user = User(first_name="Ahmet", last_name="Yılmaz")
# Act — Çalıştır
full_name = user.get_full_name()
# Assert — Doğrula
assert full_name == "Ahmet Yılmaz"Test adları açıklayıcı olsun:
test_divide_by_zero_raises_value_error— ne test ettiği isimden anlaşılsın.Her test bir şeyi test etsin: Bir test metodunda 20 assert varsa, parçala.
Testler birbirinden bağımsız olsun: Test A'nın sonucu Test B'yi etkilemesin.
Edge case'leri unutma: Boş liste, None, negatif sayılar, çok büyük inputlar...
Test de koddur: Temiz, okunabilir, bakımı kolay olsun.
Özet
Test yazmak kodun güvenlik ağıdır — değişiklik yaparken mevcut şeylerin bozulup bozulmadığını gösterir
unittest Python'ın built-in test framework'üdür:
TestCase,assertEqual,setUp/tearDownpytest daha modern ve basittir: düz
assert, fixture'lar, parametrizeMocking ile dış bağımlılıkları (API, veritabanı) taklit edebilirsin:
patch,MagicMockCoverage ile kodunun ne kadarının test edildiğini ölçersin, hedef %80+
TDD (Red → Green → Refactor) önce test yaz, sonra kod yaz yaklaşımıdır — iyi bir disiplindir
AI Asistan
Sorularını yanıtlamaya hazır