Skip to main content

Unit Test

Debugging 偵錯

assert
    可用在程式的條件測試 assert <condition>, <message> : 如果 condition 為 True,沒有作用;如果為 False,會產生錯誤,並顯示訊息
    x = 5
    assert x == 5, "x should be 5"
    
    assert type(username) == str, "username must be a string"
    prinf debugging
    print("Processing {}".format(basename))
    strace
      Linux strace Command Tutorial for Beginners (8 Examples)
      # Installation on RHEL if it's not installed
      yum install strace
      
      # Tracing system calls made by a program
      strace ./my-program.py
      strace -o my-program.strace ./my-program
      pdb

      功能:

        設定程式中斷點 逐行檢查程式碼 檢查變數 以互動方式評估表達式
        pdb3 myprog.py

        pdb-subcommands

          continue : 繼續執行直到異常的程式碼 print() : 輸出變數的內容
          (Pdb) continue
          ...
          (Pdb) print(row)

          Step 1: Set a breakpoint

          import pdb
          
          
          def add_numbers(a, b):
              pdb.set_trace()  # This will set a breakpoint in the code
              result = a + b
              return result
          
          
          print(add_numbers(3, 4))

          Setp 2: Enter the interactive debugger

            a (args): Show the arguments of the current function.

            b: Manually set a persistent breakpoint while in debugger.

            n (next): Execute the next line within the current function.

            s (step): Execute the current line and stop at the first possible occasion (e.g., in a function that is called).

            c (continue): Resume normal execution until the next breakpoint.

            p (print): Evaluate and print the expression, e.g., p variable_name will print the value of variable_name.

            Pp (pretty-print): Pretty-print the value of the expression.

            q (quit): Exit the debugger and terminate the program.

            r (return): Continue execution until the current function returns.

            tbreak: Manually set a temporary breakpoint that goes away once hit the first time.

            !: Prefix to execute an arbitrary Python command in the current environment, e.g., !variable_name = "new_value" will set variable_name to "new_value".

            Step 3: Inspect variables

            To inspect the variables, simply type the single character, p, then the variable name to see its current value. For instance, if you have a variable in your code named sentiment_score, just type p sentiment_score at the pdb prompt to inspect its value.

            Step 4: Modify variables

            A big advantage of pdb is that you can change the value of a variable directly in the debugger. For example, to change sentiment_score to 0.9, you'd type !sentiment_score = 0.9.

            To confirm these changes, use a or directly probe the value with p <value name>.

             Step 5: Exit the debugger

            When you’re done, simply enter q (quit) to exit the debugger and terminate the program.

            Post-mortem debugging

            python -m pdb your_script.py

             

            Unit Test 單元測試

            • 目的:以隔離主程式的方式,對自訂的函式(function)與方法(method),提供指定的輸入參數與期待的輸出結果,以驗證相關程式碼是否有瑕疵或錯誤。
            • 方法:撰寫額外的測試用程式碼,並使用任一個單元測試模組,例如 unittest, Pytest 或類似用途的其他模組。 
            • 自動化:整合 CI/CD 做到全自動化程式碼單元測試
            Pytest
            unittest

            Methods

            • .assertEqual(a, b) : checks that a == b
            • .assertNotEqual(a, b) : checks that a != b
            • .assertTrue('FOO'.isupper()) : checks that bool(x) is True
            • .assertFalse('Foo'.isupper()) : checks that bool(x) is False 

            Example 1: rearrange.py

            #!/usr/bin/env python3
            
            import re
            
            def rearrange_name(name):
              result = re.search(r"^([\w .]*), ([\w .]*)$", name)
              if result is None:
                return name
              return "{} {}".format(result[2], result[1])

            rearrange_test.py : 

            #!/usr/bin/env python3
            
            import unittest
            
            from rearrange import rearrange_name
            
            class TestRearrange(unittest.TestCase):
                
              def test_basic(self):  # Basic test case
                testcase = "Lovelace, Ada"
                expected = "Ada Lovelace"
                self.assertEqual(rearrange_name(testcase), expected)
            
              def test_empty(self):  # Edge case, such as zero, blank, negative numbers, or extremely large numbers
                testcase = ""
                expected = ""
                self.assertEqual(rearrange_name(testcase), expected)
            
              def test_double_name(self):   # Additional test case
                testcase = "Hopper, Grace M."
                expected = "Grace M. Hopper"
                self.assertEqual(rearrange_name(testcase), expected)
            
              def test_one_name(self):      # Additional test case
                testcase = "Voltaire"
                expected = "Voltaire"
                self.assertEqual(rearrange_name(testcase), expected)
            
            # Run the tests
            unittest.main()

            Tip: 在 Jupyter 環境執行 unittest.main() 時可能會出現錯誤,修正方法是改成unittest.main(argv = ['first-arg-is-ignored'], exit = False))

            The output of the result:

            .
            ----------------------------------------------------------------------
            Ran 4 test in 0.000s
            
            OK

            Example 2: cakefactory.py

            #!/usr/bin/env python3
            
            from typing import List
            
            class CakeFactory:
             def __init__(self, cake_type: str, size: str):
               self.cake_type = cake_type
               self.size = size
               self.toppings = []
            
               # Price based on cake type and size
               self.price = 10 if self.cake_type == "chocolate" else 8
               self.price += 2 if self.size == "medium" else 4 if self.size == "large" else 0
            
             def add_topping(self, topping: str):
                 self.toppings.append(topping)
                 # Adding 1 to the price for each topping
                 self.price += 1
            
             def check_ingredients(self) -> List[str]:
                 ingredients = ['flour', 'sugar', 'eggs']
                 ingredients.append('cocoa') if self.cake_type == "chocolate" else ingredients.append('vanilla extract')
                 ingredients += self.toppings
                 return ingredients
            
             def check_price(self) -> float:
                 return self.price
            
            # Example of creating a cake and adding toppings
            cake = CakeFactory("chocolate", "medium")
            cake.add_topping("sprinkles")
            cake.add_topping("cherries")
            cake_ingredients = cake.check_ingredients()
            cake_price = cake.check_price()
            
            
            cake_ingredients, cake_price

            cakefactory_test.py

            #!/usr/bin/env python3
            
            import unittest
            from cakefactory import CakeFactory
            
            class TestCakeFactory(unittest.TestCase):
             def test_create_cake(self):
               cake = CakeFactory("vanilla", "small")
               self.assertEqual(cake.cake_type, "vanilla")
               self.assertEqual(cake.size, "small")
               self.assertEqual(cake.price, 8) # Vanilla cake, small size
            
             def test_add_topping(self):
                 cake = CakeFactory("chocolate", "large")
                 cake.add_topping("sprinkles")
                 self.assertIn("sprinkles", cake.toppings)
            
             def test_check_ingredients(self):
                 cake = CakeFactory("chocolate", "medium")
                 cake.add_topping("cherries")
                 ingredients = cake.check_ingredients()
                 self.assertIn("cocoa", ingredients)
                 self.assertIn("cherries", ingredients)
                 self.assertNotIn("vanilla extract", ingredients)
            
             def test_check_price(self):
                 cake = CakeFactory("vanilla", "large")
                 cake.add_topping("sprinkles")
                 cake.add_topping("cherries")
                 price = cake.check_price()
                 self.assertEqual(price, 13) # Vanilla cake, large size + 2 toppings
            
            
            # Running the unittests
            unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(TestCakeFactory))

            This results in the output:

            ..F.
            ======================================================================
            FAIL: test_check_price (__main__.TestCakeFactory)
            ----------------------------------------------------------------------
            Traceback (most recent call last):
              File "<ipython-input-9-32dbf74b3655>", line 33, in test_check_price
                self.assertEqual(price, 13) # Vanilla cake, large size + 2 toppings
            AssertionError: 14 != 13
            
            ----------------------------------------------------------------------
            Ran 4 tests in 0.007s
            
            FAILED (failures=1)
            <unittest.runner.TextTestResult run=4 errors=0 failures=1>

            The program calls the TextTestRunner() method, which returns a runner (TextTestResult). It says one failure occurred: the statement self.assertEqual(price, 13) was incorrect, as it should have been 14. How can we correct that part of the test? Update that part of the code to the following:

            import unittest
            
            
            # Fixing the test_check_price method
            class TestCakeFactory(unittest.TestCase):
             # ... Other tests remain the same
            
             def test_check_price(self):
                 cake = CakeFactory("vanilla", "large")
                 cake.add_topping("sprinkles")
                 cake.add_topping("cherries")
                 price = cake.check_price()
                 self.assertEqual(price, 14) # Vanilla cake, large size + 2 toppings
            
            # Re-running the unittests
            unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(TestCakeFactory))

            And now the program works as expected, as the results provide no failures and are:

            .
            ----------------------------------------------------------------------
            Ran 4 test in 0.002s
            
            OK