Testing - CameronAuler/python-devops GitHub Wiki

Testing is crucial in software development to ensure code correctness, reliability, and maintainability. Python provides a built-in unittest module for writing and organizing tests.

Table of Contents

unitest

Unit testing involves testing individual functions or components in isolation. Python’s unittest module follows a structured approach with:

  • Test cases (TestClass with test methods)
  • Assertions (assertEqual(), assertTrue(), etc.)
  • Setup and teardown (setUp() and tearDown())

Basic Unit Test

unittest.TestCase creates a test case class. self.assertEqual() verifies expected results. unittest.main() runs all tests.

import unittest

# Function to test
def add(a, b):
    return a + b

# Test case class
class TestMathOperations(unittest.TestCase):

    def test_addition(self):
        self.assertEqual(add(2, 3), 5)  # Check if 2 + 3 = 5
        self.assertEqual(add(-1, 1), 0)  # Check negative numbers

if __name__ == "__main__":
    unittest.main()
# Output (if all tests pass):
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

unittest Assertions

self.assertEqual(a, b)  # Check if a == b
self.assertNotEqual(a, b)  # Check if a != b
self.assertTrue(condition)  # Check if condition is True
self.assertFalse(condition)  # Check if condition is False
self.assertRaises(Exception, func, *args)  # Check if an exception is raised

Using setUp() & tearDown()

setUp() runs before each test. tearDown() runs after each test. Mainly used for preparing and cleaning up test environments.

class TestExample(unittest.TestCase):

    def setUp(self):
        self.test_data = [1, 2, 3]  # Runs before each test

    def test_length(self):
        self.assertEqual(len(self.test_data), 3)

    def tearDown(self):
        self.test_data = None  # Cleanup

Test Discovery & Organization

For larger projects, organize tests into directories and run them efficiently.

Unit Test Directory Organization

Store tests in a separate tests/ directory.

project/
│── src/
β”‚   β”œβ”€β”€ math_operations.py
│── tests/
β”‚   β”œβ”€β”€ test_math_operations.py
│── main.py

Running Tests with unittest Discovery

Automatically discover and execute all tests.

Run all tests in a directory:

python -m unittest discover tests

Run a specific test file:

python -m unittest tests/test_math_operations.py

Mocking & Patching in Unit Tests

Mocking replaces real objects with fake ones during testing which is useful for testing external dependencies (APIs, databases, file systems).

unittest.mock.Mock

Mainly used for mocking database queries, network requests, or expensive computations.

from unittest.mock import Mock

# Create a mock object
mock_obj = Mock()
mock_obj.some_method.return_value = "Mocked Response"

print(mock_obj.some_method())  # Output: Mocked Response

patch()

patch() replaces a function or object with a mock only within the test scope. @patch("requests.get") replaces requests.get with a mock object. The API request does not actually happen, making tests faster and independent.

import unittest
from unittest.mock import patch
import requests

# Function that makes an API request
def fetch_data():
    response = requests.get("https://api.example.com/data")
    return response.json()

class TestAPI(unittest.TestCase):

    @patch("requests.get")  # Mock requests.get
    def test_fetch_data(self, mock_get):
        mock_get.return_value.json.return_value = {"message": "Success"}  # Mock response

        result = fetch_data()
        self.assertEqual(result["message"], "Success")  # Validate mock response

if __name__ == "__main__":
    unittest.main()
# Output:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Mocking Classes & Methods

Mainly used for simulating class methods without modifying real code.

from unittest.mock import MagicMock

class MyClass:
    def my_method(self):
        return "Real Result"

mock_instance = MagicMock()
mock_instance.my_method.return_value = "Mocked Result"

print(mock_instance.my_method())  # Mocked Result