अध्याय 01

pytest इंस्टॉल करना और आपका पहला टेस्ट

हाथ से जाँचना क्यों काम नहीं आता, खाली assert लगभग कुछ क्यों नहीं बताता, और pytest इंस्टॉल करके पहली टेस्ट फ़ाइल लिखना और चलाना। डॉट, सारांश लाइन, -q, -v और exit code पढ़ने के साथ।

40 मिनटPython 3.12
  1. 1समस्या
  2. 2समझें
  3. 3उदाहरण
  4. 4अनुमान
  5. 5स्वयं करें
  6. 6चुनौती

वह समस्या जिसे हम हल कर रहे हैं

यह रहा एक छोटा-सा फ़ंक्शन, और वह तरीका जिससे हम में से ज़्यादातर लोग ऐसे फ़ंक्शन को जाँचते हैं — उसे चलाओ और देखो कि क्या निकलता है:

python
def discounted(price, percent):
    return price - price * percent / 100


print(discounted(200, 10))
print(discounted(80, 25))
print(discounted(99, 0))
text
180.0
60.0
99.0

आप तीनों संख्याएँ पढ़ते हैं, मन में हिसाब लगाते हैं, और तय करते हैं कि वे सही हैं। यह भी एक टेस्ट है। बस यह मैन्युअल टेस्ट है, और इसमें दो समस्याएँ हैं।

पहली यह कि यह बड़े पैमाने पर नहीं चलता। तीन संख्याएँ आँख से जाँचना आसान है। लेकिन तीस फ़ंक्शन, हर एक के पाँच केस, और हर बदलाव के बाद दोबारा जाँच — यह आसान नहीं है, और कोई ऐसा करता भी नहीं।

दूसरी समस्या इससे भी बुरी है। एक महीने बाद कोई फ़ंक्शन को थोड़ा "साफ़-सुथरा" कर देता है:

python
def discounted(price, percent):
    return price * (1 - percent // 100)
text
$ 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 के बगल में:

python
from prices import discounted

assert discounted(200, 10) == 180.0
assert discounted(80, 25) == 60.0
assert discounted(99, 0) == 99
print("all good")

मूल फ़ंक्शन के साथ:

text
$ python check_prices.py
all good

"साफ़-सुथरे" किए गए फ़ंक्शन के साथ:

text
$ 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
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

regression पकड़ में आ गया, यह एक प्रगति है। लेकिन देखिए आपको बताया क्या गया: AssertionError, और कुछ नहीं। यह नहीं कि discounted(200, 10) ने असल में क्या लौटाया। और स्क्रिप्ट पहली विफलता पर ही रुक गई, इसलिए आपको पता ही नहीं कि बाकी दो जाँचें पास होती हैं या नहीं।

यह अध्याय वह टूल इंस्टॉल करता है जो इन दोनों समस्याओं को ठीक करता है: pytest।

इस अध्याय के अंत में आप कर पाएंगे

  • समझाना कि ऑटोमेटेड टेस्ट वे regressions क्यों पकड़ लेते हैं जो मैन्युअल जाँच से छूट जाते हैं
  • uv या pip से pytest इंस्टॉल करना, और pytest --version से इसकी पुष्टि करना
  • एक ऐसी टेस्ट फ़ाइल लिखना जिसे pytest अपने आप ढूँढ ले, और उसे चलाना
  • डॉट्स और सारांश लाइन पढ़ना, और -q व -v के बीच बदलना
  • बताना कि exit code 0 और 1 का क्या मतलब है, और यह क्यों मायने रखता है

ज़रूरी शर्तें: टेस्ट लिखने से पहले। इसके अलावा आपको Python 3 चाहिए और फ़ंक्शन लिखना आना चाहिए।


टेस्ट लिखने से पहले

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

1. एक प्रोजेक्ट फ़ोल्डर, एक environment। इस अध्याय की हर चीज़ एक ही फ़ोल्डर में रहती है:

text
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 प्रोजेक्ट फ़ोल्डर से काम करता है:

text
$ 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 इस्तेमाल करता है, तो प्रोजेक्ट फ़ोल्डर में यह चलाइए:

text
$ 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.1

uv इसे pyproject.toml में एक dev ग्रुप के नीचे दर्ज कर देता है, ताकि बाद में जो भी प्रोजेक्ट सेट-अप करे, उसे वही टूल मिले:

toml
[dependency-groups]
dev = [
    "pytest>=9.1.1",
]

अगर आप virtual environment के अंदर सादा pip इस्तेमाल करते हैं, तो environment को activate कीजिए और चलाइए:

text
$ python -m pip install pytest

pip डाउनलोड की प्रगति दिखाता है और आख़िर में यह लिखता है:

text
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 लिखने से यह पक्का हो जाता है कि पैकेज उसी पायथन में पहुँचे जिसे आप चलाएँगे — "मैंने इंस्टॉल तो किया था पर वह मिल नहीं रहा" वाली आम परेशानी की जड़ अक्सर यही होती है।

अब जाँचिए कि यह काम कर गया:

text
$ 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 बनाइए:

python
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) == 99

check_prices.py की हर जाँच अब अपना एक छोटा फ़ंक्शन है, और हर नाम बताता है कि क्या जाँचा जा रहा है। कोई print नहीं, टेस्ट्स को रजिस्टर करने की कोई सूची नहीं, कोई if __name__ == "__main__" नहीं। अब प्रोजेक्ट फ़ोल्डर से pytest चलाइए:

text
$ 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 के साथ, जिसे हम थोड़ी देर में ठीक से समझेंगे:

text
$ 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) हेडर हटा देता है:

text
$ pytest -q
...                                                                      [100%]
3 passed in 0.01s

-v (verbose) हर टेस्ट को उसके पूरे नाम के साथ अलग लाइन देता है:

text
$ 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 रहता है:

text
$ pytest -q
...                                                                      [100%]
3 passed in 0.01s
$ echo $?
0

और टूटे हुए फ़ंक्शन के साथ, उस रन के बाद जो 2 failed, 1 passed पर ख़त्म हुआ:

text
$ echo $?
1
  • 0 — सब कुछ collect हुआ, सब कुछ पास हुआ
  • 1 — टेस्ट चले, और कम से कम एक फ़ेल हुआ

(PowerShell में यह वेरिएबल $LASTEXITCODE है।) यही वह चीज़ है जिसकी वजह से कोड merge होने से पहले टेस्ट अपने आप चल पाते हैं: शून्य के अलावा कोई भी exit code प्रक्रिया को रोक देता है। कुछ और codes भी हैं, जिनमें से एक से आप नीचे "जब यह काम न करे" में मिलेंगे।

टेस्ट फ़ाइलें कहाँ रहती हैं

इस अध्याय में टेस्ट फ़ाइल ठीक कोड के बगल में रखी है:

text
shop/
├── prices.py
└── test_prices.py

कुछ फ़ाइलों के लिए यह ठीक है। ज़्यादातर प्रोजेक्ट इसके बजाय टेस्ट्स को उनके अपने tests/ फ़ोल्डर में इकट्ठा करते हैं। दोनों ही तरह pytest उन्हें नाम से ढूँढ लेता है; असली प्रोजेक्ट को कैसे व्यवस्थित करें, और ऐसा करने पर क्या बदलता है, यह तीसरे अध्याय का विषय है।


पूर्ण उदाहरण

एक फ़ंक्शन जो अंकों को लेटर ग्रेड में बदलता है, grades.py:

python
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:

python
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"
text
$ 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 रूप चलाइए:

text
$ 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 ...:

text
$ 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 तो नहीं छूट गया:

python
from prices import discounted


def test_quarter_off():
    discounted(80, 25) == 999
text
$ 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?