✏️ धडा 02 — Unit tests: arrange, act, assert
📍 तुम्ही इथे आहात: 12 पैकी धडा 02 · मागे: lesson-01-why-test · पुढे: lesson-03-test-design
📦 या ब्रँचमध्ये काय आहे
धडा 01, आणि एका चांगल्या परीक्षा-प्रश्नाचा आकार — arrange, act, assert — आणि
असे अनेक प्रश्न चालवण्याचे दोन मार्ग: 20 ओळींत वाचता येणारा एक छोटा test runner
(exam/lab.py मधले Runner आणि expect), आणि Python मध्ये आधीपासून असलेले
unittest (exam/suites/test_grades_unittest.py).
exam/demo.py मधले unit().
🧒 5 वर्षांच्या मुलाला समजावल्यासारखे
चांगल्या परीक्षा-प्रश्नाचे तीन भाग असतात. 📝
- Arrange — तयारी करा: "एका विद्यार्थिनीला 82 गुण मिळाले."
- Act — एकदाच प्रश्न विचारा: "त्याचा grade काय?"
- Assert — उत्तरसूचीशी जुळवा: "तो A च असायला हवा."
कतरिना दीपिकाच्या grade_v1 साठी चार प्रश्न लिहिते आणि एक मदतनीस ते तपासतो.
- दोन बरोबर. ✅
- एकाचे उत्तर चुकते: 74.5 ला A हवा, code म्हणतो B. हा FAIL. ❌
- एक code ला crash करतो: 101 हा वैध score नाही, आणि code ओरडतो "out of range!" — पण प्रश्नाला ते अपेक्षित नव्हते. हा ERROR. 💥
Fail म्हणजे "विद्यार्थिनीने चुकीचे उत्तर दिले". Error म्हणजे "प्रश्न तपासताच आला नाही". दोन्हीकडे लक्ष द्यायला हवे.
🗺️ आकृती
flowchart LR
a["🧾 Arrange<br/>score = 82"] --> b["✋ Act<br/>result = grade_v1(score)"]
b --> c["✅ Assert<br/>expect(result, A)"]
c --> r["🏃 Runner<br/>runs every test, catches failures"]
r --> o1["distinction: pass"]
r --> o2["just_a_distinction: FAIL<br/>expected A, got B"]
r --> o3["out_of_range: ERROR<br/>ValueError"]
🗺️ काढलेली आवृत्ती + एक lab: https://school-edh.pages.dev/testing/lesson-diagrams.html#l02
❓ काय
- Unit test — एका छोट्या तुकड्याची (एक function, एक class) वेगळी test, ज्यात ना network, ना disk, ना घड्याळ, म्हणून ती milliseconds मध्ये चालते. "Unit" ही तुमची निवड आहे, नियम नाही: काही teams classes च्या छोट्या गटालाही एक unit म्हणतात.
- Arrange / act / assert (AAA) — वरच्या तीन पायऱ्या. यांना given / when / then असेही म्हणतात. प्रत्येक test मध्ये एकच act ठेवल्याने failure चे कारण स्पष्ट राहते.
- Assertion — तपासणी. Lab मधले
expect(actual, expected)AssertionError("expected 'A', got 'B'")देते. - Test runner — tests शोधतो, प्रत्येक स्वतंत्रपणे चालवतो, exceptions पकडतो, आणि report करतो. एक failing test बाकीच्यांना थांबवता कामा नये.
- Fail विरुद्ध error — fail म्हणजे खरे न ठरलेले assertion; error म्हणजे इतर कोणतेही
exception.
unittestत्यांना वेगवेगळे सांगते (failuresआणिerrors); pytest दोन्ही failed म्हणून सांगते आणि exception दाखवते. unittest— Python च्या standard library मधले framework (Python 2.1 पासून, JUnit च्या धर्तीवर):TestCaseclasses,test_*नावाच्या methods,assertEqual,assertRaises,setUp,python3 -m unittestने discovery.- pytest — लोकप्रिय third-party runner: साधी functions, साधे
assert, fixtures (धडा 04), parametrize (धडा 03). तेunittesttests सुद्धा बदल न करता चालवते.
🤔 का
कारण वाचायला कठीण असलेली test लवकरच delete होते किंवा दुर्लक्षित होते. AAA मुळे प्रत्येक test एक छोटी गोष्ट बनते: काय तयार केले, काय केले, काय खरे असायला हवे. आणि runner "मी चालवून पाहिले" याचे रूपांतर पुन्हा पुन्हा करता येणाऱ्या हो/नाही मध्ये करतो, ज्यावर machine — आणि CI (धडा 12) — कृती करू शकतात.
🔧 कसे (या repo मध्ये)
exam/lab.py मधला Runner test functions ची यादी ठेवतो (@r.test
एक जोडतो). run() प्रत्येकाला try मध्ये बोलवतो: exception नाही तर pass, AssertionError असेल तर
FAIL — <message>, इतर काहीही असेल तर ERROR — <type>: <message>. summary() त्यांची मोजणी करतो.
unittest file मध्ये दुरुस्त grade() वर तेच checks आहेत, आणि setUp वापरणाऱ्या दोन report-card
tests.
🧪 करून पाहा
python3 exam/demo.py unit
python3 - <<'EOF'
from exam.lab import Runner, expect, summary
from exam.grades import grade
r = Runner()
@r.test
def pass_mark_exactly():
score = 35 # arrange
result = grade(score) # act
expect(result, "D") # assert
@r.test
def top_of_the_c_band(): expect(grade(59.4), "C")
@r.test
def wrong_expectation(): expect(grade(60), "C") # the QUESTION is wrong here, not the code
@r.test
def typo_in_the_test(): expect(grade("sixty"), "B")
for name, out in r.run(): print(f"{name:<18} {out}")
print(summary(r.run()))
EOF
python3 -m unittest exam.suites.test_grades_unittest 2>&1 | tail -1
✅ तपासा — तुम्हाला काय दिसायला हवे
unit छापते:
distinction pass
fail_below_pass_mark pass
just_a_distinction FAIL — expected 'A', got 'B'
out_of_range ERROR — ValueError: score out of range: 101
2 passed, 2 failed · a FAIL is a wrong answer, an ERROR is a crash the test did not expect
── stdlib unittest runs exam/suites/test_grades_unittest.py against grade(): 6 tests · 0 failures · 0 errors
तुमचा snippet छापतो:
pass_mark_exactly pass
top_of_the_c_band pass
wrong_expectation FAIL — expected 'C', got 'B'
typo_in_the_test ERROR — TypeError: '<' not supported between instances of 'str' and 'int'
2 passed, 2 failed
आणि unittest शेवटी छापते:
OK
🏁 तुम्ही आत्ताच काय सिद्ध केले
लाल निकाल म्हणजे नेहमीच code चुकीचा असे नाही. wrong_expectation fail झाली कारण
उत्तरसूची चुकीची होती (60 म्हणजे B), आणि typo_in_the_test ला error आला कारण
प्रश्नच मोडलेला होता. Test लाल झाली की code बदलण्यापूर्वी ती वाचा. आणि
demo मधला out_of_range "code ने मुद्दाम exception दिले" याची दुरुस्ती दाखवतो: प्रश्नाने
ते exception अपेक्षित धरायला हवे, जसे test_out_of_range_is_refused
assertRaises ने करते.
⚠️ नेहमीच्या चुका
- एका test मध्ये अनेक acts आणि खूप asserts — ती fail झाली की कोणता भाग मोडला ते कळत नाही
- एकही assert नाही ("crash न होता चालले") — कधीच fail न होऊ शकणारी test
- निकालाऐवजी खाजगी तपशील (नेमका loop) test करणे
test1सारखी नावे — नावाने नियम सांगायला हवा:test_half_mark_rounds_up_to_a_pass- test मध्येच exception पकडून दुर्लक्ष करणे
🏭 प्रत्यक्ष वापरात
तेच tests pytest मध्ये. खऱ्या project वर (pip install pytest),
tests/test_grade.py म्हणून save करा:
import pytest
from exam.grades import grade
def test_distinction():
assert grade(82) == "A"
def test_out_of_range_is_refused():
with pytest.raises(ValueError, match="out of range"):
grade(101)
रोजच्या commands:
pytest -q # quiet: dots and a summary
pytest -x # stop at the first failure
pytest -k "distinction" # only tests whose name matches
pytest --lf # only the tests that failed last time
python3 -m unittest discover -s tests -v # the standard-library way
इतर भाषांमध्येही हाच आकार आहे: Java साठी JUnit 5 (@Test, assertEquals),
JavaScript साठी Vitest / Jest (test(...), expect(x).toBe(y)), Go साठी
t.Errorf सह go test.
🏭 Production मध्ये हे का महत्त्वाचे: test suite लिहिली जाते त्यापेक्षा कितीतरी जास्त वेळा वाचली जाते — ती लाल झाल्यावर जो on call असेल त्याच्याकडून. प्रत्येक test ला ती तपासत असलेल्या नियमाचे नाव द्या, प्रत्येक test मध्ये एकच act ठेवा, आणि failure message मध्ये expected विरुद्ध actual सांगा.
⏭️ पुढे
तुम्ही एक प्रश्न लिहू शकता. पण कोणते प्रश्न? पाच सोप्या प्रश्नांना bug सापडला नाही. Equivalence classes, boundary values आणि decision tables तो शोधतात.
git checkout lesson-03-test-design