🏫 The School›📝 Testing›✏️ धडा 02 — Unit tests: arrange, act, assert
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

✏️ धडा 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 वर्षांच्या मुलाला समजावल्यासारखे

चांगल्या परीक्षा-प्रश्नाचे तीन भाग असतात. 📝

  1. Arrange — तयारी करा: "एका विद्यार्थिनीला 82 गुण मिळाले."
  2. Act — एकदाच प्रश्न विचारा: "त्याचा grade काय?"
  3. Assert — उत्तरसूचीशी जुळवा: "तो A च असायला हवा."

कतरिना दीपिकाच्या grade_v1 साठी चार प्रश्न लिहिते आणि एक मदतनीस ते तपासतो.

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

❓ काय

🤔 का

कारण वाचायला कठीण असलेली 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 ने करते.

⚠️ नेहमीच्या चुका

🏭 प्रत्यक्ष वापरात

तेच 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

✏️ Lesson 02 — Unit tests: arrange, act, assert

📍 You are here: Lesson 02 of 12 · Previous: lesson-01-why-test · Next: lesson-03-test-design


📦 What's in this branch

Lesson 01, plus the shape of one good exam question — arrange, act, assert — and two ways to run many of them: a tiny test runner you can read in 20 lines (Runner and expect in exam/lab.py), and Python's built-in unittest (exam/suites/test_grades_unittest.py). unit() in exam/demo.py.

🧒 Explain like I'm 5

A good exam question has three parts. 📝

  1. Arrange — set the scene: "A pupil scored 82."
  2. Act — ask the question once: "What grade is that?"
  3. Assert — compare with the answer key: "It must be A."

Katrina writes four questions for Dipika's grade_v1 and a helper marks them.

A fail means "the pupil answered wrongly". An error means "the question could not even be marked". Both need a look.

🗺️ Diagram

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"]

🗺️ Drawn version + a lab: https://school-edh.pages.dev/testing/lesson-diagrams.html#l02

❓ What

🤔 Why

Because a test that is hard to read is soon deleted or ignored. AAA makes each test a tiny story: what was set up, what was done, what should be true. And a runner turns "I ran it and looked" into a repeatable yes/no that a machine — and CI (lesson 12) — can act on.

🔧 How (in this repo)

Runner in exam/lab.py keeps a list of test functions (@r.test adds one). run() calls each inside try: no exception is pass, an AssertionError is FAIL — <message>, anything else is ERROR — <type>: <message>. summary() counts them. The unittest file has the same checks against the fixed grade(), plus two report-card tests that use setUp.

🧪 Try it

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

✅ Verify — what you should see

unit prints:

   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

Your snippet prints:

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

and unittest ends with:

OK

🏁 What you just proved

A red result does not always mean the code is wrong. wrong_expectation failed because the answer key was wrong (60 is a B), and typo_in_the_test errored because the question was broken. When a test goes red, read it before you change the code. And out_of_range in the demo shows the fix for "the code raised on purpose": the question should expect the exception, as test_out_of_range_is_refused does with assertRaises.

⚠️ Common mistakes

🏭 In production

The same tests in pytest. On a real project (pip install pytest), save as tests/test_grade.py:

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)

Everyday 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

Other languages have the same shape: JUnit 5 (@Test, assertEquals) for Java, Vitest / Jest (test(...), expect(x).toBe(y)) for JavaScript, go test with t.Errorf for Go.

🏭 Why this matters in production: a test suite is read far more often than it is written — by whoever is on call when it goes red. Name each test after the rule it checks, keep one act per test, and make the failure message say expected vs actual.

⏭️ Next

You can write a question. But which questions? Five easy ones missed the bug. Equivalence classes, boundary values and decision tables find it.

git checkout lesson-03-test-design
← Previouswhy testNext →test design

This page is the lesson's README from the lesson-02-unit-tests branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.