PC

Testing and Debugging Notes

Review from Last Class

  • The previous class involved writing a Python program to find unique combinations of 3 numbers from a list that sum to a target number.

  • The task included providing doctests for specific test cases.

  • Example:

    • List: [1, 2, 3, 4, 5, 6, 7, 8, 9]

    • Target: 12

    • Combinations: [(1, 3, 8), (1, 4, 7), (1, 2, 9), (1, 5, 6), (3, 4, 5), (2, 3, 7), (2, 4, 6)]

  • Example:

    • List: [1, 2, 3, 4, 5, 6, 7, 8, 9]

    • Target: 17

    • Combinations: [(4, 5, 8), (2, 6, 9), (3, 5, 9), (2, 7, 8), (4, 6, 7), (3, 6, 8), (1, 7, 9)]

Unit Testing

  • Unit testing involves testing individual units or components of code to ensure proper functioning.

  • The correct approach to unit testing is to test all code parts individually.

Key Aspects of Unit Testing:

  • All tests should be within a subclass TestCase from the unittest library.

  • Each test is a function named starting with test_.

  • Assert methods are used to check for expected results.

  • The setUp() method is called before each test, used to define elements needed for the tests.

  • The tearDown() method is called after each test, used to reset definitions after each test.

Assert Methods:

  • assertTrue: Succeeds if the expression is true.

  • assertFalse: Succeeds if the expression is false.

  • assertEqual, assertNotEqual: Checks equality and inequality.

  • assertAlmostEqual: Used for comparing floating-point numbers.

  • assertNotAlmostEqual

Running Tests in VSCode:

  1. Create a workspace where all tests are located.

  2. Configure tests in the testing tab.

  3. Select unittest -> Root directory -> test_*.py.

Troubleshooting:

  • If test files are not appearing, reload the window:

    • Control + Shift + p (Windows)

    • Command + Shift + p (Mac)

    • Options: "Reload window", "Configure python tests"

  • If the explorer is not working, run tests in the terminal:

    • python -m unittest discover -v -s . -p "test_*.py"

  • For test discovery errors, reload the window.

Tips:

  • Create a new folder for each exercise.

  • Configure Python tests in each exercise by changing the folder in the configuration.

  • Each exercise should have at least two files: class_name.py and test_class_name.py.

Exercises

  • Exercise 1 - Calculator Class:

    • Step 1: Create tests for all methods (add, subtract, multiply, divide) using pytest and unittest.

    • Step 2: Implement the functions to make tests pass.

    • Test cases provided for each method with expected results.

    • For example:

      • add(1, 3) should return 4.

      • subtract(8, 7) should return 1.

      • multiply(8, 3) should return 24.

      • divide(10, 5) should return 2.

  • Exercise 2 - Temperature Converter Class:

    • Step 1: Create tests for all methods (celsius_to_fahrenheit, fahrenheit_to_celsius, celsius_to_kelvin, kelvin_to_celsius) using pytest and unittest.

    • Step 2: Implement the functions to make tests pass.

    • Test cases provided for each method with expected results.

      • celsius_to_fahrenheit(0) should return 32.

      • fahrenheit_to_celsius(32) should return 0.

      • celsius_to_kelvin(25) should return 298.15.

      • kelvin_to_celsius(273.15) should return 0.

PyTest

  • Supports unittests.

  • Allows selecting specific tests using the -k flag.

  • Provides mark tests:

    • @pytest.mark.skip: Skips the test.

    • @pytest.mark.xfail: Marks the test as expected to fail.

  • Filter by marked tests using the -m flag.

  • Run from the last failed test using the --lf flag.

Mocking

  • Mock objects imitate real objects but with fake data.

  • Commonly used for API calls.

  • Decouples tested code from other calls.

  • Example:

    • Mocking an expensive API call to speed up tests.

      import time
      def compute(x):
          response = expensive_api_call()
          return response + x
      
      def expensive_api_call():
          time.sleep(1000) # takes 1,000 seconds
          return 123
      
      import unittest
      import compute_expensive
      
      class TestComputeExpensive(unittest.TestCase):
          def test_compute(self):
              self.assertEqual(compute_expensive.compute(1), 124)
      
      import unittest
      from unittest.mock import patch
      import compute_expensive
      
      class TestComputeExpensive(unittest.TestCase):
          @patch('compute_expensive.expensive_api_call', return_value=123)
          def test_compute(self, mock_expensive_api_call):
              # calls the compute function mocking the expensive_api_call
              self.assertEqual(124, compute_expensive.compute(1))
              # asserts that expensive_api_call was called once
              mock_expensive_api_call.assert_called_once()
      

Code Coverage

  • Tracks which lines of code are executed during tests.

  • Provides a report describing test coverage.

  • Tool to gain insight into what the tests are doing.

  • Not the sole definition of a good test suite.

How to Run Test Coverage:

  • Right-click in the folder where test folders are located and select "Run Tests with Coverage".

  • If a No module named 'coverage' error occurs, install the coverage package:

    • pip install coverage or pip3 install coverage if pip is not working.

  • The percentage of code covered appears next to the file name once configured correctly.

Exercises

  • Exercise 3 - Bank Account Class:

    • Step 1: Create test cases for BankAccount class methods (__init__, deposit, withdraw, get_balance).

    • Step 2: Implement failing tests.

    • Step 3: Implement the code to make the tests pass.

  • Exercise 4 - Counter Class:

    • Step 1: Create test cases for Counter class methods (__init__, increment, decrement, reset, get_value).

    • Step 2: Implement failing tests.

    • Step 3: Implement the code to make the tests pass.

  • Exercise 5 - To-Do List Class:

    • Step 1: Create test cases for ToDoList class methods (__init__, add_task, remove_task, get_tasks).

    • Step 2: Implement failing tests.

    • Step 3: Implement the code to make the tests pass.

  • Exercise 6 - Shapes:

    • Create tests using inheritance and abstract methods for shapes (rectangle, circle, square).

    • Each shape should have perimeter and area calculations.