एडवांस्ड फिक्स्चर तकनीकों के साथ Pytest की पूरी क्षमता को अनलॉक करें। मजबूत और कुशल Python टेस्टिंग के लिए पैरामीटराइज्ड टेस्टिंग और मॉक इंटीग्रेशन का लाभ उठाना सीखें।
Pytest एडवांस्ड फिक्स्चर्स में महारत हासिल करना: पैरामीटराइज्ड टेस्टिंग और मॉक इंटीग्रेशन
Pytest Python के लिए एक शक्तिशाली और लचीला टेस्टिंग फ्रेमवर्क है। इसकी सादगी और विस्तारशीलता इसे दुनिया भर के डेवलपर्स के बीच पसंदीदा बनाती है। Pytest की सबसे आकर्षक विशेषताओं में से एक इसका फिक्स्चर सिस्टम है, जो सुंदर और पुन: प्रयोज्य (reusable) टेस्ट सेटअप की अनुमति देता है। यह ब्लॉग पोस्ट एडवांस्ड फिक्स्चर तकनीकों पर गहराई से चर्चा करता है, विशेष रूप से पैरामीटराइज्ड टेस्टिंग और मॉक इंटीग्रेशन पर ध्यान केंद्रित करता है। हम यह पता लगाएंगे कि ये तकनीकें आपके टेस्टिंग वर्कफ़्लो को कैसे बेहतर बना सकती हैं, जिससे अधिक मजबूत और रखरखाव योग्य कोड बन सकता है।
Pytest फिक्स्चर्स को समझना
एडवांस्ड विषयों में जाने से पहले, आइए संक्षेप में Pytest फिक्स्चर की मूल बातें दोहराते हैं। एक फिक्स्चर एक फ़ंक्शन है जो प्रत्येक टेस्ट फ़ंक्शन से पहले चलता है जिस पर इसे लागू किया जाता है। इसका उपयोग टेस्ट के लिए एक निश्चित आधार रेखा प्रदान करने, स्थिरता सुनिश्चित करने और बॉयलरप्लेट कोड को कम करने के लिए किया जाता है। फिक्स्चर निम्नलिखित जैसे कार्य कर सकते हैं:
- डेटाबेस कनेक्शन सेट करना
- अस्थायी फ़ाइलें या डायरेक्टरी बनाना
- विशिष्ट कॉन्फ़िगरेशन के साथ ऑब्जेक्ट्स को इनिशियलाइज़ करना
- API के साथ प्रमाणित करना
फिक्स्चर कोड की पुन: प्रयोज्यता को बढ़ावा देते हैं और आपके टेस्ट को अधिक पठनीय और रखरखाव योग्य बनाते हैं। उन्हें उनके जीवनकाल और संसाधन खपत को नियंत्रित करने के लिए विभिन्न स्कोप (फ़ंक्शन, मॉड्यूल, सेशन) पर परिभाषित किया जा सकता है।
बेसिक फिक्स्चर उदाहरण
यहाँ एक Pytest फिक्स्चर का एक सरल उदाहरण है जो एक अस्थायी डायरेक्टरी बनाता है:
import pytest
import tempfile
import os
@pytest.fixture
def temp_dir():
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir
इस फिक्स्चर को एक टेस्ट में उपयोग करने के लिए, बस इसे अपने टेस्ट फ़ंक्शन के तर्क (argument) के रूप में शामिल करें:
def test_create_file(temp_dir):
filepath = os.path.join(temp_dir, "test_file.txt")
with open(filepath, "w") as f:
f.write("Hello, world!")
assert os.path.exists(filepath)
Pytest के साथ पैरामीटराइज्ड टेस्टिंग
पैरामीटराइज्ड टेस्टिंग आपको एक ही टेस्ट फ़ंक्शन को इनपुट डेटा के विभिन्न सेटों के साथ कई बार चलाने की अनुमति देती है। यह अलग-अलग इनपुट और अपेक्षित आउटपुट वाले फ़ंक्शन के परीक्षण के लिए विशेष रूप से उपयोगी है। Pytest पैरामीटराइज्ड टेस्ट को लागू करने के लिए @pytest.mark.parametrize डेकोरेटर प्रदान करता है।
पैरामीटराइज्ड टेस्टिंग के लाभ
- कोड दोहराव कम करता है: कई लगभग समान टेस्ट फ़ंक्शन लिखने से बचें।
- टेस्ट कवरेज में सुधार: इनपुट मानों की एक विस्तृत श्रृंखला का आसानी से परीक्षण करें।
- टेस्ट पठनीयता बढ़ाता है: प्रत्येक टेस्ट केस के लिए इनपुट मान और अपेक्षित आउटपुट स्पष्ट रूप से परिभाषित करें।
बेसिक पैरामीटराइजेशन उदाहरण
मान लीजिए आपके पास एक फ़ंक्शन है जो दो संख्याओं को जोड़ता है:
def add(x, y):
return x + y
आप इस फ़ंक्शन को विभिन्न इनपुट मानों के साथ परीक्षण करने के लिए पैरामीटराइज्ड टेस्टिंग का उपयोग कर सकते हैं:
import pytest
@pytest.mark.parametrize("x, y, expected", [
(1, 2, 3),
(5, 5, 10),
(-1, 1, 0),
(0, 0, 0),
])
def test_add(x, y, expected):
assert add(x, y) == expected
इस उदाहरण में, @pytest.mark.parametrize डेकोरेटर चार टेस्ट केस परिभाषित करता है, जिनमें से प्रत्येक में x, y और अपेक्षित परिणाम के लिए अलग-अलग मान हैं। Pytest test_add फ़ंक्शन को चार बार चलाएगा, प्रत्येक पैरामीटर सेट के लिए एक बार।
एडवांस्ड पैरामीटराइजेशन तकनीकें
Pytest पैरामीटराइजेशन के लिए कई एडवांस्ड तकनीकें प्रदान करता है, जिनमें शामिल हैं:
- पैरामीटराइजेशन के साथ फिक्स्चर का उपयोग: प्रत्येक टेस्ट केस के लिए अलग-अलग सेटअप प्रदान करने के लिए फिक्स्चर को पैरामीटराइजेशन के साथ मिलाएं।
- टेस्ट केस के लिए आईडी: बेहतर रिपोर्टिंग और डीबगिंग के लिए टेस्ट केस को कस्टम आईडी असाइन करें।
- अप्रत्यक्ष पैरामीटराइजेशन: फिक्स्चर को पास किए गए तर्कों को पैरामीटराइज करें, जिससे डायनेमिक फिक्स्चर निर्माण की अनुमति मिलती है।
पैरामीटराइजेशन के साथ फिक्स्चर का उपयोग
यह आपको टेस्ट में पास किए गए पैरामीटर के आधार पर फिक्स्चर को गतिशील रूप से कॉन्फ़िगर करने की अनुमति देता है। कल्पना कीजिए कि आप एक ऐसे फ़ंक्शन का परीक्षण कर रहे हैं जो डेटाबेस के साथ इंटरैक्ट करता है। आप विभिन्न टेस्ट मामलों के लिए विभिन्न डेटाबेस कॉन्फ़िगरेशन (जैसे, विभिन्न कनेक्शन स्ट्रिंग्स) का उपयोग करना चाह सकते हैं।
import pytest
@pytest.fixture
def db_config(request):
if request.param == "prod":
return {"host": "prod.example.com", "port": 5432}
elif request.param == "test":
return {"host": "test.example.com", "port": 5433}
else:
raise ValueError("Invalid database environment")
@pytest.fixture
def db_connection(db_config):
# Simulate establishing a database connection
print(f"Connecting to database at {db_config['host']}:{db_config['port']}")
return f"Connection to {db_config['host']}"
@pytest.mark.parametrize("db_config", ["prod", "test"], indirect=True)
def test_database_interaction(db_connection):
# Your test logic here, using the db_connection fixture
print(f"Using connection: {db_connection}")
assert "Connection" in db_connection
इस उदाहरण में, db_config फिक्स्चर पैरामीटराइज्ड है। indirect=True तर्क Pytest को पैरामीटर ("prod" और "test") को db_config फिक्स्चर फ़ंक्शन में पास करने के लिए कहता है। db_config फिक्स्चर तब पैरामीटर मान के आधार पर विभिन्न डेटाबेस कॉन्फ़िगरेशन लौटाता है। db_connection फिक्स्चर डेटाबेस कनेक्शन स्थापित करने के लिए db_config फिक्स्चर का उपयोग करता है। अंत में, test_database_interaction फ़ंक्शन डेटाबेस के साथ इंटरैक्ट करने के लिए db_connection फिक्स्चर का उपयोग करता है।
टेस्ट केस के लिए आईडी
कस्टम आईडी टेस्ट रिपोर्ट में आपके टेस्ट केस के लिए अधिक वर्णनात्मक नाम प्रदान करते हैं, जिससे विफलताओं को पहचानना और डीबग करना आसान हो जाता है।
import pytest
@pytest.mark.parametrize(
"input_string, expected_output",
[
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
],
ids=["lowercase_hello", "lowercase_world", "empty_string"],
)
def test_uppercase(input_string, expected_output):
assert input_string.upper() == expected_output
आईडी के बिना, Pytest test_uppercase[0], test_uppercase[1], आदि जैसे सामान्य नाम उत्पन्न करेगा। आईडी के साथ, टेस्ट रिपोर्ट test_uppercase[lowercase_hello] जैसे अधिक सार्थक नाम प्रदर्शित करेगी।
अप्रत्यक्ष पैरामीटराइजेशन
अप्रत्यक्ष पैरामीटराइजेशन आपको सीधे टेस्ट फ़ंक्शन के बजाय, एक फिक्स्चर के इनपुट को पैरामीटराइज करने की अनुमति देता है। यह तब उपयोगी होता है जब आप पैरामीटर मान के आधार पर विभिन्न फिक्स्चर इंस्टेंस बनाना चाहते हैं।
import pytest
@pytest.fixture
def input_data(request):
if request.param == "valid":
return {"name": "John Doe", "email": "john.doe@example.com"}
elif request.param == "invalid":
return {"name": "", "email": "invalid-email"}
else:
raise ValueError("Invalid input data type")
def validate_data(data):
if not data["name"]:
return False, "Name cannot be empty"
if "@" not in data["email"]:
return False, "Invalid email address"
return True, "Valid data"
@pytest.mark.parametrize("input_data", ["valid", "invalid"], indirect=True)
def test_validate_data(input_data):
is_valid, message = validate_data(input_data)
if input_data == {"name": "John Doe", "email": "john.doe@example.com"}:
assert is_valid is True
assert message == "Valid data"
else:
assert is_valid is False
assert message in ["Name cannot be empty", "Invalid email address"]
इस उदाहरण में, input_data फिक्स्चर "valid" और "invalid" मानों के साथ पैरामीटराइज्ड है। indirect=True तर्क Pytest को इन मानों को input_data फिक्स्चर फ़ंक्शन में पास करने के लिए कहता है। input_data फिक्स्चर तब पैरामीटर मान के आधार पर विभिन्न डेटा डिक्शनरी लौटाता है। test_validate_data फ़ंक्शन फिर विभिन्न इनपुट डेटा के साथ validate_data फ़ंक्शन का परीक्षण करने के लिए input_data फिक्स्चर का उपयोग करता है।
Pytest के साथ मॉकिंग
मॉकिंग एक ऐसी तकनीक है जिसका उपयोग परीक्षण के दौरान वास्तविक निर्भरताओं को नियंत्रित विकल्पों (mocks) से बदलने के लिए किया जाता है। यह आपको परीक्षण किए जा रहे कोड को अलग करने और बाहरी सिस्टम, जैसे डेटाबेस, API, या फ़ाइल सिस्टम पर निर्भर रहने से बचने की अनुमति देता है।
मॉकिंग के लाभ
- कोड को अलग करना: बाहरी निर्भरताओं पर भरोसा किए बिना, कोड को अलगाव में परीक्षण करें।
- व्यवहार को नियंत्रित करना: निर्भरताओं के व्यवहार को परिभाषित करें, जैसे रिटर्न मान और अपवाद।
- टेस्ट को गति देना: धीमे या अविश्वसनीय बाहरी सिस्टम से बचें।
- एज केस का परीक्षण: त्रुटि स्थितियों और एज केस का अनुकरण करें जिन्हें वास्तविक वातावरण में पुन: उत्पन्न करना मुश्किल है।
unittest.mock लाइब्रेरी का उपयोग करना
Python मॉक्स बनाने के लिए unittest.mock लाइब्रेरी प्रदान करता है। Pytest unittest.mock के साथ सहजता से एकीकृत होता है, जिससे आपके टेस्ट में निर्भरताओं को मॉक करना आसान हो जाता है।
बेसिक मॉकिंग उदाहरण
मान लीजिए आपके पास एक फ़ंक्शन है जो बाहरी API से डेटा प्राप्त करता है:
import requests
def get_data_from_api(url):
response = requests.get(url)
response.raise_for_status() # Raise an exception for bad status codes
return response.json()
API से वास्तव में अनुरोध किए बिना इस फ़ंक्शन का परीक्षण करने के लिए, आप requests.get फ़ंक्शन को मॉक कर सकते हैं:
import pytest
import requests
from unittest.mock import patch
@patch("requests.get")
def test_get_data_from_api(mock_get):
# Configure the mock to return a specific response
mock_get.return_value.json.return_value = {"data": "test data"}
mock_get.return_value.status_code = 200
# Call the function being tested
data = get_data_from_api("https://example.com/api")
# Assert that the mock was called with the correct URL
mock_get.assert_called_once_with("https://example.com/api")
# Assert that the function returned the expected data
assert data == {"data": "test data"}
इस उदाहरण में, @patch("requests.get") डेकोरेटर requests.get फ़ंक्शन को एक मॉक ऑब्जेक्ट से बदल देता है। mock_get तर्क मॉक ऑब्जेक्ट है। हम फिर मॉक ऑब्जेक्ट को एक विशिष्ट प्रतिक्रिया लौटाने के लिए कॉन्फ़िगर कर सकते हैं और यह सुनिश्चित कर सकते हैं कि इसे सही URL के साथ कॉल किया गया था।
फिक्स्चर के साथ मॉकिंग
आप मॉक्स बनाने और प्रबंधित करने के लिए फिक्स्चर का भी उपयोग कर सकते हैं। यह कई टेस्टों में मॉक्स साझा करने या अधिक जटिल मॉक सेटअप बनाने के लिए उपयोगी हो सकता है।
import pytest
import requests
from unittest.mock import Mock
@pytest.fixture
def mock_api_get():
mock = Mock()
mock.return_value.json.return_value = {"data": "test data"}
mock.return_value.status_code = 200
return mock
@pytest.fixture
def patched_get(mock_api_get, monkeypatch):
monkeypatch.setattr(requests, "get", mock_api_get)
return mock_api_get
def test_get_data_from_api(patched_get):
# Call the function being tested
data = get_data_from_api("https://example.com/api")
# Assert that the mock was called with the correct URL
patched_get.assert_called_once_with("https://example.com/api")
# Assert that the function returned the expected data
assert data == {"data": "test data"}
यहां, mock_api_get एक मॉक बनाता है और उसे लौटाता है। फिर patched_get वास्तविक `requests.get` को मॉक से बदलने के लिए pytest फिक्स्चर monkeypatch का उपयोग करता है। यह अन्य टेस्ट को समान मॉक किए गए API एंडपॉइंट का उपयोग करने की अनुमति देता है।
एडवांस्ड मॉकिंग तकनीकें
Pytest और unittest.mock कई एडवांस्ड मॉकिंग तकनीकें प्रदान करते हैं, जिनमें शामिल हैं:
- साइड इफेक्ट्स: इनपुट तर्कों के आधार पर मॉक्स के लिए कस्टम व्यवहार परिभाषित करें।
- प्रॉपर्टी मॉकिंग: ऑब्जेक्ट्स की प्रॉपर्टीज को मॉक करें।
- कॉन्टेक्स्ट मैनेजर: अस्थायी प्रतिस्थापन के लिए कॉन्टेक्स्ट मैनेजर के भीतर मॉक्स का उपयोग करें।
साइड इफेक्ट्स
साइड इफेक्ट्स आपको प्राप्त होने वाले इनपुट तर्कों के आधार पर अपने मॉक्स के लिए कस्टम व्यवहार परिभाषित करने की अनुमति देते हैं। यह विभिन्न परिदृश्यों या त्रुटि स्थितियों का अनुकरण करने के लिए उपयोगी है।
import pytest
from unittest.mock import Mock
def test_side_effect():
mock = Mock()
mock.side_effect = [1, 2, 3]
assert mock() == 1
assert mock() == 2
assert mock() == 3
with pytest.raises(StopIteration):
mock()
यह मॉक क्रमिक कॉलों पर 1, 2, और 3 लौटाता है, फिर सूची समाप्त हो जाने पर एक `StopIteration` अपवाद उठाता है।
प्रॉपर्टी मॉकिंग
प्रॉपर्टी मॉकिंग आपको ऑब्जेक्ट्स पर प्रॉपर्टीज के व्यवहार को मॉक करने की अनुमति देती है। यह उस कोड का परीक्षण करने के लिए उपयोगी है जो तरीकों के बजाय ऑब्जेक्ट प्रॉपर्टीज पर निर्भर करता है।
import pytest
from unittest.mock import patch
class MyClass:
@property
def my_property(self):
return "original value"
def test_property_mocking():
obj = MyClass()
with patch.object(obj, "my_property", new_callable=pytest.PropertyMock) as mock_property:
mock_property.return_value = "mocked value"
assert obj.my_property == "mocked value"
यह उदाहरण MyClass ऑब्जेक्ट की my_property प्रॉपर्टी को मॉक करता है, जिससे आप टेस्ट के दौरान इसके रिटर्न मान को नियंत्रित कर सकते हैं।
कॉन्टेक्स्ट मैनेजर
कॉन्टेक्स्ट मैनेजर के भीतर मॉक्स का उपयोग आपको कोड के एक विशिष्ट ब्लॉक के लिए अस्थायी रूप से निर्भरताओं को बदलने की अनुमति देता है। यह उस कोड का परीक्षण करने के लिए उपयोगी है जो बाहरी सिस्टम या संसाधनों के साथ इंटरैक्ट करता है जिन्हें केवल सीमित समय के लिए मॉक किया जाना चाहिए।
import pytest
from unittest.mock import patch
def test_context_manager_mocking():
with patch("os.path.exists") as mock_exists:
mock_exists.return_value = True
assert os.path.exists("dummy_path") is True
# The mock is automatically reverted after the 'with' block
# Ensure the original function is restored, although we can't really assert
# the real `os.path.exists` function's behavior without a real path.
# The important thing is that the patch is gone after the context.
print("Mock has been removed")
पैरामीटराइजेशन और मॉकिंग का संयोजन
इन दो शक्तिशाली तकनीकों को और भी अधिक परिष्कृत और प्रभावी टेस्ट बनाने के लिए जोड़ा जा सकता है। आप विभिन्न मॉक कॉन्फ़िगरेशन के साथ विभिन्न परिदृश्यों का परीक्षण करने के लिए पैरामीटराइजेशन का उपयोग कर सकते हैं।
import pytest
import requests
from unittest.mock import patch
def get_user_data(user_id):
url = f"https://api.example.com/users/{user_id}"
response = requests.get(url)
response.raise_for_status()
return response.json()
@pytest.mark.parametrize(
"user_id, expected_data",
[
(1, {"id": 1, "name": "John Doe"}),
(2, {"id": 2, "name": "Jane Smith"}),
],
)
@patch("requests.get")
def test_get_user_data(mock_get, user_id, expected_data):
mock_get.return_value.json.return_value = expected_data
mock_get.return_value.status_code = 200
data = get_user_data(user_id)
assert data == expected_data
mock_get.assert_called_once_with(f"https://api.example.com/users/{user_id}")
इस उदाहरण में, test_get_user_data फ़ंक्शन को विभिन्न user_id और expected_data मानों के साथ पैरामीटराइज किया गया है। @patch डेकोरेटर requests.get फ़ंक्शन को मॉक करता है। Pytest टेस्ट फ़ंक्शन को दो बार चलाएगा, प्रत्येक पैरामीटर सेट के लिए एक बार, जिसमें मॉक को संबंधित expected_data लौटाने के लिए कॉन्फ़िगर किया गया है।
एडवांस्ड फिक्स्चर का उपयोग करने के लिए सर्वोत्तम अभ्यास
- फिक्स्चर को केंद्रित रखें: प्रत्येक फिक्स्चर का एक स्पष्ट और विशिष्ट उद्देश्य होना चाहिए।
- उपयुक्त स्कोप का उपयोग करें: संसाधन उपयोग को अनुकूलित करने के लिए उपयुक्त फिक्स्चर स्कोप (फ़ंक्शन, मॉड्यूल, सेशन) चुनें।
- फिक्स्चर को डॉक्यूमेंट करें: प्रत्येक फिक्स्चर के उद्देश्य और उपयोग को स्पष्ट रूप से डॉक्यूमेंट करें।
- अति-मॉकिंग से बचें: केवल उन निर्भरताओं को मॉक करें जो परीक्षण किए जा रहे कोड को अलग करने के लिए आवश्यक हैं।
- स्पष्ट दावे लिखें: सुनिश्चित करें कि आपके दावे स्पष्ट और विशिष्ट हैं, जो परीक्षण किए जा रहे कोड के अपेक्षित व्यवहार को सत्यापित करते हैं।
- टेस्ट-ड्रिवन डेवलपमेंट (TDD) पर विचार करें: विकास प्रक्रिया का मार्गदर्शन करने के लिए फिक्स्चर और मॉक्स का उपयोग करके, कोड लिखने से पहले अपने टेस्ट लिखें।
निष्कर्ष
Pytest की एडवांस्ड फिक्स्चर तकनीकें, जिनमें पैरामीटराइज्ड टेस्टिंग और मॉक इंटीग्रेशन शामिल हैं, मजबूत, कुशल और रखरखाव योग्य टेस्ट लिखने के लिए शक्तिशाली उपकरण प्रदान करती हैं। इन तकनीकों में महारत हासिल करके, आप अपने Python कोड की गुणवत्ता में काफी सुधार कर सकते हैं और अपने टेस्टिंग वर्कफ़्लो को सुव्यवस्थित कर सकते हैं। स्पष्ट, केंद्रित फिक्स्चर बनाने, उपयुक्त स्कोप का उपयोग करने और व्यापक दावे लिखने पर ध्यान केंद्रित करना याद रखें। अभ्यास के साथ, आप एक व्यापक और प्रभावी परीक्षण रणनीति बनाने के लिए Pytest के फिक्स्चर सिस्टम की पूरी क्षमता का लाभ उठाने में सक्षम होंगे।