Skip to content

Understanding Test Structure

When you first see a unittest test, the structure might look confusing:

import unittest

class TestAddFunction(unittest.TestCase):
    def test_add_positive_numbers(self):
        result = add(2, 3)
        self.assertEqual(result, 5)

If you're new to testing, you don't need to fully understand all these concepts to write effective tests. You can treat it as a recipe and focus on the pattern. However, if you're curious about what's happening under the hood, this explanation will help.

What is self?

self is a Python convention that refers to the current instance of a class. When you see self.assertEqual(result, 5), you're calling the assertEqual method on the test instance.

Think of it this way: - Without self: Python doesn't know where to find assertEqual - With self: Python knows to look for assertEqual in the TestCase class

# ❌ This won't work - Python doesn't know where assertEqual is
assertEqual(result, 5)

# ✅ This works - Python knows to use the TestCase's assertEqual method
self.assertEqual(result, 5)

What is a Class?

A class is like a blueprint or template. In testing:

class TestAddFunction(unittest.TestCase):
    # This is the blueprint for add function tests
    pass

When you run unittest.main(), Python creates an instance of this class for each test method and runs them.

What is unittest.TestCase?

unittest.TestCase is a class provided by Python's unittest module. When you write:

class TestAddFunction(unittest.TestCase):

You're saying: "Create a new test class that inherits all the testing capabilities from TestCase."

This inheritance gives your class access to: - Assertion methods (assertEqual, assertTrue, etc.) - Test setup and teardown capabilities - Test discovery mechanisms - Result reporting

Why This Structure?

This structure might seem complex, but it provides several benefits:

1. Organisation

Tests are grouped into logical classes:

class TestCalculator(unittest.TestCase):
    def test_add(self):
        pass

    def test_subtract(self):
        pass

    def test_multiply(self):
        pass

2. Shared Setup

You can share setup code across tests:

class TestDatabase(unittest.TestCase):
    def setUp(self):
        # This runs before each test
        self.db = create_test_database()

    def test_insert(self):
        # self.db is available here
        self.db.insert("data")

    def test_query(self):
        # self.db is available here too
        results = self.db.query()

3. Test Discovery

unittest can automatically find and run all methods starting with test_:

class TestExample(unittest.TestCase):
    def test_this_runs(self):
        pass  # ✅ Found and executed

    def helper_method(self):
        pass  # ❌ Ignored (doesn't start with test_)

    def test_this_also_runs(self):
        pass  # ✅ Found and executed

The Pattern in Practice

Here's the pattern you'll use repeatedly:

import unittest                              # 1. Import the testing framework

class Test[WhatYoureTesting](unittest.TestCase):  # 2. Create test class
    """Description of what you're testing."""

    def test_[specific_scenario](self):      # 3. Define test methods
        """Description of this specific test."""
        # Arrange: Set up your test data
        input_value = 10

        # Act: Call the function you're testing
        result = my_function(input_value)

        # Assert: Check the result
        self.assertEqual(result, expected_value)

Common Questions

Do I need to understand object-oriented programming?

Not to get started! You can write effective tests by following the pattern. As you become more comfortable, understanding classes and objects will help you write more sophisticated tests.

Why can't it be simpler?

Python's assert statement is simpler:

assert add(2, 3) == 5

However, unittest's approach provides: - Better error messages - Test organisation - Setup and teardown capabilities - Integration with test runners and CI/CD tools - Consistent patterns across projects

When should I learn more about classes?

You'll naturally pick up class concepts as you write more tests. Consider learning more about Python classes when: - You want to share setup code between tests - You're working on larger projects with many tests - You want to understand test fixtures in depth - You're curious about how testing frameworks work

Key Takeaways

  • self gives you access to TestCase methods like assertEqual
  • Classes group related tests together
  • unittest.TestCase provides all the testing capabilities
  • You don't need to master these concepts to write good tests
  • The pattern is consistent - follow it and you'll be fine

Next Steps