pytest इंस्टॉल करना और आपका पहला टेस्ट
हाथ से जाँचना क्यों काम नहीं आता, खाली assert लगभग कुछ क्यों नहीं बताता, और pytest इंस्टॉल करके पहली टेस्ट फ़ाइल लिखना और चलाना। डॉट, सारांश लाइन, -q, -v और exit code पढ़ने के साथ।
- 1समस्या
- 2समझें
- 3उदाहरण
- 4अनुमान
- 5स्वयं करें
- 6चुनौती
वह समस्या जिसे हम हल कर रहे हैं
यह रहा एक छोटा-सा फ़ंक्शन, और वह तरीका जिससे हम में से ज़्यादातर लोग ऐसे फ़ंक्शन को जाँचते हैं — उसे चलाओ और देखो कि क्या निकलता है:
def discounted(price, percent):
return price - price * percent / 100
print(discounted(200, 10))
print(discounted(80, 25))
print(discounted(99, 0))180.0
60.0
99.0आप तीनों संख्याएँ पढ़ते हैं, मन में हिसाब लगाते हैं, और तय करते हैं कि वे सही हैं। यह भी एक टेस्ट है। बस यह मैन्युअल टेस्ट है, और इसमें दो समस्याएँ हैं।
पहली यह कि यह बड़े पैमाने पर नहीं चलता। तीन संख्याएँ आँख से जाँचना आसान है। लेकिन तीस फ़ंक्शन, हर एक के पाँच केस, और हर बदलाव के बाद दोबारा जाँच — यह आसान नहीं है, और कोई ऐसा करता भी नहीं।
दूसरी समस्या इससे भी बुरी है। एक महीने बाद कोई फ़ंक्शन को थोड़ा "साफ़-सुथरा" कर देता है:
def discounted(price, percent):
return price * (1 - percent // 100)$ python -c "from prices import discounted; print(discounted(200, 10))"
200देखने में यह ठीक लगता है, और बिना किसी एरर के चलता भी है। लेकिन // पूर्णांक भाग (whole-number division) है, इसलिए 10 // 100 का नतीजा 0 है, और हर डिस्काउंट चुपचाप गायब हो गया है। जो कोड पहले काम करता था और अब नहीं करता, उसे regression कहते हैं, और regression ठीक वही चीज़ है जो मैन्युअल जाँच से छूट जाती है — क्योंकि पुरानी संख्याओं को दोबारा जाँचने कोई वापस नहीं जाता।
इसका इलाज है अपेक्षित जवाबों को कोड में लिख देना, ताकि मशीन उन्हें हर बार जाँच सके। पायथन में इसके लिए पहले से एक स्टेटमेंट मौजूद है, assert। जाँचों को check_prices.py नाम की फ़ाइल में रखिए, prices.py के बगल में:
from prices import discounted
assert discounted(200, 10) == 180.0
assert discounted(80, 25) == 60.0
assert discounted(99, 0) == 99
print("all good")मूल फ़ंक्शन के साथ:
$ python check_prices.py
all good"साफ़-सुथरे" किए गए फ़ंक्शन के साथ:
$ python check_prices.py
Traceback (most recent call last):
File "/home/you/shop/check_prices.py", line 3, in <module>
assert discounted(200, 10) == 180.0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionErrorregression पकड़ में आ गया, यह एक प्रगति है। लेकिन देखिए आपको बताया क्या गया: AssertionError, और कुछ नहीं। यह नहीं कि discounted(200, 10) ने असल में क्या लौटाया। और स्क्रिप्ट पहली विफलता पर ही रुक गई, इसलिए आपको पता ही नहीं कि बाकी दो जाँचें पास होती हैं या नहीं।
यह अध्याय वह टूल इंस्टॉल करता है जो इन दोनों समस्याओं को ठीक करता है: pytest।
इस अध्याय के अंत में आप कर पाएंगे
- समझाना कि ऑटोमेटेड टेस्ट वे regressions क्यों पकड़ लेते हैं जो मैन्युअल जाँच से छूट जाते हैं
uvयाpipसे pytest इंस्टॉल करना, औरpytest --versionसे इसकी पुष्टि करना- एक ऐसी टेस्ट फ़ाइल लिखना जिसे pytest अपने आप ढूँढ ले, और उसे चलाना
- डॉट्स और सारांश लाइन पढ़ना, और
-qव-vके बीच बदलना - बताना कि exit code
0और1का क्या मतलब है, और यह क्यों मायने रखता है
ज़रूरी शर्तें: टेस्ट लिखने से पहले। इसके अलावा आपको Python 3 चाहिए और फ़ंक्शन लिखना आना चाहिए।
टेस्ट लिखने से पहले
पिछला अध्याय इस बारे में था कि क्या टेस्ट करना है। यह अध्याय उन ठोस चीज़ों के बारे में है जिनका टेस्ट कोड की पहली लाइन चलने से पहले मौजूद होना ज़रूरी है। ऐसी चार चीज़ें हैं, और इनमें से हर एक के न होने पर अपनी अलग, उलझाने वाली एरर आती है — इसलिए इन्हें क्रम से जाँचना फ़ायदेमंद है।
1. एक प्रोजेक्ट फ़ोल्डर, एक environment। इस अध्याय की हर चीज़ एक ही फ़ोल्डर में रहती है:
shop/
├── .venv/ the project's own Python environment
├── pyproject.toml created by uv; lists pytest as a dev dependency
├── prices.py the code under test
└── test_prices.py the testsअपना अलग environment क्यों? क्योंकि pytest को उसी पायथन में इंस्टॉल होना चाहिए जो आपका कोड import करेगा। अगर pytest एक पायथन में है और आपके प्रोजेक्ट के पैकेज दूसरे में, तो ऐसी एरर आती हैं जो कोड के गायब होने जैसी दिखती हैं, जबकि असली समस्या टूल का गायब होना होती है। हर प्रोजेक्ट का अपना .venv यह अंदाज़ेबाज़ी ख़त्म कर देता है।
2. उस environment में pytest इंस्टॉल हो — यह अगला सेक्शन करता है।
3. कोड import हो सके। टेस्ट फ़ाइल साधारण पायथन है: उसकी पहली लाइन from prices import discounted होगी। इसलिए कोई भी टेस्ट लिखने से पहले जाँच लीजिए कि ठीक यही import प्रोजेक्ट फ़ोल्डर से काम करता है:
$ python -c "from prices import discounted; print(discounted(200, 10))"
180.0अगर यह लाइन फ़ेल होती है, तो pytest भी उसी तरह फ़ेल होगा — बस एक लंबी रिपोर्ट में लिपटा हुआ। इसे अलग से जाँचने में पाँच सेकंड लगते हैं, और इससे "मेरा कोड मिल नहीं रहा" और "मेरा कोड ग़लत है" अलग-अलग हो जाते हैं। इसका यह भी मतलब है कि मॉड्यूल का नाम एक वैध पायथन नाम होना चाहिए: prices.py import हो सकता है, my-prices.py और 2prices.py नहीं।
4. वादा, केसों के रूप में लिखा हुआ। टेस्ट टाइप करने से पहले लिख लीजिए कि discounted क्या वादा करता है, हर केस के लिए एक पंक्ति:
| केस | इनपुट | अपेक्षित | | --- | --- | --- | | सामान्य डिस्काउंट | discounted(200, 10) | 180.0 | | एक दूसरा, अलग डिस्काउंट | discounted(80, 25) | 60.0 | | किनारे का केस: कोई डिस्काउंट नहीं | discounted(99, 0) | 99 |
और उतने ही सोच-समझकर यह भी कि सूची में क्या नहीं है। ऋणात्मक प्रतिशत, या 100 से ज़्यादा: फ़ंक्शन अभी इनके बारे में कोई वादा नहीं करता, और इनका टेस्ट बस उसी को जमा देगा जो वह आज संयोग से करता है। (ग़लत इनपुट को एरर के साथ अस्वीकार करना अपने आप में एक अलग वादा है — अध्याय चार।) पायथन का अपना गणित भी नहीं: आप अपना फ़ॉर्मूला टेस्ट कर रहे हैं, यह नहीं कि * गुणा करता है।
एक के बजाय दो सामान्य केस क्यों? तीसरी पंक्ति को अध्याय की शुरुआत वाले "साफ़-सुथरे" फ़ंक्शन से मिलाइए: 99 * (1 - 0 // 100) अब भी 99 है। शून्य प्रतिशत वाला केस बग के बावजूद पास हो जाता है। सिर्फ़ किनारे के केसों से बनी योजना ठीक उसी ग़लती को चूक सकती है जो मायने रखती है, और सिर्फ़ एक सामान्य केस वाली योजना किनारों को चूक जाती है। हर पंक्ति कुछ ऐसा पकड़ने के लिए है जो बाकी नहीं पकड़तीं।
अध्याय का बाकी हिस्सा इस टेबल को एक टेस्ट फ़ाइल में बदलता है।
pytest इंस्टॉल करना
pytest पायथन का हिस्सा नहीं है; यह एक पैकेज है जिसे आप अपने प्रोजेक्ट में इंस्टॉल करते हैं। यह कोड को डेवलप करने का टूल है, ऐसी चीज़ नहीं जिसकी प्रोग्राम को चलते समय ज़रूरत हो — इसलिए यह एक development dependency के रूप में जोड़ा जाता है।
अगर आपका प्रोजेक्ट uv इस्तेमाल करता है, तो प्रोजेक्ट फ़ोल्डर में यह चलाइए:
$ uv add --dev pytest
Resolved 7 packages in 59ms
Installed 5 packages in 18ms
+ iniconfig==2.3.1
+ packaging==26.3
+ pluggy==1.6.0
+ pygments==2.21.0
+ pytest==9.1.1uv इसे pyproject.toml में एक dev ग्रुप के नीचे दर्ज कर देता है, ताकि बाद में जो भी प्रोजेक्ट सेट-अप करे, उसे वही टूल मिले:
[dependency-groups]
dev = [
"pytest>=9.1.1",
]अगर आप virtual environment के अंदर सादा pip इस्तेमाल करते हैं, तो environment को activate कीजिए और चलाइए:
$ python -m pip install pytestpip डाउनलोड की प्रगति दिखाता है और आख़िर में यह लिखता है:
Successfully installed iniconfig-2.3.1 packaging-26.3 pluggy-1.6.0 pygments-2.21.0 pytest-9.1.1सिर्फ़ pip के बजाय python -m pip लिखने से यह पक्का हो जाता है कि पैकेज उसी पायथन में पहुँचे जिसे आप चलाएँगे — "मैंने इंस्टॉल तो किया था पर वह मिल नहीं रहा" वाली आम परेशानी की जड़ अक्सर यही होती है।
अब जाँचिए कि यह काम कर गया:
$ pytest --version
pytest 9.1.1आगे बढ़ने से पहले एक बात। uv में, कमांड के आगे uv run लगाने पर वह प्रोजेक्ट के environment के अंदर चलती है — यानी uv run pytest --version। activate किए गए virtual environment में सादा pytest काफ़ी है। इस कोर्स में आगे हर जगह सादा pytest लिखा है; अगर आप इसी तरह काम करते हैं तो आगे uv run जोड़ लीजिए।
आपकी पहली टेस्ट फ़ाइल
नाम के दो नियम, और pytest को आपसे बस इतना ही चाहिए:
- फ़ाइल का नाम
test_से शुरू हो (या_test.pyपर ख़त्म हो) - जिस भी फ़ंक्शन को चलवाना है, उसका नाम
testसे शुरू हो
मूल, सही prices.py वापस रखिए, और उसके बगल में test_prices.py बनाइए:
from prices import discounted
def test_ten_percent_off():
assert discounted(200, 10) == 180.0
def test_quarter_off():
assert discounted(80, 25) == 60.0
def test_no_discount():
assert discounted(99, 0) == 99check_prices.py की हर जाँच अब अपना एक छोटा फ़ंक्शन है, और हर नाम बताता है कि क्या जाँचा जा रहा है। कोई print नहीं, टेस्ट्स को रजिस्टर करने की कोई सूची नहीं, कोई if __name__ == "__main__" नहीं। अब प्रोजेक्ट फ़ोल्डर से pytest चलाइए:
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/you/shop
collected 3 items
test_prices.py ... [100%]
============================== 3 passed in 0.01s ===============================pytest ने फ़ोल्डर देखा, नाम के कारण test_prices.py को ढूँढा, उनके नामों के कारण तीनों फ़ंक्शन ढूँढे, हर एक को चलाया, और रिपोर्ट दी।
आउटपुट पढ़ना
एक-एक लाइन करके देखिए।
platform ...— कौन-सा पायथन और कौन-सा pytest चला। जब दो मशीनों पर नतीजे अलग हों तो इस पर एक नज़र डालना काम का है।rootdir: /home/you/shop— वह फ़ोल्डर जिसे pytest प्रोजेक्ट का सबसे ऊपरी फ़ोल्डर मानता है।collected 3 items— उसने कितने टेस्ट ढूँढे। टेस्ट ढूँढने को collection कहते हैं। अगर यह संख्या आपकी उम्मीद से कम है, तो कहीं कोई नाम गलत है।test_prices.py ...— हर टेस्ट के लिए एक अक्षर, उसी क्रम में जिसमें वे चले।.का मतलब है पास।[100%]— पूरे रन में कितनी प्रगति हुई।3 passed in 0.01s— सारांश लाइन (summary line)। सबसे पहले यही लाइन पढ़िए।
अब "साफ़-सुथरा" किया गया discounted वापस रखिए और फिर से चलाइए। इस बार -q के साथ, जिसे हम थोड़ी देर में ठीक से समझेंगे:
$ pytest -q
FF. [100%]
=================================== FAILURES ===================================
_____________________________ test_ten_percent_off _____________________________
def test_ten_percent_off():
> assert discounted(200, 10) == 180.0
E assert 200 == 180.0
E + where 200 = discounted(200, 10)
test_prices.py:5: AssertionError
_______________________________ test_quarter_off _______________________________
def test_quarter_off():
> assert discounted(80, 25) == 60.0
E assert 80 == 60.0
E + where 80 = discounted(80, 25)
test_prices.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_prices.py::test_ten_percent_off - assert 200 == 180.0
FAILED test_prices.py::test_quarter_off - assert 80 == 60.0
2 failed, 1 passed in 0.01sप्रगति वाली लाइन अब FF. है: हर विफलता के लिए एक F। अब इसकी तुलना पहले वाले अकेले AssertionError से कीजिए:
- हर टेस्ट चला — एक की विफलता ने बाकियों को नहीं रोका, इसलिए आप देख सकते हैं कि शून्य प्रतिशत वाला केस अब भी काम करता है
- हर विफलता टेस्ट का नाम और लाइन बताती है
assert 200 == 180.0वह वैल्यू दिखाता है जो फ़ंक्शन ने असल में लौटाई
इस रिपोर्ट को पूरा पढ़ना ही अगले अध्याय का विषय है। अभी के लिए बस इतना देखिए कि सारांश लाइन बदलकर 2 failed, 1 passed हो गई — और जो assert आप पहले से जानते थे, वही अब ख़ुद अपनी बात समझा रहा है।
-q और -v: कम और ज़्यादा
डिफ़ॉल्ट आउटपुट बीच का रास्ता है। दो flags इसे दोनों तरफ़ ले जाते हैं।
-q (quiet) हेडर हटा देता है:
$ pytest -q
... [100%]
3 passed in 0.01s-v (verbose) हर टेस्ट को उसके पूरे नाम के साथ अलग लाइन देता है:
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/shop
collecting ... collected 3 items
test_prices.py::test_ten_percent_off PASSED [ 33%]
test_prices.py::test_quarter_off PASSED [ 66%]
test_prices.py::test_no_discount PASSED [100%]
============================== 3 passed in 0.01s ===============================test_prices.py::test_ten_percent_off टेस्ट की node ID है: फ़ाइल, दो कोलन, फिर फ़ंक्शन। बाद में एक अकेला टेस्ट चलाने के लिए आप इसका इस्तेमाल करेंगे। यहीं पर वर्णनात्मक टेस्ट नामों का फ़ायदा भी दिखता है — -v की सूची में test_quarter_off बताता है कि क्या पास हुआ; test_2 नहीं बताता।
जब आप लगातार टेस्ट चलाते हैं और सिर्फ़ फ़ैसला चाहिए, तब -q इस्तेमाल कीजिए, और जब ठीक-ठीक देखना हो कि कौन-से टेस्ट चले, तब -v।
.pytest_cache क्या है? एक फ़ोल्डर जिसे pytest रन के बीच चीज़ें याद रखने के लिए बनाता है, जैसे कि पिछली बार कौन-से टेस्ट फ़ेल हुए थे। इसे डिलीट करना सुरक्षित है, और इसे version control में नहीं रखना चाहिए।Exit codes: मशीन नतीजा कैसे पढ़ती है
आप सारांश लाइन पढ़ते हैं। कोई स्क्रिप्ट, एडिटर या बिल्ड सर्वर ऐसा नहीं कर सकता — वह exit code पढ़ता है, वह संख्या जो हर प्रोग्राम ख़त्म होने पर shell को लौटाता है। Unix shell में, $? में आख़िरी exit code रहता है:
$ pytest -q
... [100%]
3 passed in 0.01s
$ echo $?
0और टूटे हुए फ़ंक्शन के साथ, उस रन के बाद जो 2 failed, 1 passed पर ख़त्म हुआ:
$ echo $?
10— सब कुछ collect हुआ, सब कुछ पास हुआ1— टेस्ट चले, और कम से कम एक फ़ेल हुआ
(PowerShell में यह वेरिएबल $LASTEXITCODE है।) यही वह चीज़ है जिसकी वजह से कोड merge होने से पहले टेस्ट अपने आप चल पाते हैं: शून्य के अलावा कोई भी exit code प्रक्रिया को रोक देता है। कुछ और codes भी हैं, जिनमें से एक से आप नीचे "जब यह काम न करे" में मिलेंगे।
टेस्ट फ़ाइलें कहाँ रहती हैं
इस अध्याय में टेस्ट फ़ाइल ठीक कोड के बगल में रखी है:
shop/
├── prices.py
└── test_prices.pyकुछ फ़ाइलों के लिए यह ठीक है। ज़्यादातर प्रोजेक्ट इसके बजाय टेस्ट्स को उनके अपने tests/ फ़ोल्डर में इकट्ठा करते हैं। दोनों ही तरह pytest उन्हें नाम से ढूँढ लेता है; असली प्रोजेक्ट को कैसे व्यवस्थित करें, और ऐसा करने पर क्या बदलता है, यह तीसरे अध्याय का विषय है।
पूर्ण उदाहरण
एक फ़ंक्शन जो अंकों को लेटर ग्रेड में बदलता है, grades.py:
def letter_grade(score):
"""Turn a score from 0 to 100 into a letter."""
if score >= 80:
return "A"
if score >= 60:
return "B"
if score >= 40:
return "C"
return "F"और इसके टेस्ट, test_grades.py:
from grades import letter_grade
def test_top_score_is_an_a():
assert letter_grade(95) == "A"
def test_exactly_eighty_is_still_an_a():
assert letter_grade(80) == "A"
def test_just_below_eighty_is_a_b():
assert letter_grade(79) == "B"
def test_middle_score_is_a_c():
assert letter_grade(50) == "C"
def test_zero_is_an_f():
assert letter_grade(0) == "F"$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/grades
collecting ... collected 5 items
test_grades.py::test_top_score_is_an_a PASSED [ 20%]
test_grades.py::test_exactly_eighty_is_still_an_a PASSED [ 40%]
test_grades.py::test_just_below_eighty_is_a_b PASSED [ 60%]
test_grades.py::test_middle_score_is_a_c PASSED [ 80%]
test_grades.py::test_zero_is_an_f PASSED [100%]
============================== 5 passed in 0.01s ===============================दो बातें ध्यान देने लायक हैं।
पहली, 80 और 79 वाले टेस्ट। ये एक सीमा (boundary) के दोनों तरफ़ बैठे हैं, और ग़लतियाँ अक्सर वहीं रहती हैं। score >= 80 को score > 80 में बदल दीजिए — एक आसान-सी चूक — और quiet रूप चलाइए:
$ pytest -q
.F... [100%]
=================================== FAILURES ===================================
______________________ test_exactly_eighty_is_still_an_a _______________________
def test_exactly_eighty_is_still_an_a():
> assert letter_grade(80) == "A"
E AssertionError: assert 'B' == 'A'
E
E - A
E + B
test_grades.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_grades.py::test_exactly_eighty_is_still_an_a - AssertionError: as...
1 failed, 4 passed in 0.01sदूसरा टेस्ट, test_exactly_eighty_is_still_an_a, ही फ़ेल होता है: 80 को अब 'B' मिल रहा है। अकेला 95 वाला टेस्ट इसे कभी नहीं पकड़ पाता।
दूसरी, नाम। -v की सूची में हर लाइन फ़ंक्शन के व्यवहार के बारे में एक वाक्य की तरह पढ़ी जाती है। जब महीनों बाद कोई टेस्ट फ़ेल होगा, तो अकेला नाम ही बता देगा कि कौन-सा वादा टूटा।
जब यह काम न करे
zsh: command not found: pytest (या bash: pytest: command not found) shell को pytest नाम का प्रोग्राम नहीं मिल रहा। या तो यह इंस्टॉल नहीं है, या ऐसे virtual environment में इंस्टॉल है जो active नहीं है। uv के साथ uv run pytest चलाइए; pip के साथ पहले environment activate कीजिए। python -m pytest भी काम करता है, और pytest को उसी पायथन से चलाता है जो python है।
/usr/bin/python3: No module named pytest आपने python -m pytest चलाया, और उस पायथन में pytest नहीं है। शुरुआत का path बताता है कि वह कौन-सा पायथन था — यहाँ सिस्टम वाला, आपके प्रोजेक्ट वाला नहीं। pytest को उसी environment में इंस्टॉल कीजिए जिसे आप इस्तेमाल करना चाहते हैं।
collection के दौरान ModuleNotFoundError: No module named 'price' टेस्ट फ़ाइल आपका कोड import नहीं कर पाई — यहाँ एक टाइपो की वजह से, from prices import ... की जगह from price import ...:
$ pytest -q
==================================== ERRORS ====================================
_______________________ ERROR collecting test_prices.py ________________________
ImportError while importing test module '/home/you/shop/test_prices.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.12/importlib/__init__.py:90: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test_prices.py:1: in <module>
from price import discounted
E ModuleNotFoundError: No module named 'price'
=========================== short test summary info ============================
ERROR test_prices.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.01sध्यान दीजिए, शब्द ERROR है, FAILED नहीं: कोई भी टेस्ट चला ही नहीं, क्योंकि फ़ाइल लोड तक नहीं हो पाई, और exit code 2 है। आख़िरी E वाली लाइन पढ़िए, फिर "टेस्ट लिखने से पहले" वाली import जाँच — python -c "from prices import discounted" — तब तक चलाइए जब तक वह अपने आप काम न करने लगे।
no tests ran in 0.01s pytest चला, लेकिन कुछ भी collect नहीं हुआ। या तो फ़ाइल का नाम test_ से शुरू नहीं होता (tests_prices.py, prices_tests.py और test.py सभी छूट जाते हैं), या फ़ंक्शन test से शुरू नहीं होते (check_quarter_off चुपचाप अनदेखा हो जाता है)। यहाँ exit code 5 है, 0 नहीं, ताकि ऑटोमेटेड रन "कुछ नहीं मिला" को "सब पास हो गया" न समझ ले।
कोई टेस्ट पास हो जाता है जबकि साफ़ तौर पर उसे फ़ेल होना चाहिए था देखिए कहीं assert तो नहीं छूट गया:
from prices import discounted
def test_quarter_off():
discounted(80, 25) == 999$ pytest -q
. [100%]
1 passed in 0.01sतुलना की गणना होती है और फिर उसे फेंक दिया जाता है। टेस्ट तभी फ़ेल होता है जब उसके अंदर कुछ raise हो; assert के बिना कुछ भी raise नहीं होता।
PytestReturnNotNoneWarning: Test functions should return None आपने assert ... की जगह return discounted(99, 0) == 99 लिख दिया। टेस्ट क्या लौटाता है, pytest इसे अनदेखा करता है, इसलिए यह टेस्ट पास होगा चाहे तुलना सही हो या ग़लत। चेतावनी ख़ुद पूछती भी है: Did you mean to use assert instead of return?
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
यह फ़ाइल test_double.py नाम से सेव करके pytest -q चलाया गया। आउटपुट की पहली लाइन किन अक्षरों से शुरू होगी?
def double(n):
return n * 2
def test_two():
assert double(2) == 4
def test_zero():
assert double(0) == 1
def test_negative():
assert double(-3) == -6- AF..
- B..F
- C.F.
- DF
999 साफ़ तौर पर गलत जवाब है, फिर भी pytest कहता है 1 passed। क्यों?
def discounted(price, percent):
return price - price * percent / 100
def test_quarter_off():
discounted(80, 25) == 999- Apytest सिर्फ़ देखता है कि फ़ंक्शन चला या नहीं, नतीजा नहीं देखता
- Bदशमलव संख्याओं की तुलना `==` से नहीं हो सकती
- Cफ़ंक्शन का नाम `test_` से शुरू नहीं होता
- Dलाइन में `assert` नहीं है — तुलना का नतीजा बनता है और फेंक दिया जाता है
सही टेस्ट वाली एक फ़ाइल का नाम double_checks.py है। उस फ़ोल्डर में pytest चलाने पर क्या होगा?
- Aसारे टेस्ट चलेंगे, क्योंकि फ़ंक्शन के नाम `test_` से शुरू होते हैं
- B`no tests ran` — फ़ाइल का नाम `test_` से शुरू नहीं होता, इसलिए उसे खोजा ही नहीं जाता; exit code `5`
- Cpytest एक `ImportError` के साथ रुक जाएगा
- Dpytest फ़ाइल का नाम बदलकर `test_double_checks.py` कर देगा
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
एक फ़ोल्डर बनाइए जिसमें temperature.py फ़ाइल हो, और उसमें दो फ़ंक्शन:
c_to_f(celsius)—celsius * 9 / 5 + 32लौटाता हैf_to_c(fahrenheit)—(fahrenheit - 32) * 5 / 9लौटाता है
फिर उस फ़ोल्डर के environment में pytest इंस्टॉल कीजिए और pytest --version से पुष्टि कीजिए।
कम से कम पाँच टेस्ट के साथ test_temperature.py लिखिए। इनमें पानी का जमना (0 और 32), पानी का उबलना (100 और 212), और -40 शामिल कीजिए, जो दोनों पैमानों में एक ही है। हर टेस्ट को ऐसा नाम दीजिए जो बताए कि वह क्या जाँचता है।
इसे तीन तरह से चलाइए — pytest, pytest -q और pytest -v — और इनमें से किसी एक के बाद exit code जाँचिए।
फिर इसे जान-बूझकर तोड़िए: c_to_f में 9 / 5 को 5 / 9 कर दीजिए। चलाने से पहले अनुमान लगाइए कि कौन-से टेस्ट फ़ेल होंगे और प्रगति वाली लाइन कैसी दिखेगी। pytest -q चलाइए, मिलान कीजिए, और फिर से exit code देखिए।
आख़िर में, test_temperature.py का नाम बदलकर temperature_check.py कर दीजिए और pytest चलाइए। पढ़िए कि वह क्या कहता है, और उसका exit code क्या है। फिर नाम वापस बदल दीजिए। "कुछ नहीं मिला" कैसा दिखता है, यह जानना आपको एक उलझन भरी दोपहर से बचा लेगा।
समाधान
पहले योजना, किसी भी कोड से पहले — पहले जैसी ही टेबल:
| केस | इनपुट | अपेक्षित | | --- | --- | --- | | पानी जमता है | c_to_f(0), f_to_c(32) | 32, 0 | | पानी उबलता है | c_to_f(100), f_to_c(212) | 212, 100 | | दोनों पैमाने मिलते हैं | c_to_f(-40), f_to_c(-40) | -40, -40 |
temperature.py:
def c_to_f(celsius):
return celsius * 9 / 5 + 32
def f_to_c(fahrenheit):
return (fahrenheit - 32) * 5 / 9टेस्ट लिखने से पहले, प्रोजेक्ट फ़ोल्डर से import जाँच:
$ python -c "import temperature; print('importable')"
importabletest_temperature.py:
from temperature import c_to_f, f_to_c
def test_water_freezes_at_32_f():
assert c_to_f(0) == 32
def test_water_boils_at_212_f():
assert c_to_f(100) == 212
def test_32_f_is_zero_c():
assert f_to_c(32) == 0
def test_212_f_is_100_c():
assert f_to_c(212) == 100
def test_minus_40_is_the_same_in_both_scales():
assert c_to_f(-40) == -40
assert f_to_c(-40) == -40$ pytest -q
..... [100%]
5 passed in 0.01s
$ echo $?
0
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/temperature
collecting ... collected 5 items
test_temperature.py::test_water_freezes_at_32_f PASSED [ 20%]
test_temperature.py::test_water_boils_at_212_f PASSED [ 40%]
test_temperature.py::test_32_f_is_zero_c PASSED [ 60%]
test_temperature.py::test_212_f_is_100_c PASSED [ 80%]
test_temperature.py::test_minus_40_is_the_same_in_both_scales PASSED [100%]
============================== 5 passed in 0.01s ===============================अब जान-बूझकर डाला गया बग, c_to_f में 9 / 5 को 5 / 9 किया गया:
$ pytest -q
.F..F [100%]
=================================== FAILURES ===================================
__________________________ test_water_boils_at_212_f ___________________________
def test_water_boils_at_212_f():
> assert c_to_f(100) == 212
E assert 87.55555555555556 == 212
E + where 87.55555555555556 = c_to_f(100)
test_temperature.py:9: AssertionError
___________________ test_minus_40_is_the_same_in_both_scales ___________________
def test_minus_40_is_the_same_in_both_scales():
> assert c_to_f(-40) == -40
E assert 9.777777777777779 == -40
E + where 9.777777777777779 = c_to_f(-40)
test_temperature.py:21: AssertionError
=========================== short test summary info ============================
FAILED test_temperature.py::test_water_boils_at_212_f - assert 87.55555555555...
FAILED test_temperature.py::test_minus_40_is_the_same_in_both_scales - assert...
2 failed, 3 passed in 0.01s
$ echo $?
1और नाम बदली गई फ़ाइल:
$ pytest -q
no tests ran in 0.01s
$ echo $?
5इसे इस तरह क्यों लिखा गया है:
- हर टेस्ट में एक व्यवहार, वाक्य जैसा नाम।
-vकी सूची में हर लाइन बताती है कि क्या वादा किया गया है। जबtest_water_boils_at_212_fफ़ेल होता है, तो आगे कुछ भी पढ़ने से पहले आपको पता होता है कि क्या टूटा। - जानी-पहचानी, जाँचने लायक वैल्यूज़।
0/32,100/212और-40ऐसे तथ्य हैं जिन्हें आप बिना कैलकुलेटर के जाँच सकते हैं, इसलिए विफलता कोड की ओर इशारा करती है, किसी संदिग्ध अपेक्षित वैल्यू की ओर नहीं। - दोनों दिशाएँ।
c_to_fऔरf_to_cअलग-अलग कोड हैं, इसलिए हर एक के अपने टेस्ट चाहिए; एक के सही होने से दूसरे के बारे में कुछ पता नहीं चलता। - बग वाला रन सबसे ज़्यादा बताता है। सिर्फ़ दो टेस्ट फ़ेल हुए, और
test_water_freezes_at_32_fउनमें नहीं था:0 * 5 / 9 + 32अब भी32है, क्योंकि शून्य से गुणा किसी भी गुणक को छिपा देता है। ठीक इसीलिए योजना में एक से ज़्यादा केस हैं — सिर्फ़ जमने वाले बिंदु वाला टेस्ट-सूट बग के रहते भी पास हो जाता।-40वाले टेस्ट में दोassertलाइनें हैं; पहली फ़ेल हुई, इसलिए दूसरी कभी चली ही नहीं। यहाँ यह ठीक है, क्योंकि दोनों एक ही वादे का वर्णन करती हैं। - Exit codes —
0,1,5— वे तीन हैं जिनसे आपका सबसे ज़्यादा सामना होगा: सब पास, कुछ फ़ेल, कुछ नहीं मिला।
Step 6 of 6
चुनौती — the chapter quiz
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
Sign in to take the quiz