Testing: unittest, pytest, doctest

টেস্টিং — script থেকে software-এ উত্তরণের মূল মানদণ্ড

Read: ~30 min Intermediate 5 practice problems Live code runner

1. Why Tests?

Code without tests is code you hope works. A test suite tells you when you broke something, what broke, and lets you refactor fearlessly. Good tests are small, focused, readable, and fast. Python ships with two testing tools (unittest, doctest); the third (pytest) is the community standard and installed with pip install pytest.

Test ছাড়া কোড মানে আপনি শুধু আশা করছেন এটি কাজ করবে। Test suite বলে দেয় কখন আপনি কিছু ভেঙেছেন, কী ভেঙেছে — এবং নিশ্চিন্তে refactor করা যায়। ভালো test ছোট, নির্দিষ্ট, পাঠযোগ্য, ও দ্রুত। Python-এর সঙ্গে আসে দুটি (unittest, doctest); তৃতীয়টি pytest — community standard।

2. Start with Plain assert

assert_demo.py
def add(a, b):
    return a + b

assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
print("all passed")

assert is the atomic unit of testing. Everything above builds on it. But assert alone gives you no organization, no reporting, and no way to collect tests across a project — which is why testing frameworks exist.

3. unittest — Standard Library

unittest_demo.py
import unittest

def add(a, b): return a + b

class TestAdd(unittest.TestCase):
    def test_positive(self):
        self.assertEqual(add(2, 3), 5)

    def test_negative(self):
        self.assertEqual(add(-1, -1), -2)

    def test_zero(self):
        self.assertEqual(add(0, 0), 0)

# Normally: python -m unittest; here we run in-script:
if __name__ == "__main__":
    unittest.main(argv=["first"], exit=False)

4. pytest — The Community Favorite

pytest uses plain functions and plain assert. It finds tests automatically (files named test_*.py, functions test_*) and prints rich failure messages. Install with pip install pytest, run with pytest.

test_math.py
# pytest is not on Piston, so we simulate it in plain Python

def mul(a, b): return a * b

def test_mul_basic():
    assert mul(3, 4) == 12

def test_mul_zero():
    assert mul(10, 0) == 0

def test_mul_negative():
    assert mul(-2, 5) == -10

# Run them all
for name, obj in list(globals().items()):
    if name.startswith("test_") and callable(obj):
        obj()
        print(f"{name} ✓")

With pytest installed, you would write:

@pytest.fixture
def sample_data():
    return [1, 2, 3]

def test_sum(sample_data):
    assert sum(sample_data) == 6

@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

5. doctest — Tests in Your Docstrings

doctest_demo.py
def factorial(n):
    """Return n!.

    >>> factorial(0)
    1
    >>> factorial(5)
    120
    >>> factorial(1)
    1
    """
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

import doctest
doctest.testmod(verbose=True)

doctests double as documentation. Great for small utility functions; less suitable for complex test scenarios.

6. What Makes a Good Test?

  • Fast — a suite that takes a minute gets skipped.
  • Isolated — no shared state, no file writes into project directories, no hardcoded localhost:8080.
  • Focused — one test per behavior; short and readable.
  • Deterministic — same input ⇒ same result. No datetime.now() or random unless seeded.
  • Automated — runs in CI, not only in developers' heads.
Arrange / Act / Assert: structure every test in three mental sections — set up inputs, call the code, then assert on outputs. It keeps tests readable.

7. Vocabulary (শব্দভাণ্ডার)

TermMeaningবাংলায়
Unit testTests a single function/class in isolation.একটিমাত্র ফাংশন/class-এর test।
FixtureReusable test setup data.Test-এর জন্য reusable setup।
ParametrizeRun one test over many input sets.একই test একাধিক input-এ চালানো।
MockA fake object standing in for a real one.আসল object-এর জায়গায় নকল।
CoverageFraction of code exercised by tests.Test যে কোড অংশে পৌঁছেছে তার অনুপাত।

8. Practice Problems

  1. Write three assert statements to test a max_of_three(a,b,c) function.
    max_of_three(a,b,c) ফাংশনের জন্য তিনটি assert লিখুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans1.py
    def max_of_three(a, b, c):
        return max(a, b, c)
    
    assert max_of_three(3, 7, 5) == 7
    assert max_of_three(-1, -5, -3) == -1
    assert max_of_three(0, 0, 0) == 0
    print("passed")
  2. Use unittest.TestCase to test that sorted returns a new list.
    unittest.TestCase দিয়ে test করুন — sorted একটি নতুন list return করে।
    ✨ Show Answer (উত্তর দেখুন)
    ans2.py
    import unittest
    
    class TestSorted(unittest.TestCase):
        def test_returns_new(self):
            a = [3, 1, 2]
            b = sorted(a)
            self.assertEqual(b, [1, 2, 3])
            self.assertEqual(a, [3, 1, 2])
            self.assertIsNot(a, b)
    
    unittest.main(argv=["x"], exit=False)
  3. Write a doctest for a function is_palindrome(s).
    is_palindrome(s) ফাংশনের জন্য doctest লিখুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans3.py
    def is_palindrome(s):
        """Return True iff s reads the same forwards and backwards.
    
        >>> is_palindrome("level")
        True
        >>> is_palindrome("python")
        False
        >>> is_palindrome("")
        True
        """
        return s == s[::-1]
    
    import doctest
    doctest.testmod(verbose=True)
  4. Explain why relying on datetime.now() inside a test can cause flakiness.
    ব্যাখ্যা করুন — test-এর ভেতর datetime.now() ব্যবহার কেন flaky।
    ✨ Show Answer (উত্তর দেখুন)

    Answer: The result depends on the exact time the test runs, which is non-deterministic. Tests that pass today can fail tomorrow (around DST, leap seconds, a different timezone). Inject the clock as a parameter or use mocking so the value is controlled by the test.

    ফলাফল test চলার সঠিক সময়ের উপর নির্ভর করে — non-deterministic। আজ pass করা test কাল fail করতে পারে (DST, leap second, ভিন্ন timezone)। Clock-কে parameter করে inject করুন বা mock করে test-এ মান নিয়ন্ত্রণ করুন।

  5. Write one test that verifies a function raises ValueError on invalid input.
    Invalid input-এ ValueError raise হয় কি না — তা test করুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans5.py
    import unittest
    
    def sqrt_of_positive(x):
        if x < 0:
            raise ValueError("negative")
        return x ** 0.5
    
    class T(unittest.TestCase):
        def test_raises(self):
            with self.assertRaises(ValueError):
                sqrt_of_positive(-4)
    
    unittest.main(argv=["x"], exit=False)

Summary — Module 29

Tests are the difference between a script and software. Start with plain assert, graduate to unittest for structure, and adopt pytest for productive day-to-day work. Add doctests to tiny utilities. Keep tests fast, isolated, focused, and deterministic — then wire them into CI.

Test-ই script ও software-এর পার্থক্য। সাধারণ assert, তারপর unittest, এবং পেশাদারী কাজে pytest। ছোট utility-তে doctest। Test রাখুন fast, isolated, focused, deterministic — এবং CI-তে wire করুন।

Next Module → Pythonic Idioms & Best Practices (PEP 8, PEP 20)।