Python Unit Testing Sinhala | unittest Module | Test Automation | SC Guide

Python Unit Testing Sinhala | unittest Module | Test Automation | SC Guide

Mastering Python Unit Testing with unittest SC Guide

ආයුබෝවන් හැමෝටම! 👋 අද අපි කතා කරන්න යන්නේ software engineering වල තියෙන හරිම වැදගත් මාතෘකාවක් ගැන. ඒ තමයි Testing. විශේෂයෙන්ම, Python වල Unit Testing කරන විදිහ සහ ඒකට තියෙන unittest module එක ගැන.

දැන් හිතන්න, ඔයා අලුත් software එකක් හදලා, ඒක customers ලාට දුන්නා කියලා. ඒත් ටික දවසකින් ඒකේ වැරදි එන්න පටන් ගත්තොත්? 😞 ඒක අපිටත්, අපේ customer ලාටත් ලොකු කරදරයක්. Testing කියන්නේ මෙන්න මේ වගේ ගැටළු ඇතිවෙන්න කලින්ම හොයාගෙන විසඳගන්න තියෙන හොඳම ක්‍රමය. අපේ වැඩ ටික හොඳටම කරනවා වගේම, ඒ වැඩ ටික හරියටම කරනවා කියලා sure කරගන්න එකත් හරිම වැදගත් නේද?

මේ blog post එකෙන් අපි බලමු:

  • Software testing වල වැදගත්කම මොකක්ද කියලා.
  • Python වල unittest module එක පාවිච්චි කරලා Unit Tests ලියන හැටි.
  • Test එකක් fail වුනොත් ඒක තේරුම් ගන්නෙ කොහොමද කියලා.
  • අවසානයේදී, හොඳම Testing පුරුදු (Best Practices) සහ TDD (Test-Driven Development) ගැන පොඩි හැඳින්වීමක්.

ඉතින්, අපි පටන් ගමු!

Test කිරීමේ වැදගත්කම (The Importance of Testing)

නොදැනීම වගේ බඩු විකුණන්න කලින් ඒකේ තත්ත්වේ බලනවා වගේ, software එකක් develop කරලා release කරන්න කලින් ඒකේ තත්ත්වේ බලන එක තමයි Testing කියන්නේ. හැබැයි මේක 단순히 Bug හොයන එකට වඩා ගොඩක් දුර යන දෙයක්.

ඇයි අපි Test කරන්න ඕනෙ?

  • Bugs අඩුවීම (Bug Reduction): මේක තමයි ප්‍රධානම හේතුව. අලුත් feature එකක් හරි, code change එකක් හරි කරාම ඒක නිසා කලින් හොඳට වැඩ කරපු තැනක අවුලක් එන්න පුළුවන් (Regression Bug). Tests ලියලා තියෙනවා නම්, මෙහෙම වුනාම ඒක ඉක්මනින්ම හොයාගන්න පුළුවන්.
  • Confidence වැඩිවීම (Increased Confidence): Tests හරියට pass වෙනවා කියන්නේ අපේ code එක හරියට වැඩ කරනවා කියන විශ්වාසය අපිට ලැබෙනවා. අලුත් features එකතු කරනකොට හරි, code එක refactor කරනකොට හරි, බය නැතුව ඒ දේවල් කරන්න පුළුවන්. මොකද, Test suite එකක් තියෙනවා නම්, වෙන දේවල් කැඩෙන එක වළක්වා ගන්න පුළුවන්.
  • Code Quality වැඩිවීම (Improved Code Quality): Tests ලියන්න හිතාගෙන code ලියනකොට, අපි ස්වභාවිකවම Modular සහ test කරන්න පහසු code ලියනවා. ඒක දිගු කාලීනව code එක maintain කරන්න සහ තේරුම් ගන්න පහසු වෙනවා.
  • Documentation එකක් විදිහට (As Documentation): හොඳින් ලියපු tests, ඒ අදාල code block එක මොනවද කරන්නේ කියලා පැහැදිලි කරනවා. අලුතින් project එකට එන කෙනෙකුට tests බලලා code base එක තේරුම් ගන්න ලේසියි.
  • Maintenance සහ Refactoring පහසු වීම (Easier Maintenance and Refactoring): Code එකට වෙනස්කම් කරනකොට, ඒ වෙනස්කම් නිසා අහිතකර බලපෑම් ඇතිවෙන්නේ නැති බවට tests අපිට සහතික වෙනවා. ඒක නිසා code එක maintain කරන එක සහ පවතින code එකේ ව්‍යුහය වැඩිදියුණු කිරීම (Refactoring) පහසු වෙනවා.

සරලව කිව්වොත්, Testing කියන්නේ අපේ software එකේ ගුණාත්මකභාවය සහ ස්ථාවරත්වය රැකගන්න තියෙන අත්‍යවශ්‍ය මෙවලමක්.

Python වල Unit Testing වලට පිවිසීම (Diving into Python Unit Testing)

Testing වල ගොඩක් වර්ග තියෙනවා. Unit Testing, Integration Testing, End-to-End Testing වගේ. මේ හැම එකකම වෙනස් අරමුණු තියෙනවා. අපි අද කතා කරන්නේ Unit Testing ගැන.

Unit Testing කියන්නේ මොකක්ද?

Unit Testing කියන්නේ software එකේ තියෙන පුංචිම පුංචි කොටස්, එහෙමත් නැත්නම් “units” තනි තනිවම test කරන ක්‍රියාවලිය. මේ “unit” එකක් කියන්නේ function එකක්, method එකක්, එහෙම නැත්නම් class එකක් වෙන්න පුළුවන්. අරමුණ තමයි ඒ හැම unit එකක්ම අපිට ඕන විදිහටම හරියට වැඩ කරනවද කියලා බලන එක.

Python වල Unit Testing කරන්න, අපිට Built-in module එකක් තියෙනවා. ඒ තමයි unittest module එක.

unittest Module එකට හැඳින්වීමක්

Python වල unittest module එක Junit වගේම test frameworks වලට සමාන විදිහට තමයි නිර්මාණය කරලා තියෙන්නේ. මේකේ ප්‍රධාන සංරචක කිහිපයක් තියෙනවා:

  • Test Case: මේක තමයි test කරන්න ඕන දේවල් තියෙන තැන. unittest.TestCase කියන class එක inherit කරලා අපි අපේ tests ලියනවා. මේක ඇතුලේ methods විදිහට තනි තනි tests තියෙනවා. (ඒ methods නිතරම test_ වලින් පටන් ගන්න ඕන).
    උදාහරණයක් විදිහට: test_add_two_numbers(), test_subtract_with_zero().
  • Test Fixture: test එකක් run කරන්න කලින් අවශ්‍ය දේවල් සකස් කරන එක (setUp()) සහ test එක run කරලා ඉවර වුනාම cleanup කරන එක (tearDown()). උදාහරණයක් විදිහට, database connection එකක් open කරන එක setUp() එකේදි කරලා, tearDown() එකේදි ඒක close කරන්න පුළුවන්.
  • Test Suite: එක test case එකක් හරි, test cases කිහිපයක් හරි එකට එකතු කරලා run කරන්න පුළුවන් එකක්.
  • Test Runner: tests හොයාගෙන, ඒව run කරලා, ප්‍රතිඵල report කරන එක. unittest.main() කියන්නේ මේකට හොඳම උදාහරණයක්.

Assertions මොනවද?

Test method එකක් ඇතුලේ, අපි self.assertEqual(), self.assertTrue() වගේ methods පාවිච්චි කරනවා. මේවා තමයි Assertions. Assertion එකකින් කරන්නේ, අපි බලාපොරොත්තු වෙන ප්‍රතිඵලය සහ ඇත්තටම ලැබුණු ප්‍රතිඵලය අතර සමානතාවක් තියෙනවද කියලා බලන එක. මේක හරියට නැත්නම්, test එක Fail වෙනවා.

පොදුවේ පාවිච්චි කරන Assertions කිහිපයක්:

  • assertEqual(a, b): a සහ b සමානද කියලා බලනවා.
  • assertNotEqual(a, b): a සහ b සමාන නැද්ද කියලා බලනවා.
  • assertTrue(x): x True ද කියලා බලනවා.
  • assertFalse(x): x False ද කියලා බලනවා.
  • assertIsNone(x): x None ද කියලා බලනවා.
  • assertIsNotNone(x): x None නොවේද කියලා බලනවා.
  • assertIn(a, b): a කියන එක b ඇතුලේ තියෙනවද කියලා බලනවා (b iterable එකක් නම්).
  • assertNotIn(a, b): a කියන එක b ඇතුලේ නැද්ද කියලා බලනවා.
  • assertRaises(exception, callable, ...): යම් function එකක් run කරනකොට exception එකක් raise වෙනවද කියලා බලනවා.

හරි, දැන් අපි මේ theories ටික practice එකට ගමු!

පියවරෙන් පියවර Unit Test ලියමු (Let's Write Unit Tests Step-by-Step)

මෙන්න මේ section එක තමයි වැඩේට බහින තැන. අපි සරල Python functions කිහිපයකට tests ලියමු.

අපේ Simple Function එක (Our Simple Function)

මුලින්ම, අපි test කරන්න යන code එක ලියාගමු. calculator.py කියලා file එකක් හදලා මේ code එක ඒකේ paste කරන්න:

# calculator.py

def add(a, b):
    """දෙකක් එකතු කරයි."""
    return a + b

def subtract(a, b):
    """එකකින් අනෙක අඩු කරයි."""
    return a - b

def multiply(a, b):
    """දෙකක් වැඩි කරයි."""
    return a * b

def divide(a, b):
    """එකක් අනෙකෙන් බෙදයි. බිංදුවෙන් බෙදීම වලක්වයි."""
    if b == 0:
        raise ValueError("Cannot divide by zero!")
    return a / b

මේක බොහොම සරල calculator එකක්. දැන් මේ functions ටික හරියට වැඩ කරනවද කියලා බලන්න tests ලියමු.

Test Case එකක් හදමු (Creating a Test Case)

Test cases ලියන එක හොඳම පුරුද්ද තමයි, test කරන්න යන file එකට සමාන නමක් දීලා, test_ කියලා මුලට දාන එක. ඒ කියන්නේ calculator.py ට අපි test_calculator.py කියලා file එකක් හදමු.

test_calculator.py file එක හදලා මේ code එක ඒකේ දාන්න:

# test_calculator.py

import unittest
import calculator # අපේ calculator.py file එක import කරගන්නවා

class TestCalculator(unittest.TestCase):
    """calculator module එකේ functions test කරනවා."""

    def test_add(self):
        """add function එක හරියට වැඩ කරනවද කියලා බලනවා."""
        self.assertEqual(calculator.add(5, 3), 8)
        self.assertEqual(calculator.add(-1, 1), 0)
        self.assertEqual(calculator.add(0, 0), 0)
        self.assertEqual(calculator.add(2.5, 3.5), 6.0)
        self.assertEqual(calculator.add(-5, -3), -8)

    def test_subtract(self):
        """subtract function එක හරියට වැඩ කරනවද කියලා බලනවා."""
        self.assertEqual(calculator.subtract(10, 5), 5)
        self.assertEqual(calculator.subtract(5, 10), -5)
        self.assertEqual(calculator.subtract(0, 0), 0)
        self.assertEqual(calculator.subtract(10, 0), 10)
        self.assertEqual(calculator.subtract(-5, -3), -2)

    def test_multiply(self):
        """multiply function එක හරියට වැඩ කරනවද කියලා බලනවා."""
        self.assertEqual(calculator.multiply(2, 3), 6)
        self.assertEqual(calculator.multiply(-1, 5), -5)
        self.assertEqual(calculator.multiply(0, 10), 0)
        self.assertEqual(calculator.multiply(2.5, 2), 5.0)

    def test_divide(self):
        """divide function එක හරියට වැඩ කරනවද කියලා බලනවා."""
        self.assertEqual(calculator.divide(10, 2), 5)
        self.assertEqual(calculator.divide(7, 2), 3.5)
        self.assertEqual(calculator.divide(-10, 5), -2)
        self.assertEqual(calculator.divide(0, 5), 0)

    def test_divide_by_zero(self):
        """බිංදුවෙන් බෙදීමකදී ValueError එකක් raise වෙනවද කියලා බලනවා."""
        with self.assertRaises(ValueError):
            calculator.divide(10, 0)


if __name__ == '__main__':
    unittest.main() # මේක tests හොයාගෙන run කරන්න උදව් වෙනවා

මේ code එක දිහා හොඳට බලන්න:

  • මුලින්ම unittest module එක සහ අපි test කරන්න යන calculator module එක import කරගෙන තියෙනවා.
  • TestCalculator කියලා class එකක් හදලා තියෙනවා, ඒක unittest.TestCase එකෙන් inherit වෙනවා.
  • ඒ class එක ඇතුලේ test_add, test_subtract වගේ methods තියෙනවා. මේ methods නිතරම test_ කියන prefix එකෙන් පටන් ගන්න ඕන. unittest module එක automatic tests විදිහට මේවා identify කරගන්නේ ඒ විදිහටයි.
  • සෑම test method එකක් ඇතුළතම, අපි self.assertEqual() වගේ assertions පාවිච්චි කරලා, function එකෙන් ලැබෙන ප්‍රතිඵලය අපි බලාපොරොත්තු වෙන ප්‍රතිඵලයමද කියලා check කරනවා.
  • test_divide_by_zero කියන test එකේදී, අපි with self.assertRaises(ValueError): කියන assertion එක පාවිච්චි කරලා තියෙනවා. මේකෙන් කරන්නේ, calculator.divide(10, 0) කියන එක run කරනකොට ValueError එකක් raise වෙනවද කියලා බලන එක. අපේ divide function එක බිංදුවෙන් බෙදනකොට ValueError එකක් raise කරන නිසා, මේ test එක pass වෙනවා.
  • අවසානයේ තියෙන if __name__ == '__main__': unittest.main() කියන block එකෙන් කරන්නේ, අපි මේ file එක direct run කරනකොට, unittest test runner එක automatically tests හොයාගෙන run කරන එකයි.

Tests Run කරමු (Running the Tests)

දැන් අපි tests run කරමු. ඔයාගේ terminal එක open කරලා, calculator.py සහ test_calculator.py files දෙකම තියෙන directory එකට ගිහින් මේ command එක run කරන්න:

python -m unittest test_calculator.py

හරි නම්, ඔයාට මේ වගේ output එකක් පෙනෙයි:


.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

..... කියන්නේ test methods 5ක් run වුනා කියන එක. ඒ හැම එකක්ම pass වුන නිසා OK කියලා පෙන්නනවා. නියමයි නේද? 🎉

තවත් විදිහකට, ඔයාට terminal එකේම unittest discover mode එක පාවිච්චි කරන්න පුළුවන්. ඒකෙන් දැනට තියෙන directory එකේ තියෙන හැම test_*.py file එකක්ම හොයාගෙන tests run කරනවා. මේක ලොකු project එකකදී හරිම ප්‍රයෝජනවත්.

python -m unittest discover

Test Failures තේරුම් ගැනීම සහ Coverage (Understanding Test Failures & Coverage)

Test එකක් Fail වුනොත් (If a Test Fails)

හරි, දැන් අපි හිතමු අපේ code එකේ වැරැද්දක් තියෙනවා කියලා. අපි calculator.py file එකේ subtract function එක පොඩ්ඩක් වෙනස් කරමු, වැරැද්දක් එන විදිහට:

# calculator.py (වැරදි විදිහට වෙනස් කරා)

def subtract(a, b):
    """එකකින් අනෙක අඩු කරයි."""
    return a + b # Oops! අඩු කරනව වෙනුවට එකතු කරනවා!

දැන් ආයෙත් tests run කරමු:

python -m unittest test_calculator.py

මෙන්න මේ වගේ output එකක් ඔයාට පෙනෙයි:


.F...
======================================================================
FAIL: test_subtract (__main__.TestCalculator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/your/project/test_calculator.py", line 25, in test_subtract
    self.assertEqual(calculator.subtract(10, 5), 5)
AssertionError: 15 != 5

----------------------------------------------------------------------
Ran 5 tests in 0.001s

FAILED (failures=1)

බය වෙන්න එපා! Test Failure එකක් කියන්නේ නරක දෙයක් නෙමෙයි. ඒකෙන් පෙන්නන්නේ අපේ code එක අපි බලාපොරොත්තු වෙන විදිහට වැඩ කරන්නේ නෑ කියලා. මේ output එක දිහා හොඳට බලමු:

  • .F...: මෙතන F එකෙන් කියන්නේ එක test එකක් Fail වුනා කියලා. (. කියන්නේ Pass).
  • FAIL: test_subtract (__main__.TestCalculator): මේකෙන් පෙන්නනවා test_subtract කියන method එක Fail වුනා කියලා.
  • Traceback: මේකෙන් පෙන්නනවා error එක ආවේ කොතනින්ද කියලා. අපේ test_calculator.py file එකේ 25 වෙනි line එකේ තියෙන self.assertEqual(calculator.subtract(10, 5), 5) කියන line එකෙන් තමයි error එක ආවේ.
  • AssertionError: 15 != 5: මේක තමයි ප්‍රධානම කොටස. අපි බලාපොරොත්තු වුනේ 5 ලැබෙයි කියලා (assertEqual(..., 5)), ඒත් calculator.subtract(10, 5) එකෙන් 15 ලැබිලා තියෙනවා. ඒක නිසා 15 කියන එක 5 ට සමාන නැති නිසා Assertion එක Fail වෙලා.

මේ traceback එකෙන් අපිට හොඳටම පැහැදිලියි මොකද වුනේ කියලා. subtract function එකේ වැරැද්දක් තියෙනවා, ඒක + කරනවා වෙනුවට - කරන්න ඕනේ.

දැන් අපි calculator.py එකේ subtract function එක ආයෙත් හරි විදිහට වෙනස් කරලා (return a - b) tests run කරොත්, ආයෙත් OK කියලා එයි.

Test Coverage (Test Coverage)

Test Coverage කියන්නේ, අපේ code base එකෙන් කොච්චර ප්‍රමාණයක් tests මගින් cover කරලා තියෙනවද කියලා බලන එක. උදාහරණයක් විදිහට, අපේ calculator.py file එකේ add, subtract, multiply, divide functions හතරම අපි test කරලා තියෙනවා. ඒ කියන්නේ අපේ coverage එක හොඳ මට්ටමක තියෙනවා.

නමුත් හිතන්න, අපේ calculator.py එකට අලුතින් function එකක් දැම්මා කියලා:

# calculator.py (අලුත් function එකක් එකතු කළා)

def power(base, exp):
    """බලයට උසස් කරයි."""
    return base ** exp

අපි මේ power function එකට test එකක් ලිව්වේ නැත්නම්, අපේ test coverage එක 100% ක් වෙන්නේ නෑ. මොකද, code එකේ කොටසක් tests වලින් cover වෙලා නැහැ.

හොඳ test coverage එකක් තියාගන්න එක වැදගත් වුනත්, 100% coverage කියන්නේ හැමදාම 100% bug-free code එකක් කියලා නෙමෙයි. සමහර වෙලාවට tests ලියලා තියෙන්න පුළුවන්, ඒත් ඒ tests වලින් edge cases හරි, complex scenarios හරි cover කරලා නැති වෙන්න පුළුවන්.

Python වල coverage.py වගේ tools පාවිච්චි කරලා test coverage එක බලන්න පුළුවන්. ඒකෙන් පෙන්නනවා code එකේ මොන lines ද tests වලින් cover වෙලා නැත්තේ කියලා.

හොඳම පුරුදු සහ TDD (Best Practices and TDD)

Effective Test Writing (ඵලදායී Test ලිවීම)

හොඳ tests ලියන එකත් කලාවක්. මෙන්න පොඩි tips ටිකක්:

  • Atomic Tests: හැම test method එකක්ම එක දෙයක් විතරක් test කරන්න ඕන. test_add එක ඇතුලේ add function එක විතරක් test කරන්න. subtract test කරන්න එපා. මේකෙන් test failure එකක් ආවම මොකද වුනේ කියලා හොයාගන්න ලේසියි.
  • Descriptive Test Names: Test methods වලට හොඳ, පැහැදිලි නම් දෙන්න. test_add_positive_numbers, test_divide_by_zero_raises_error වගේ නම් වලින් test එක මොනවද කරන්නේ කියලා පැහැදිලි වෙනවා.
    • Arrange: Test එක run කරන්න අවශ්‍ය දේවල් සකස් කරන්න (variables initialize කරන එක, objects හදන එක).
    • Act: test කරන්න යන function එක හරි, method එක හරි call කරන්න.
    • Assert: ප්‍රතිඵලය අපිට ඕන විදිහටම ආවද කියලා check කරන්න (assertions පාවිච්චි කරලා).
  • Test Edge Cases: සාමාන්‍ය inputs විතරක් නෙමෙයි, විශේෂ අවස්ථාත් test කරන්න. Negative numbers, Zero, Empty lists, Max/Min values වගේ දේවල් test කරන්න.

ARRANGE-ACT-ASSERT (AAA) Pattern:අපේ test_add එකේ මේක පැහැදිලිව පේනවා:

    def test_add(self):
        # Arrange (අවශ්‍ය දෙයක් නෑ මේ සරල එකට)
        # Act
        result = calculator.add(5, 3)
        # Assert
        self.assertEqual(result, 8)

TDD (Test-Driven Development) වලට පොඩි හැඳින්වීමක්

TDD කියන්නේ software development methodology එකක්. මේකේදී code ලියන්න කලින් test ලියනවා. හරියට 'හදන දේට වැඩිය හදන එක හරිද කියලා බලන එක' වගේ.

TDD වල ප්‍රධාන පියවර තුනක් තියෙනවා:

  1. RED: මුලින්ම, අලුත් feature එකකට හරි, bug එකකට හරි අදාළව, fail වෙන test එකක් ලියන්න. මේ මොහොතේදී ඒ feature එකට code එකක් නැති නිසා, මේ test එක fail වෙන්නම ඕන. (මොකද තාම code එක ලිව්වේ නෑනේ 😉)
  2. GREEN: ඊට පස්සේ, අර fail වුන test එක pass කරන්න පුළුවන් අවම code ප්‍රමාණය ලියන්න. මේ වෙලාවේදී code එක ලස්සනට හරි, efficient විදිහට හරි ලියන එක ගැන හිතන්න එපා. Test එක pass වෙන එක විතරයි අරමුණ.
  3. REFACTOR: දැන් test එක pass වෙනවා නම්, දැන් තමයි ලියපු code එක optimize කරන්න, clean කරන්න, සහ refactor කරන්න හොඳම වෙලාව. මේ වෙලාවේදී code එක වෙනස් කලත් tests ටික pass වෙනවා නම්, code එක හරි විදිහට වැඩ කරනවා කියලා අපිට විශ්වාසයි.

මේ RED-GREEN-REFACTOR cycle එක දිගටම කරගෙන යනවා. මේක නම් නියම විදිහක්, මොකද මේකෙන් අපේ code එකේ ගුණාත්මකභාවය වැඩි වෙනවා වගේම, code එකට අවශ්‍යම දේ විතරක් ලියන්නත් අපිට පුරුදු වෙනවා.

අවසානයට (Conclusion)

අපි මේ blog post එකෙන් Python වල Unit Testing වලට අත ගහපු හැටි, unittest module එක පාවිච්චි කරන හැටි, tests ලියන හැටි, run කරන හැටි, failures තේරුම් ගන්න හැටි, සහ TDD වගේ best practices ගැන ඉගෙන ගත්තා.

Software Development වලදී Testing කියන්නේ අත්‍යවශ්‍ය දෙයක්. ඒක අපේ code එකේ විශ්වාසනීයත්වය වැඩි කරනවා වගේම, අනාගතයේදී එන්න පුළුවන් ලොකු ලොකු ගැටළු වලක්වා ගන්නත් උදව් වෙනවා.

ඉතින්, ඔයාලත් මේක implement කරන්න බලන්න. ඔයාලගේ project වලට tests එකතු කරන්න. මුලදී අමාරු වගේ පෙනුනත්, ටිකෙන් ටික ඒක ඔයාලගේ development process එකේ කොටසක් බවට පත් වෙයි.

මේ ගැන ඔයාලගේ අදහස්, ප්‍රශ්න හරි, ඔයාලා TDD practice කරනවද නැත්නම් වෙන test frameworks පාවිච්චි කරනවද කියලා Comment එකක් දාන්න. අපි ඊළඟ blog post එකෙන් හමුවෙමු! 👋