Python Code Testing और Debugging
Testing और debugging क्यों ज़रूरी
Debugging यह पता लगाना है कि program ग़लत क्यों चल रहा है। Testing छोटे programs लिखना है जो आपका code अपने-आप जाँचें, ताकि वही bug दोबारा न आए। Professional developers नया code लिखने से ज़्यादा समय इन दो skills पर लगाते हैं।
Step 1: traceback नीचे से पढ़ें
def average(marks):
return sum(marks) / len(marks)
def report(student):
avg = average(student["marks"])
return f"{student['name']}: {avg:.1f}"
print(report({"name": "Asha", "marks": [91, 88]}))
print(report({"name": "Ravi", "marks": []}))- आख़िरी line = error का प्रकार और message:
ZeroDivisionError: division by zero। - उसके ऊपर की line = जो line fail हुई (
averageके अंदर)। - ऊपर की ओर पढ़ें कि किसने बुलाया:
report→ आपकी script की line 9। - पूछें: किस value से यह fail हुआ? यहाँ
marksख़ाली list थी।
| Error | आम कारण |
|---|---|
NameError | Spelling ग़लती या assign से पहले variable |
TypeError | ग़लत type, जैसे "5" + 3 या argument छूटा |
KeyError / IndexError | Dict में key नहीं / list के अंत से आगे index |
AttributeError | ग़लत object पर method, अक्सर None |
IndentationError | Tabs/spaces मिले हुए या indent छूटा |
Step 2: print() बनाम logging
जल्दी जाँच के लिए print ठीक है, पर असली programs में logging इस्तेमाल करें: level से messages चालू/बंद कर सकते हैं, समय जोड़ सकते हैं और file में लिख सकते हैं – बाद में lines delete किए बिना।
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(funcName)s: %(message)s")
log = logging.getLogger(__name__)
def apply_discount(price, percent):
log.debug("raw input price=%r percent=%r", price, percent) # hidden at INFO level
if not 0 <= percent <= 50:
log.warning("discount %s%% looks wrong, using 0", percent)
percent = 0
final = round(price * (100 - percent) / 100, 2)
log.info("final price %s", final)
return final
apply_discount(1200, 10)
apply_discount(1200, 90)| Level | किसके लिए |
|---|---|
| DEBUG | Development के दौरान विस्तृत values |
| INFO | सामान्य पड़ाव (“order saved”) |
| WARNING | कुछ अजीब पर program चलता रहता है |
| ERROR | कोई काम fail हुआ |
| CRITICAL | Program आगे नहीं चल सकता |
Step 3: breakpoint() से program रोकें
किसी भी line पर breakpoint() लगाएँ। Python वहाँ पहुँचते ही program रोककर terminal में debugger खोलता है। आप variables छाप सकते हैं, अगली line चला सकते हैं और आगे बढ़ सकते हैं।
def grade(marks):
total = sum(marks)
breakpoint() # execution stops here
percent = total / len(marks)
return "A" if percent >= 85 else "B"
print(grade([91, 88, 79]))| Command | अर्थ |
|---|---|
p x | x की value छापो |
n | अगली line चलाओ |
s | function call के अंदर जाओ |
l | मौजूदा line के आस-पास का code |
c | अगले breakpoint तक चलाओ |
q | बाहर निकलो |
VS Code में line number के बाईं ओर click करके लाल breakpoint लगाएँ, फिर F5 → “Python File”। Variables panel हर value दिखाता है और F10/F11 से step कर सकते हैं – कोई command याद नहीं रखनी।
Step 4: assert – सबसे छोटा test
def to_paise(rupees):
return round(rupees * 100)
assert to_paise(10) == 1000
assert to_paise(0.1 + 0.2) == 30 # floats are tricky – round() saves us
assert to_paise(99.99) == 9999
print("all checks passed")assert की condition false हो तो Python AssertionError से रुक जाता है। Asserts tests में और “यह कभी नहीं होना चाहिए” checks में लगाएँ – user input जाँचने में नहीं (python -O से asserts बंद हो सकते हैं)।
Step 5: pytest से automated tests
यह दुकान का छोटा billing function है। नियम: coupon SAVE10 ₹500 या उससे ज़्यादा के bill पर चलता है, ₹1,000 से delivery मुफ़्त।
def bill_total(items, coupon=None):
"""items: list of (price, qty). SAVE10 gives 10% off on bills of Rs 500 or more."""
subtotal = sum(price * qty for price, qty in items)
if coupon == "SAVE10" and subtotal > 500:
subtotal = subtotal * 0.9
delivery = 0 if subtotal >= 1000 else 40
return round(subtotal + delivery)Tests साधारण functions हैं जिनके नाम test_ से शुरू होते हैं, test_*.py नाम की files में। pytest एक बार install करें (pip install pytest) और लिखें:
import pytest
from bill import bill_total
def test_small_order_pays_delivery():
assert bill_total([(100, 2)]) == 240
def test_free_delivery_from_1000():
assert bill_total([(500, 2)]) == 1000
def test_coupon_on_exactly_500():
assert bill_total([(250, 2)], coupon="SAVE10") == 490 # 450 + 40 delivery
@pytest.mark.parametrize("items, expected", [
([], 40),
([(99.5, 2)], 239),
([(300, 1), (200, 1)], 540),
])
def test_many_cases(items, expected):
assert bill_total(items) == expectedएक test fail हुआ, और pytest ठीक-ठीक कारण दिखाता है: coupon के साथ ₹500 पर 490 चाहिए था पर 540 मिला। Bug है subtotal > 500; नियम कहता है “500 या ज़्यादा”। इसे >= 500 करें और फिर चलाएँ:
def bill_total(items, coupon=None):
"""items: list of (price, qty). SAVE10 gives 10% off on bills of Rs 500 or more."""
subtotal = sum(price * qty for price, qty in items)
if coupon == "SAVE10" and subtotal >= 500:
subtotal = subtotal * 0.9
delivery = 0 if subtotal >= 1000 else 40
return round(subtotal + delivery)Bugs किनारों पर रहते हैं: ख़ाली lists, ठीक 500, ठीक 1,000, decimals, zero, negative numbers। अच्छा test suite हर boundary एक बार जाँचता है।
Errors उठने की जाँच
import pytest
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > balance:
raise ValueError("insufficient balance")
return balance - amount
def test_normal_withdraw():
assert withdraw(1000, 300) == 700
def test_overdraw_is_blocked():
with pytest.raises(ValueError, match="insufficient"):
withdraw(100, 500)Built-in विकल्प: unittest
import unittest
def is_valid_pin(pin):
return len(pin) == 6 and pin.isdigit() and pin[0] != "0"
class PinTests(unittest.TestCase):
def test_valid(self):
self.assertTrue(is_valid_pin("302001"))
def test_invalid(self):
for pin in ["02001", "0123456", "30200A", ""]:
with self.subTest(pin=pin):
self.assertFalse(is_valid_pin(pin))
if __name__ == "__main__":
unittest.main(verbosity=2)unittest को install नहीं करना पड़ता और कई पुराने projects में इस्तेमाल होता है। pytest unittest tests भी चला सकता है, इसलिए दोनों मिला सकते हैं।
हमेशा काम करने वाली debugging routine
- सबसे छोटे input से bug दोबारा पैदा करें।
- Traceback नीचे से पढ़ें।
- logging, breakpoint() या VS Code debugger से values देखें।
- एक अनुमान बनाकर जाँचें – एक बार में एक बदलाव।
- ऐसा test लिखें जो bug की वजह से fail हो।
- Test pass होने तक code ठीक करें, फिर सारे tests चलाएँ।
अभ्यास
- ऊपर वाले
average()को ठीक करें ताकि ख़ाली list पर 0 लौटे, और उसका pytest test लिखें। - समय के साथ (
%(asctime)s)app.logfile में logging जोड़ें। is_valid_mobile(number)(10 digits, 6–9 से शुरू) के लिए 5 pytest tests लिखें।breakpoint()से पता लगाएँ कि total में"10" + str(5)से 105 क्यों आता है।
सारांश
- Traceback नीचे से पढ़ें: error type → fail हुई line → बुलाने वाले।
- बिखरे prints की जगह logging levels।
breakpoint()या VS Code debugger से रोककर values देखें।- pytest: files
test_*.py, functionstest_*, साधारणassert; parametrize और pytest.raises। - हर ठीक किए bug का test बनाएँ ताकि वह लौटे नहीं।
