मुख्य सामग्री पर जाएँ

यूनिट टेस्टिंग के लिए Pytest का उपयोग कैसे करें

जानें कि Pytest क्या है और इसका उपयोग किस लिए होता है, साथ ही इसे अन्य सॉफ्टवेयर टेस्टिंग तरीकों से तुलना करें।
· 13 मि॰ पढ़ें

AI के साथ खोजें

सॉफ्टवेयर विकास में टेस्टिंग एक बहुत बड़ा विषय है। किसी सॉफ्टवेयर उत्पाद के किसी अंतिम उपयोगकर्ता तक पहुंचने से पहले, उसके कई परीक्षणों से गुजरने की संभावना रहती है, जैसे इंटीग्रेशन टेस्ट, सिस्टम टेस्ट और एक्सेप्टेंस टेस्ट। ऐसे कड़े परीक्षणों के पीछे विचार यह है कि एप्लिकेशन का व्यवहार अंत-उपयोगकर्ता के दृष्टिकोण से अपेक्षा के अनुरूप काम कर रहा है। टेस्टिंग का यह तरीका behavior-driven development (BDD) के नाम से जाना जाता है।   

हाल के समय में, test-driven development (TDD) में डेवलपर्स के बीच काफी रुचि बढ़ी है। इसकी गहराई में जाना इस लेख के दायरे से बाहर हो सकता है, लेकिन सामान्य विचार यह है कि पारंपरिक विकास और परीक्षण की प्रक्रिया उलट दी जाती है — आप पहले अपने यूनिट टेस्ट लिखते हैं, और फिर कोड में बदलाव लागू करते हैं जब तक कि टेस्ट पास न हो जाएं। 

इस लेख में, हम यूनिट टेस्ट पर और खास तौर पर, एक लोकप्रिय Python टेस्टिंग फ्रेमवर्क Pytest का उपयोग करके उन्हें कैसे किया जाए, इस पर ध्यान देंगे। 

यूनिट टेस्ट क्या हैं?

यूनिट टेस्ट स्वचालित परीक्षण का एक रूप हैं — इसका सरल अर्थ है कि टेस्ट प्लान किसी मानव द्वारा मैन्युअली करने के बजाय एक स्क्रिप्ट द्वारा निष्पादित किया जाता है। ये सॉफ्टवेयर टेस्टिंग के पहले स्तर के रूप में काम करते हैं और आमतौर पर ऐसी फ़ंक्शनों के रूप में लिखे जाते हैं जो किसी सॉफ्टवेयर प्रोग्राम के भीतर विभिन्न कार्यात्मकताओं के व्यवहार को मान्य करते हैं। 

सॉफ्टवेयर परीक्षण के स्तर

सॉफ्टवेयर परीक्षण के स्तर

इन परीक्षणों के पीछे विचार यह है कि डेवलपर्स ऐसे सबसे छोटे कोड यूनिट को अलग कर सकें जो तार्किक रूप से स्वतंत्र हो और यह परख सकें कि वह अपेक्षा के अनुसार व्यवहार करता है। दूसरे शब्दों में, यूनिट टेस्ट मान्य करते हैं कि किसी सॉफ्टवेयर प्रोग्राम का एकल घटक वैसा ही काम कर रहा है जैसा डेवलपर्स ने इरादा किया था। 

आदर्श रूप से, ये टेस्ट काफी छोटे होने चाहिए — जितने छोटे, उतने बेहतर। छोटे टेस्ट बनाने का एक कारण यह है कि टेस्ट अधिक कुशल होंगे, क्योंकि छोटे यूनिट का परीक्षण करने से परीक्षण कोड बहुत तेज़ी से चल सकेगा। छोटे घटकों का परीक्षण करने का एक और कारण यह है कि इससे आपको यह समझने में अधिक अंतर्दृष्टि मिलती है कि सूक्ष्म स्तर का कोड मिलकर कैसे व्यवहार करता है। 

हमें यूनिट टेस्ट की आवश्यकता क्यों है? 

यूनिट टेस्ट करने की वैश्विक तर्कसंगतता यह है कि डेवलपर्स को यह सुनिश्चित करना चाहिए कि वे जो कोड लिखते हैं वह गुणवत्ता मानकों को पूरा करता है, इससे पहले कि उसे प्रोडक्शन वातावरण में जाने दिया जाए। हालांकि, कई अन्य कारक भी यूनिट टेस्ट की आवश्यकता में योगदान देते हैं। आइए उन कारणों में से कुछ को गहराई से समझते हैं।

संसाधनों की बचत 

यूनिट टेस्ट करने से डेवलपर्स को सॉफ्टवेयर निर्माण चरण के दौरान ही कोड बग पकड़ने में मदद मिलती है, जिससे वे विकास जीवनचक्र में आगे नहीं बढ़ पाते। इससे संसाधनों की बचत होती है क्योंकि डेवलपर्स को विकास के बाद के चरणों में बग ठीक करने की कीमत नहीं चुकानी पड़ती — इसका मतलब यह भी है कि अंतिम उपयोगकर्ताओं को बग्गी कोड से जूझने की संभावना कम होती है।  

अतिरिक्त डॉक्यूमेंटेशन 

यूनिट टेस्ट करने का एक और अच्छा औचित्य यह है कि वे आपके सॉफ्टवेयर उत्पाद के लिए जीवंत डॉक्यूमेंटेशन की एक अतिरिक्त परत के रूप में काम करते हैं। डेवलपर्स संपूर्ण सिस्टम की समग्र समझ के लिए सरलता से यूनिट टेस्ट का संदर्भ ले सकते हैं क्योंकि वे बताते हैं कि छोटे घटकों को कैसे व्यवहार करना चाहिए। 

आत्मविश्वास में बढ़ोतरी

कुछ फ़ंक्शनैलिटी लिखते समय अपने कोड में सूक्ष्म गलतियाँ करना बेहद आसान है। हालांकि, अधिकांश डेवलपर्स सहमत होंगे कि प्रोडक्शन वातावरण में जाने से पहले कोडबेस के ब्रेकिंग पॉइंट्स की पहचान करना कहीं बेहतर है: यूनिट टेस्ट डेवलपर्स को यह अवसर प्रदान करते हैं। 

यह कहना उचित है कि “यूनिट टेस्ट से कवर किया गया कोड, बिना टेस्ट कवरेज वाले कोड से अधिक विश्वसनीय माना जा सकता है।” भविष्य में कोड में आने वाले ब्रेक, बिना टेस्ट कवरेज वाले कोड की तुलना में, कहीं अधिक तेज़ी से पकड़े जा सकते हैं, जिससे समय और धन की बचत होती है। डेवलपर्स को अतिरिक्त डॉक्यूमेंटेशन का भी लाभ मिलता है, ताकि वे कोडबेस को जल्दी समझ सकें, और यह अतिरिक्त आत्मविश्वास भी रहता है कि यदि वे अपने कोड में गलती करते हैं, तो उसे कोई अंतिम उपयोगकर्ता नहीं, बल्कि यूनिट टेस्ट पकड़ लेंगे। 

Python टेस्टिंग फ्रेमवर्क

सालों में Python की लोकप्रियता बहुत बढ़ी है। Python की इस वृद्धि के हिस्से के रूप में, टेस्टिंग फ्रेमवर्क्स की संख्या भी बढ़ी है, जिससे आपके Python कोड का परीक्षण करने में मदद के लिए ढेरों टूल उपलब्ध हो गए हैं। प्रत्येक टूल के सूक्ष्म विवरण में जाना इस लेख के दायरे से बाहर है, लेकिन हम कुछ सबसे आम Python टेस्टिंग फ्रेमवर्क्स पर नज़र डालेंगे। 

unittest

Unittest यूनिट टेस्टिंग के लिए एक बिल्ट-इन Python फ्रेमवर्क है। इसे Java प्रोग्रामिंग भाषा के JUnit नामक यूनिट टेस्टिंग फ्रेमवर्क से प्रेरणा मिली है। चूंकि यह Python भाषा के साथ बॉक्स से बाहर आता है, इसलिए अतिरिक्त मॉड्यूल इंस्टॉल करने की आवश्यकता नहीं होती, और अधिकांश डेवलपर्स टेस्टिंग सीखना शुरू करने के लिए इसका उपयोग करते हैं।  

Pytest

Pytest संभवतः सबसे व्यापक रूप से उपयोग किया जाने वाला Python टेस्टिंग फ्रेमवर्क है — इसका मतलब है कि जब भी आप अटकें, आपको सहायता देने वाला बड़ा समुदाय मौजूद है। यह एक ओपन-सोर्स फ्रेमवर्क है जो डेवलपर्स को सरल, कॉम्पैक्ट टेस्ट सूट लिखने में सक्षम बनाता है और यूनिट टेस्टिंग, फंक्शनल टेस्टिंग और API टेस्टिंग का समर्थन करता है।

doctest

doctest फ्रेमवर्क सॉफ्टवेयर इंजीनियरिंग के दो मुख्य घटकों को जोड़ता है: डॉक्यूमेंटेशन और टेस्टिंग। यह सुविधा सुनिश्चित करती है कि सभी सॉफ्टवेयर प्रोग्राम अच्छी तरह से डॉक्यूमेंटेड और टेस्टेड हों ताकि वे अपेक्षित रूप से चलें। doctest Python की स्टैंडर्ड लाइब्रेरी के साथ आता है और सीखने में काफी सरल है। 

nose2

Nose2, nose अनुशासन का उत्तराधिकारी, मूल रूप से प्लगइन्स के साथ unittest है। इसकी Python के बिल्ट-इन यूनिट टेस्टिंग फ्रेमवर्क से करीबी नज़दीकी के कारण लोग अक्सर nose2 को “extended unit tests” या “unit tests with a plugin” कहते हैं। चूंकि यह व्यावहारिक रूप से unittest फ्रेमवर्क का एक विस्तार है, nose2 unittest से परिचित लोगों के लिए अपनाना बेहद आसान है।

Testify

Testify, यूनिट, इंटीग्रेशन और सिस्टम टेस्टिंग के लिए एक Python फ्रेमवर्क, लोकप्रिय रूप से उस फ्रेमवर्क के रूप में जाना जाता है जिसे unittest और nose को बदलने के लिए डिज़ाइन किया गया था। यह फ्रेमवर्क व्यापक प्लगइन्स से भरा हुआ है और यदि आप unittest से पहले से परिचित हैं तो इसकी लर्निंग कर्व काफी सहज है।

Hypothesis

Hypothesis डेवलपर्स को ऐसे यूनिट टेस्ट बनाने में सक्षम बनाता है जो लिखने में सरल और चलने में शक्तिशाली हों। चूंकि यह फ्रेमवर्क डेटा साइंस प्रोजेक्ट्स का समर्थन करने के लिए बनाया गया है, यह आपके द्वारा परिभाषित विशिष्ट गुणों के अनुरूप इनपुट के उदाहरण उत्पन्न करके ऐसे किनाराई मामलों को खोजने में मदद करता है जो टेस्ट बनाते समय तुरंत स्पष्ट नहीं होते।

हमारे ट्यूटोरियल के लिए, हम pytest का उपयोग करेंगे। अगले खंड में देखें कि आप उन अन्य विकल्पों के बजाय Pytest क्यों चुनना चाहेंगे जिनकी हमने सूची दी है। 

Pytest का उपयोग क्यों करें? 

इसके विशाल सहायक समुदाय से परे, pytest में कई ऐसे कारक हैं जो इसे Python में अपनी स्वचालित टेस्ट सूट चलाने के लिए बेहतरीन टूल्स में से एक बनाते हैं। Pytest का दर्शन और फ़ीचर्स सॉफ्टवेयर टेस्टिंग को डेवलपर के लिए बेहतर अनुभव बनाने के लिए तय किए गए हैं। Pytest के निर्माताओं ने यह लक्ष्य इस तरह हासिल किया कि आम कार्यों को करने के लिए आवश्यक कोड की मात्रा को काफी हद तक कम कर दिया और विस्तृत कमांड्स और प्लग-इन्स के साथ उन्नत कार्यों को करना संभव बना दिया। 

Pytest उपयोग करने के कुछ और कारण निम्नलिखित हैं: 

सीखने में आसान 

Pytest सीखना बेहद आसान है: यदि आप Python के assert कीवर्ड का काम समझते हैं, तो आप पहले से ही फ्रेमवर्क में महारत हासिल करने की राह पर हैं। pytest के साथ लिखे गए टेस्ट वे Python फ़ंक्शन होते हैं जिनके नाम के आगे “test_” जोड़ा या अंत में “_test” जोड़ा जाता है — हालांकि आप कई टेस्ट को समूहित करने के लिए एक क्लास का उपयोग कर सकते हैं। समग्र रूप से, pytest की लर्निंग कर्व unittest जैसी के मुकाबले कहीं अधिक समतल है, क्योंकि आपको कोई नए कॉन्स्ट्रक्ट सीखने की आवश्यकता नहीं होती। 

टेस्ट फ़िल्टरिंग

आप हर बार निष्पादन के साथ अपने सभी टेस्ट नहीं चलाना चाहेंगे — जैसे-जैसे आपका टेस्ट सूट बढ़ता है, यह स्थिति हो सकती है। कभी-कभी, आप विकास के दौरान तेज़ फीडबैक पाने के लिए किसी नई सुविधा पर कुछ टेस्ट को अलग-थलग चलाना चाहेंगे, फिर जब आपको विश्वास हो जाए कि सब कुछ योजना के अनुसार काम कर रहा है, तो पूरा सूट चलाएं। Pytest में टेस्ट अलग करने के तीन तरीके हैं: 1) नाम-आधारित फ़िल्टरिंग, जो pytest को केवल उन्हीं टेस्ट चलाने के लिए कहती है जिनके नाम दिए गए पैटर्न से मेल खाते हैं 2) डायरेक्टरी स्कोपिंग, जो एक डिफ़ॉल्ट सेटिंग है जो pytest को केवल वर्तमान डायरेक्टरी में या उसके तहत मौजूद टेस्ट चलाने को कहती है और 3) टेस्ट कैटेगरीकरण, जो आपको उन श्रेणियों को परिभाषित करने देता है जिन्हें pytest को शामिल या बहिष्कृत करना चाहिए। 

पैरामीटराइज़ेशन

Pytest में parametrize नामक एक बिल्ट-इन डेकोरेटर है जो किसी टेस्ट फ़ंक्शन के आर्ग्युमेंट्स का पैरामीटराइज़ेशन सक्षम करता है। इसलिए, यदि जिन फ़ंक्शनों का आप परीक्षण कर रहे हैं वे डेटा प्रोसेस करते हैं या कोई सामान्य रूपांतरण करते हैं, तो आपको कई समान टेस्ट लिखने की आवश्यकता नहीं है। हम लेख में आगे पैरामीटराइज़ेशन पर और बात करेंगे। 

हम यहीं रुकेंगे, लेकिन यह सूची कि pytest आपके स्वचालित टेस्ट सूट के लिए क्यों बेहतरीन टूलिंग विकल्प है, और भी लंबी है। 

Pytest बनाम unittest

ऊपर बताए गए सभी कारणों के बावजूद, कोई अब भी सिर्फ इस तथ्य के कारण pytest का उपयोग करने पर आपत्ति जता सकता है कि यह थर्ड-पार्टी फ्रेमवर्क है — “जब एक फ्रेमवर्क पहले से बिल्ट-इन है, तो कोई अतिरिक्त फ्रेमवर्क इंस्टॉल करने का क्या मतलब?” यह एक अच्छी दलील है, लेकिन उस बहस में अपनी स्थिति स्पष्ट करने के लिए, हम आपको कुछ बातों पर विचार करने के लिए देंगे। 

नोट: यदि आप पहले से ही pytest के पक्ष में हैं, तो अगले खंड पर जाएँ जहाँ हम फ्रेमवर्क का उपयोग करना सीखेंगे। 

कम बाइलरप्लेट

Unittest डेवलपर्स से TestCase मॉड्यूल से व्युत्पन्न क्लासेस बनाने और फिर टेस्ट केसों को क्लास में मेथड्स के रूप में परिभाषित करने की आवश्यकता रखता है। 

"""
An example test case with unittest.
See: https://docs.python.org/3/library/unittest.html
"""
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

दूसरी ओर, Pytest आपसे केवल “test_” से शुरू होने वाला एक फ़ंक्शन परिभाषित करने और उसके अंदर assert शर्तों का उपयोग करने की अपेक्षा रखता है। 

"""
An example test case with pytest.
See: https://docs.pytest.org/en/6.2.x/index.html
"""
# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

आवश्यक कोड की मात्रा में फर्क पर ध्यान दें; unittest में पर्याप्त मात्रा में बाइलरप्लेट कोड की आवश्यकता होती है, जो किसी भी टेस्ट के लिए न्यूनतम आवश्यकता के रूप में काम करता है। इसका मतलब है कि आपके द्वारा वही कोड बार-बार लिखे जाने की संभावना बहुत अधिक है। दूसरी ओर, pytest में समृद्ध इनबिल्ट फीचर्स हैं जो टेस्ट केस लिखने के लिए आवश्यक कोड की मात्रा कम करके इस वर्कफ़्लो को सरल बनाते हैं। 

आउटपुट

प्रत्येक फ्रेमवर्क द्वारा दिए गए आउटपुट एक-दूसरे से काफी अलग हैं। यहाँ एक उदाहरण pytest निष्पादन है: 

"""
See: https://docs.pytest.org/en/6.2.x/index.html
"""
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================

ऊपर दिया गया टेस्ट केस विफल हुआ, लेकिन देखें कि असफलता का विश्लेषण कितना विस्तृत है। इससे डेवलपर्स के लिए अपने कोड में बग कहाँ हैं, यह पहचानना आसान हो जाता है, जो डिबगिंग के समय बेहद मददगार है। बोनस के रूप में, टेस्ट सूट के लिए एक समग्र स्टेटस रिपोर्ट भी मिलती है, जो बताती है कि कितने टेस्ट विफल हुए और इसमें कितना समय लगा। 

अब unittest के साथ विफल हुए टेस्ट केस का एक उदाहरण देखते हैं।

import unittest

def square(n):
    return n*n

def cube(n):
    return n*n*n

class TestCase(unittest.TestCase):
    def test_square(self):
        self.asserEquals(square(4), 16)

    def test_cube(self):
        self.assertEquals(cube(4), 16)

जब हम स्क्रिप्ट चलाते हैं, तो हमें निम्न आउटपुट मिलता है: 

---------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (errors=1)

ऊपर दिया आउटपुट बताता है कि 0.001s में दो टेस्ट चलाए गए और एक विफल हुआ, लेकिन इससे अधिक जानकारी नहीं मिलती। अंततः, pytest कहीं अधिक सूचनात्मक फीडबैक देता है, जो डिबगिंग के समय काम आता है। 

कुल मिलाकर, pytest और unittest दोनों Python में स्वचालित टेस्टिंग के लिए बेहतरीन टूल हैं। कई Python डेवलपर्स इसकी कॉम्पैक्टनेस और दक्षता के कारण pytest को उसके समकक्षों पर प्राथमिकता दे सकते हैं। इसे अपनाना भी बेहद आसान है और आप एक प्रभावी टेस्ट सूट बनाने के लिए कई फीचर्स का इस्तेमाल कर सकते हैं। 

अब इस लेख के मुख्य भाग की ओर चलते हैं। हमने चर्चा की कि यूनिट टेस्टिंग क्या है और Python में स्वचालित टेस्टिंग के लिए pytest क्यों एक बेहतरीन टूल है। अब देखते हैं कि इस टूल का उपयोग कैसे करें। 

Pytest ट्यूटोरियल

आइए देखें कि यह Python टेस्टिंग फ्रेमवर्क, जिसके बारे में हम बात कर रहे थे, कैसे काम करता है। 

पहला कदम पैकेज को इंस्टॉल करना है, जिसे एक साधारण pip कमांड से किया जा सकता है। 

नोट: pytest के निर्माताओं की सिफारिश है कि आप विकास के लिए venv और अपने एप्लिकेशन, निर्भरताएँ और pytest स्वयं इंस्टॉल करने के लिए pip का उपयोग करें। 

pip install -U pytest

अगला कदम यह जाँचना है कि फ्रेमवर्क इंस्टॉल हो चुका है या नहीं, इसके लिए निम्न कमांड का उपयोग करें: 

>>>> pytest --version

pytest 7.1.2

सब कुछ इंस्टॉल हो गया। अब आप कुछ टेस्ट चलाना शुरू करने के लिए तैयार हैं। 

एक सरल टेस्ट बनाना

Pytest के साथ एक टेस्ट बनाना सरल है। इस फ़ंक्शनैलिटी को दिखाने के लिए, हमने calcualte_age.py नाम की एक स्क्रिप्ट बनाई है। इस स्क्रिप्ट में केवल एक फ़ंक्शन है, get_age, जो जन्मतिथि दिए जाने पर किसी उपयोगकर्ता की आयु की गणना करने के लिए ज़िम्मेदार है। 

import datetime

def get_age(yyyy:int, mm:int, dd:int) -> int:
    dob = datetime.date(yyyy, mm, dd)
    today = datetime.date.today()
    age = round((today - dob).days / 365.25)
    return age

Pytest उन सभी Python फ़ाइलों को निष्पादित करेगा जिनके नाम की शुरुआत test_ से होती है या नाम के अंत में _test होता है। अधिक विशिष्ट होने के लिए, pytest टेस्ट डिस्कवरी के लिए निम्नलिखित मान्यताओं का पालन करता है [स्रोत: डॉक्यूमेंटेशन]: 

  • यदि कोई आर्ग्युमेंट निर्दिष्ट नहीं हैं, तो pytest कलेक्शन testpaths में शुरू होगा यदि वे कॉन्फ़िगर हैं: testpaths उन डायरेक्टरीज़ की सूची है जिन्हें pytest तब खोजेगा जब कोई विशिष्ट डायरेक्टरी, फ़ाइलें, या टेस्ट आईडी प्रदान नहीं किए गए हों। 
  • फिर pytest डायरेक्टरीज़ में रिकर्स करेगा, जब तक कि आपने norecursedirs सेट करके उसे ऐसा न करने को नहीं कहा हो; यह ऐसी फ़ाइलें खोज रहा होता है जो test_*.py से शुरू होती हैं या *_test.py पर समाप्त होती हैं
  • उन फ़ाइलों में, pytest निम्न क्रम में टेस्ट आइटम एकत्र करेगा:
    • क्लास के बाहर “test_” प्रीफिक्स वाले फ़ंक्शन या मेथड
    • Test प्रीफिक्स वाली उन क्लासेस के अंदर “test_” प्रीफिक्स वाले फ़ंक्शन या मेथड, जिनमें __init__ मेथड नहीं है। 

हमने कोई आर्ग्युमेंट निर्दिष्ट नहीं किए हैं, लेकिन हमने उसी डायरेक्टरी में test_calculate_age.py नाम की एक और स्क्रिप्ट बनाई है: अतः जब डायरेक्टरीज़ को रिकर्स किया जाएगा तो यह टेस्ट खोज लिया जाएगा। इस स्क्रिप्ट में, हमारे पास एकल टेस्ट test_get_age है, ताकि हम मान्य कर सकें कि हमारा फ़ंक्शन अपेक्षा के अनुरूप काम कर रहा है। 

नोट: आप अपने टेस्ट को अपने एप्लिकेशन से बाहर किसी अतिरिक्त डायरेक्टरी में रखने का निर्णय ले सकते हैं, जो एक अच्छा विचार है यदि आपके पास कई फंक्शनल टेस्ट हैं या आप किसी अन्य कारण से टेस्टिंग कोड और एप्लिकेशन कोड को अलग रखना चाहते हैं। 

from calculate_age import get_age

def test_get_age():
    # Given.
    yyyy, mm, dd = map(int, "1996/07/11".split(""))   
    # When.
    age = get_age(yyyy, mm, dd)
    # Then.
    assert age == 26 

टेस्ट को निष्पादित करने के लिए, कमांड प्रॉम्प्ट से निम्न कमांड चलाएँ: 

py -m pytest 

एक सफल pytest रन का आउटपुट

एक सफल pytest रन का आउटपुट।

और बस इतना ही। 

लेकिन क्या होगा यदि हमें अपने टेस्ट पास कराने के लिए कुछ डेटा प्रदान करने की आवश्यकता हो? मिलिए pytest fixtures से। 

Pytest Fixtures 

Pytest की अमूल्य fixtures सुविधा डेवलपर्स को टेस्ट में डेटा फीड करने की अनुमति देती है। ये मूल रूप से ऐसे फ़ंक्शन होते हैं जो प्रत्येक टेस्ट फ़ंक्शन से पहले चलते हैं, ताकि हमारे टेस्ट की स्थिति का प्रबंधन कर सकें। उदाहरण के लिए, मान लीजिए हमारे पास कई टेस्ट हैं जो सभी एक ही डेटा का उपयोग करते हैं, तो हम एक फ़िक्स्चर का उपयोग करके एक एकल फ़ंक्शन के माध्यम से दोहराए जाने वाले डेटा को खींच सकते हैं। 

import pytest
from sklearn.model_selection import train_test_split
from fraud_detection_model.config.core import config
from fraud_detection_model.processing.data_manager import (

load_datasets, 

load_interim_data

) 


@pytest.fixture(scope="session")
def pipeline_inputs():
    # import the training dataset
    dataset = load_datasets(transaction=config.app_config.train_transaction, identity=config.app_config.train_identity, )
    # divide train and test
    X_train, X_test, y_train, y_test = train_test_split(

dataset[config.model_config.all_features], dataset[config.model_config.target],
test_size=config.model_config.test_size,
stratify=dataset[config.model_config.target],
random_state=config.model_config.random_state)
    return X_train, X_test, y_train, y_test

ऊपर दिए गए कोड में, हमने pytest.fixture डेकोरेटर का उपयोग करके एक फ़िक्स्चर बनाया है। ध्यान दें कि स्कोप "session" पर सेट है जिससे pytest को सूचित किया जा सके कि हम चाहते हैं कि टेस्ट सेशन के अंत में फ़िक्स्चर नष्ट हो जाए।

नोट: हमारे फ़िक्स्चर conftest.py में संग्रहीत हैं।

कोड किसी अन्य मॉड्यूल से आयात किए गए एक फ़ंक्शन का उपयोग करता है, जो दो csv फ़ाइलों को मेमोरी में लोड करता है और उन्हें एकल डेटासेट में मर्ज करता है। उसके बाद, डेटासेट को प्रशिक्षण और परीक्षण सेट में विभाजित किया जाता है और फ़ंक्शन से वापस कर दिया जाता है। 

इस फ़िक्स्चर का उपयोग अपने टेस्ट में करने के लिए, हमें इसे अपने टेस्ट फ़ंक्शन में एक पैरामीटर के रूप में पास करना होगा। इस उदाहरण में, हम अपने test_pipeline.py टेस्ट स्क्रिप्ट में अपने फ़िक्स्चर का इस प्रकार उपयोग करते हैं: 

import pandas as pd
from pandas.api.types import is_categorical_dtype, is_object_dtype
from fraud_detection_model import pipeline
from fraud_detection_model.config.core import configfrom fraud_detection_model.processing.validation import validate_inputs 


def test_pipeline_most_frequent_imputer(pipeline_inputs):
    # Given
    X_train, _, _, _ = pipeline_inputs assert all( x in X_train.loc[:, X_train.isnull().any()].columns for x in config.model_config.impute_most_freq_cols )
    # When
    X_transformed = pipeline.fraud_detection_pipe[:1].fit_transform(X_train[:50])
    # Then
    assert all( x not in X_transformed.loc[:, X_transformed.isnull().any()].columns for x in config.model_config.impute_most_freq_cols )
def test_pipeline_aggregate_categorical(pipeline_inputs):
    # Given
    X_train, _, _, _ = pipeline_inputs  

    assert X_train["R_emaildomain"].nunique() == 60
    # When
    X_transformed = pipeline.fraud_detection_pipe[:2].fit_transform(X_train[:50])
    # Then
    assert X_transformed["R_emaildomain"].nunique() == 2

कोड क्या कर रहा है, इसकी बहुत चिंता न करें। सबसे महत्वपूर्ण बात जो हम उजागर करना चाहते हैं, वह यह है कि हमने एक फ़िक्स्चर बनाया है जिसे हम पैरामीटर के रूप में पास करते हैं ताकि टेस्ट में डेटा खींचा जा सके, और इस तरह हमने अनावश्यक कोड लिखने की आवश्यकता को काफी हद तक कम कर दिया है। 

हालाँकि, ऐसी स्थितियाँ भी होती हैं जहाँ फ़िक्स्चर का उपयोग अधिक हो सकता है। उदाहरण के लिए, यदि आपके टेस्ट में खींचा गया डेटा प्रत्येक टेस्ट केस में फिर से प्रोसेस किया जाना चाहिए — यह व्यावहारिक रूप से आपके कोड में कई सादे ऑब्जेक्ट बिखेरने के बराबर है। यह सब कहते हुए, फ़िक्स्चर आपके टेस्ट सूट में एक अहम भूमिका निभाने की संभावना रखते हैं, लेकिन उन्हें कब इस्तेमाल करना है और कब नहीं, यह समझना अभ्यास और गहन सोच से आएगा।  

Pytest parametrize

जब कई टेस्ट में इनपुट समान हों तो फ़िक्स्चर बेहतरीन हैं। पर यदि आप किसी एक फ़ंक्शन को इनपुट में हल्के बदलावों के साथ टेस्ट करना चाहते हैं तो? एक समाधान है विभिन्न केसों के साथ कई अलग-अलग टेस्ट लिखना। 

def test_eval_addition():
    assert eval("2 + 2") == 4

def test_eval_subtraction():
    assert eval("2 - 2") == 0

def test_eval_multiplication():
    assert eval("2 * 2") == 4

def test_eval_division():
    assert eval("2 / 2") == 1.0

यह समाधान निश्चित रूप से काम करता है, लेकिन यह सबसे कुशल नहीं है: शुरुआत के लिए, यहाँ बहुत सारा बाइलरप्लेट कोड है। इससे बेहतर समाधान है pytest.mark.parametrize() डेकोरेटर का उपयोग करना, जो किसी टेस्ट फ़ंक्शन के आर्ग्युमेंट्स के पैरामीटराइज़ेशन को सक्षम करता है। इससे हम एक ही टेस्ट परिभाषा तय कर पाएँगे, और फिर pytest हमारे द्वारा निर्दिष्ट विभिन्न पैरामीटर्स पर टेस्ट चला देगा। 

यदि हम पैरामीटराइज़ेशन का उपयोग करें, तो ऊपर वाले कोड को हम इस तरह लिखेंगे: 

import pytest

@pytest.mark.parametrize("test_input, expected_output", [("2+2", 4), ("2-2", 0), ("2*2", 4), ("2/2", 1.0)])
def test_eval(test_input, expected_output):
    assert eval(test_input) == expected_output

@parametrize डेकोरेटर test_eval फ़ंक्शन के लिए चार अलग-अलग टेस्ट इनपुट और अपेक्षित मान परिभाषित करता है — यानी यह फ़ंक्शन चार बार चलेगा और हर बार एक-एक सेट का उपयोग करेगा। 

पैरामीटराइज़ टेस्ट के लिए pytest का आउटपुट

पैरामीटराइज़ टेस्ट के लिए pytest का आउटपुट

इस लेख में, हमने निम्न विषयों को कवर किया: 

  • यूनिट टेस्ट क्या होते हैं
  • हमें यूनिट टेस्ट की आवश्यकता क्यों है
  • Python में अलग-अलग टेस्टिंग फ्रेमवर्क
  • pytest इतना उपयोगी क्यों है 
  • pytest का उपयोग कैसे करें और इसकी दो प्रमुख विशेषताएँ (fixtures और पैरामीटराइज़ेशन)

अब आप pytest फ्रेमवर्क का उपयोग करके अपने स्वयं के टेस्ट लिखना शुरू करने के लिए पर्याप्त जानते हैं। हम आपको ऐसा करने के लिए प्रोत्साहित करते हैं ताकि आपने इस लेख में जो कुछ भी सीखा है, वह पुख्ता हो जाए। आपको हमारे कोर्स unit testing for data science in python को भी देखना चाहिए, जिसमें उदाहरणों के साथ अधिक विस्तृत व्याख्या दी गई है। 

विषय