Python Test İşlemleri
Bu yazıda, Python test konusuna odaklanacağız. Python’un test yazılımına destek sağlayan özellikler arasında çeşitli test çerçeveleri (unittest, pytest, doctest vb.), hata ayıklama yetenekleri, geniş kütüphane desteği ve mock nesneler oluşturmayı kolaylaştıran özellikler bulunmaktadır.
Temel Test Kavramları ve Terminolojisi
Test süreci, bir yazılımın belirli bir girdi seti ile beklenen sonuçları verip vermediğini kontrol etme işlemidir. Test sürecinin temel amacı, yazılımdaki hataları, eksiklikleri veya gereksinimlere uygun olmayan özellikleri belirlemek, düzeltmek, yazılımın güvenilirliğini ve performansını doğrulamak ve son kullanıcıya daha iyi bir deneyim sunmaktır.
Test Terminolojisi
- Test Case: Bir test senaryosunu veya testin bir parçasını tanımlayan belge veya yapı.
- Test Suite: Bir grup test case’inin veya testlerin bir araya getirilmesiyle oluşturulan ve birlikte çalıştırılan testlerin toplamı.
- Test Fixture: Bir testin başarılı bir şekilde çalışabilmesi için gerekli olan hazırlık işlemleri ve koşullar.
- Test Runner: Test case’leri çalıştıran ve sonuçları raporlayan yazılım veya araç.
- Test Coverage: Bir test setinin bir yazılımın ne kadarını test ettiğini belirten ölçüm.
Python’da Test Yazmak İçin Kullanılan Popüler Kütüphaneler
- unittest: Python’ın standart kütüphanesinin bir parçası olan unittest, xUnit mimarisine dayalı bir test çerçevesidir. Testleri organize etmek ve çalıştırmak için bir dizi araç ve asseration sunar.
- pytest: pytest, Python programlarını test etmek için kullanılan güçlü ve esnek bir çerçevedir. Basit syntaxı ve geniş özellik seti ile bilinir. Ayrıca, fixture’lar ve parametrize testler gibi özellikler sunar.
- doctest: doctest, Python modüllerinde, işlevlerde ve sınıflarda yerleşik belgelere yazılan testleri çalıştırır. Bu, testlerin ve belgelerin aynı yerde tutulmasını sağlar.
- tox: Farklı Python sürümlerinde ve ortamlarda birim testlerini çalıştırmak için kullanılan bir araçtır.
- coverage: Birim testleri tarafından hangi kodların kapsanmadığını gösteren bir kütüphanedir.
- nose: nose, unittest’in genişletilmiş bir versiyonudur ve daha karmaşık test senaryolarını destekler. Ancak, aktif olarak bakımı yapılmamaktadır ve yerini “nose2” almıştır.
- behave: behave, Davranış-Tabanlı Geliştirme (BDD) için bir çerçeve sunar. Kullanıcı hikayelerine dayalı senaryoları test etmeyi kolaylaştırır.
- robotframework: Kabul testleri yazmak için kullanılır.
- Selenium: Selenium, özellikle web uygulamaları için otomatik testler yazmak üzere tasarlanmış bir kütüphanedir. Web tarayıcıları üzerinde otomatik işlemler yaparak uygulamanın davranışını kontrol eder.
- pylint: Kodunuzu analiz etmek ve olası hataları ve kod kokularını bulmak için kullanılan bir araçtır.
- mypy: Kodunuzu statik olarak analiz etmek ve tip hatalarını bulmak için kullanılan bir araçtır.
Python Test Türleri
- Birim Testi (Unit Test)Unit test, yazılımın belirli bir özelliğinin veya fonksiyonunun doğru çalışıp çalışmadığını kontrol etmek için kullanılır ve hataları erken aşamada tespit etmek için önemlidir. Python’un
unittest
modülü, unit testlerin kolayca yazılmasını ve çalıştırılmasını sağlar. Test case’ler,unittest.TestCase
sınıfından türetilen bir sınıf içinde tanımlanan bir metoddur veassert
metodlarıyla beklenen ve gerçek sonuçlar karşılaştırılır.import unittest # Test edilecek fonksiyon def square(x): return x ** 2 # Test case class TestSquare(unittest.TestCase): def test_positive_number(self): self.assertEqual(square(3), 9) def test_negative_number(self): self.assertEqual(square(-2), 4) # Testlerin çalıştırılması if __name__ == '__main__': unittest.main()
- Entegrasyon Testi (Integration Test)Entegrasyon testleri, yazılımın farklı bileşenleri arasındaki etkileşimi kontrol eder ve hataları erken aşamada tespit etmeye yardımcı olur. Python’da,
unittest
modülü kullanılarak entegrasyon testleri yazılır ve genellikle ayrı bir dosyada saklanır. Bu testler, test sürecinin bir parçası olarak çalıştırılır.import unittest from myproject import MyModule class TestIntegration(unittest.TestCase): def setUp(self): self.module = MyModule() def test_feature(self): self.module.setup_feature() result = self.module.run_feature() self.assertEqual(result, expected_result) if __name__ == '__main__': unittest.main()
Bu örnekte,
setUp()
metodu test başlamadan önce çalışır ve test edilecek modülü başlatır.test_feature()
metodu ise özellikleri çalıştırır ve sonucu beklenen sonuçla karşılaştırır.Python’da entegrasyon testlerinde dış bağımlılıkları kontrol etmek için
unittest.mock
modülü kullanılır. Mock objeler, gerçek objelerin yerine geçerek belli koşullar altında testlerin nasıl işleyeceğini kontrol etme imkanı sağlar. Gerçek işlevsellik sunmazlar, fakat gerçek objelerin arayüzünü taklit ederler ve belirli bir durumu simüle etmek için önceden belirlenmiş davranışları olabilir. Örneğin, bir API çağrısını mock etmek isteyebiliriz. Bu, gerçek API çağrısının zaman alıcı veya maliyetli olduğu durumlarda oldukça yararlı olabilir.import unittest from unittest.mock import patch import requests # API'ye get request yapacak olan fonksiyon def get_data_from_api(url): response = requests.get(url) if response.status_code == 200: return response.json() else: return None # Test case class TestApiCall(unittest.TestCase): @patch('requests.get') def test_get_data_from_api(self, mock_get): # Mock nesnesinin döndüreceği yanıtı belirleyelim mock_response = mock_get.return_value mock_response.status_code = 200 mock_response.json.return_value = {'key': 'value'} url = '<http://fakeurl.com>' result = get_data_from_api(url) # API çağrısının doğru url ile yapıldığını kontrol edelim mock_get.assert_called_once_with(url) # Fonksiyonun doğru değeri döndürdüğünü kontrol edelim self.assertEqual(result, {'key': 'value'}) # Testlerin çalıştırılması if __name__ == '__main__': unittest.main()
Bu örnekte,
requests.get
fonksiyonunu mock ediyoruz veget_data_from_api
fonksiyonunun doğru URL ile çağrıldığını ve beklediğimiz sonucu döndürdüğünü kontrol ediyoruz. - Kabul Testi (Acceptance Test)Acceptance test, yazılımın belirli işlevlerini kullanıcı perspektifinden kontrol eder ve genellikle geliştirme sürecinin son aşamalarında yapılır. Python,
behave
adlı bir kütüphane ile bu testlerin otomatikleştirilmesini sağlar, bu da BDD yaklaşımını benimser ve kullanıcı hikayelerine dayalı senaryoları test etmeyi kolaylaştırır.from behave import given, when, then from some_module import System @given('the system has booted') def step_given(context): context.system = System() @when('a user logs in') def step_when(context): context.system.login('user', 'passwd') @then('the user should see a welcome message') def step_then(context): assert context.system.has_welcome_message()
Yukarıdaki örneklerde,
given
,when
,then
dekoratörleri kullanılarak BDD senaryoları oluşturulmuştur.given
ile başlangıç durumu,when
ile senaryonun gerçekleştiği durum vethen
ile sonuç durumu tanımlanır. Bu yaklaşım, testlerin anlaşılırlığını ve okunabilirliğini artırır. - Fonksiyonel Testler (Functional Tests)Fonksiyonel testler, yazılımın işlevlerinin doğru çalışıp çalışmadığını kontrol eder ve genellikle kullanıcı senaryoları ve iş akışları üzerinden gerçekleştirilir. Python ve Selenium, bu tür testlerin yazılması ve otomatize edilmesi için kullanılır, örneğin Selenium WebDriver ile bir web sitesine giriş yapma ve başarılı giriş sonrası mesajı kontrol etme.
from selenium import webdriver def test_login(): driver = webdriver.Firefox() driver.get("<http://www.example.com/login>") username_field = driver.find_element_by_name("username") password_field = driver.find_element_by_name("password") submit_button = driver.find_element_by_name("submit") username_field.send_keys("user") password_field.send_keys("pass") submit_button.click() assert "Welcome, user!" in driver.page_source driver.quit()
- Regresyon Testleri (Regression Tests)Regression testleri, yazılımın mevcut özelliklerinin yeni eklemeler veya değişiklikler sonucu bozulup bozulmadığını kontrol eder. Python’un
unittest
modülü ile regression testleri yazılabilir. Otomatik regression test süitleri oluşturmak içinunittest.TestSuite
sınıfı kullanılır.import unittest class TestMyFunction(unittest.TestCase): def test_addition(self): self.assertEqual(add(2, 2), 4) def test_subtraction(self): self.assertEqual(subtract(2, 2), 0) def suite(): suite = unittest.TestSuite() suite.addTest(TestMyFunction('test_addition')) suite.addTest(TestMyFunction('test_subtraction')) return suite if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suite())
Bu örnekte,
add
vesubtract
fonksiyonlarını test eden iki test case oluşturulmuştur.suite
fonksiyonu, bu iki test case’i bir araya getirerek bir test süiti oluşturur. Test süiti,unittest.TextTestRunner
nesnesi tarafından çalıştırılır. - Performans Testleri (Performance Tests)Performans testleri, bir uygulamanın yüksek yük altında beklenen performansı sağlayıp sağlamadığını kontrol eder ve Python, bu testleri yazmak için çeşitli kütüphaneler ve araçlar sunar. Özellikle
pytest
,locust
vepyperf
gibi kütüphaneler, uygulamanın performansını ölçmek ve analiz etmek için kullanılır.import time import pytest def test_performance(): start_time = time.time() # Some function to test function_to_test() end_time = time.time() assert end_time - start_time < 2
Test Verileri ve Fixture’lar
Python ile test işlemlerinde, test verileri testlerin doğru çalışıp çalışmadığını kontrol etmek için oluşturulur ve genellikle bir veritabanında saklanır veya test kodunda tanımlanır. Fixture’lar, testlerin düzgün çalışması için gereken veri veya durumları oluşturur ve Python, pytest
kütüphanesi ile bu fixture’ları destekler.
import pytest @pytest.fixture def example_data(): return { 'username': 'test_user', 'password': 'test_password' } def test_login(example_data): username = example_data['username'] password = example_data['password'] assert login(username, password) == True
Bu örnekte, example_data
bir fixture’dır ve test_login
testi bu fixture’ı kullanır. pytest
bu fixture’ı otomatik olarak enjekte eder ve kullanılabilir hale getirir.
Testlerde Veri Tabanı Erişimi ve SQLModel Kullanımı
Test süreçlerinde veri tabanı erişiminin zorlukları, testlerin bağımsız ve yalıtılmış olması gerekliliğinden kaynaklanır. SQLModel, Python’da SQL veri tabanları ile etkileşim için kullanılan bir kütüphane ile bu süreçler gerçekleştirilebilir. Veri tabanı işlemleri genellikle bir transaction içerisinde gerçekleştirilir ve bu, testlerin birbirini etkilememesi için önemlidir.
from sqlmodel import Session, SQLModel, create_engine from sqlmodel.pool import StaticPool engine = create_engine("sqlite://", poolclass=StaticPool) def test_create_customer(): with Session(engine) as session: customer = Customer(name="John Doe", email="john.doe@example.com") session.add(customer) session.commit() assert customer.id is not None
Bu örnekte, create_customer
testi bir Customer
oluşturup veri tabanına ekler ve ID’nin None
olmadığını kontrol eder.
Test Raporlama ve Raporlarının Değerlendirilmesi
Python’da birçok test çerçevesi, otomatik raporlama özellikleri sunar. Örneğin, unittest
modülü, testlerin sonuçlarını ve hata mesajlarını otomatik olarak çıktı olarak verir. Bu, test sonuçlarının hızlı ve kolay bir şekilde analiz edilmesini ve problemli alanların belirlenmesini sağlar.
Daha ayrıntılı ve okunabilir test raporları oluşturmak için çeşitli Python kütüphaneleri bulunmaktadır. Bunlardan biri pytest-html
kütüphanesidir. pytest-html
, pytest
test çerçevesi ile birlikte kullanılarak HTML formatında test raporları oluşturur. Bu raporlar, test sonuçlarını, hata mesajlarını ve diğer önemli bilgileri içerir. Bu raporlar, bir web tarayıcısında kolayca görüntülenebilir, bu da raporların paylaşılmasını ve incelenmesini kolaylaştırır.
Test raporlarını okurken ve yorumlarken, öncelikle hatalı testlere odaklanmak genellikle en iyisidir. Hata mesajları, hatalı testlerin neden başarısız olduğuna dair ipuçları sağlar. Bu, hataların hızlı bir şekilde belirlenip düzeltilmesine yardımcı olur. Başarılı testlerin sayısı ve toplam test sayısı, test sürecinin genel başarısını değerlendirmek için kullanılabilir. Bu sayılar, test sürecinin genel etkinliği ve kapsamı hakkında bilgi verir. Testlerin ne kadar sürede çalıştığını gösteren zaman damgaları, performans sorunlarını belirlemeye yardımcı olabilir. Uzun çalışma süreleri, performans sorunlarını gösterebilir ve bu sorunların çözülmesi için daha fazla inceleme gerektirebilir.
CI/CD Süreçleri ve Testlerin Entegrasyonu
CI/CD (Sürekli Entegrasyon / Sürekli Dağıtım) süreçleri, yazılım geliştirme aşamasından itibaren testlerin otomatik olarak entegre edilmesini sağlar, bu da kodun sürekli entegrasyonunu ve dağıtımını mümkün kılar. Her kod değişikliği veya güncelleme ile testler otomatik olarak çalıştırılır, bu da hataların erken tespit edilmesini ve düzeltilmesini sağlar. Araçlar, örneğin Jenkins, Travis CI ve GitLab CI/CD, Python testlerinin otomatik olarak çalıştırılmasını ve sonuçlarının raporlanmasını sağlar. Otomatik testler, CI/CD süreçlerinde yazılımın kalitesini ve güvenilirliğini sağlamak için önemlidir.
Yazımızın sonuna geldik. Test yazarken detaylara dikkat etmek, her yazılım bölümü için ayrıntılı ve kapsamlı testler yapmak daha da önemlidir. Testlerin bağımsız çalıştırılması ve kodun kendini test edebilme yeteneği, sadece bir gereklilik değil, bir zorunluluktur. Testlerin kullanıcı beklentilerini karşılaması ve otomatikleştirilmesi, başarıya giden yolda kritik bir adımdır. Dahası, test stratejileri ve teknikleri sürekli olarak gözden geçirilmeli ve sürekli iyileştirme stratejileri sadece uygulanmalı, aynı zamanda uygulamaya devam etmeliyiz. Sonraki yazıda görüşmek üzere.