Pyunit or Unitest

Used to test a unit of source code

Features:
  1. PyUnit is very simple yet very flexible
  2. Test report formats generated: XML, unittest xml reports
  3. Supports fixtures, test cases, test suites and test runners for automation of testing

How Code is tested?
  1. Create the class which need to be tested
  2. Create seperate module(which import above class) & calls its functions for testing.

Examples

Ex1: Simple Test


# car.py        # Class to test
class car:
    name = ""

    def set(self, name) -> bool():
        self.name = name
        return True

    def get(self):
        return self.name
            
if __name__ == '__main__':
    c = car()
    if c.set('BMW') == True:
        print('car BMW has been added')
    else:
        print('car add failed')
    print('car is ', c.get())

> python.exe .\car.py
car BMW has been added
car is  "BMW"


# car_testing.py        # Write the unittest module(which will test car class)
import unittest
import car as c     # import the class which we want to test

class car_testing(unittest.TestCase):       # This class should inherit unittest.TestCase
    car = c.car()       # Create object of car class

    """
    Note unittest module executes the test functions in the order of their name
    since we want our set() test to execute first, we have named our test case
    functions as test_0_set() and test_1_get()
    """
    def test_0_set(self):           # Any method which starts with ``test_`` will considered as a test case.
        print ("\nStart set() test..")
        self.car.set('Audi')
        print("Finish set() test\n")

    def test__1_get(self):
        print("\nStart get() test..")
        # 5. if 2 parameters does not match test case will fail
        var = self.car.get()
        print (var)
        self.assertEqual(var, 'Audi')
        print("Finish get() test\n")

if __name__ == '__main__':
    unittest.main()
    
> python.exe .\car_testing.py

Start set() test..
Finish set() test

.
Start get() test..
Audi
Finish get() test

.
----------------------------------------------------------------------
Ran 2 tests in 0.009s
OK
                

Ex2: Writing Mock object

How it works?
1. We create mock object.
2. Assign mock object a function call which need to be mocked. For example:
requests.get(): We will mock this API, since this can take n/w delay which is not needed while testing
3. Write assert() and compare return value from mock.

// File to be tested
# weather.py
import requests

def get_weather(city):
    response = requests.get(f"https://api.weather.com/{city}")
    if response.status_code == 200:
        return response.json()["weather"]
    return None

// test module. This will test weather.get_weather()
import unittest
from unittest.mock import patch
from weather import get_weather

class TestWeather(unittest.TestCase):

    """
    weather.requests.get = { status_code=200, json.return_value = {"weather": "Sunny"} }
                            mock object = mock_get = mock response
    """
    # The @patch decorator is used to replace an object or function with a mock object during the test.
    # This replaces weather.requests.get with a mock object(mock_get)
    @patch("weather.requests.get")
    def test_get_weather_success(self, mock_get):
        mock_response = mock_get.return_value       #Create a local reference to the mock object
        mock_response.status_code = 200     # Setting values in the mock response
        mock_response.json.return_value = {"weather": "Sunny"}  # Setting values in the mock response

        city = "NewYork"
        result = get_weather(city)      #Call the function to test

        self.assertEqual(result, "Sunny")   # Test. fail on 2 results are unequal

        # Test. Ensures that requests.get was called exactly once with the expected URL
        # If mock_get was never called or called multiple times, the test fails.
        mock_get.assert_called_once_with(f"https://api.weather.com/{city}") 

    @patch("weather.requests.get")
    def test_get_weather_failure(self, mock_get):
        # Mock response with a failure status code
        mock_response = mock_get.return_value
        mock_response.status_code = 404

        city = "InvalidCity"
        result = get_weather(city)

        # Assertions
        self.assertIsNone(result)
        mock_get.assert_called_once_with(f"https://api.weather.com/{city}")

if __name__ == "__main__":
    unittest.main()

> python test.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK