No history yet

Unit Testing

Testing the Smallest Pieces

Imagine you're building a car. You wouldn't assemble the entire vehicle and then test if the radio works. You'd test the radio, the engine, and the brakes individually long before putting them all together. This way, if something is broken, you know exactly which part is to blame.

Programming works the same way. Instead of running your entire application to see if a tiny function does its job, you can test that function in isolation. This is called unit testing.

unit test

noun

A test that verifies the functionality of a specific, isolated piece of code, such as a single function or method.

Python comes with a built-in framework for this called unittest. It provides a structured way to write and run tests, helping you catch bugs early and ensure your code works as expected. It's like a specialized toolkit for checking your work, one component at a time.

Writing Your First Test

Let's start with a simple function we want to test. Suppose we have a file named calculator.py with a function that adds two numbers.

# calculator.py
def add(x, y):
    """Adds two numbers together."""
    return x + y

To test this, we create a separate file. By convention, it's often named test_ followed by the name of the file we're testing. So, we'll create test_calculator.py.

Inside this test file, we'll import the unittest library and the add function we want to test. Then, we create a class that inherits from unittest.TestCase. This class will hold all our tests for the calculator.

# test_calculator.py
import unittest
from calculator import add

class TestCalculator(unittest.TestCase):
    # Test methods will go here
    pass

Within our TestCalculator class, we write methods to test specific scenarios. Each test method's name must start with test_. Inside these methods, we use special assertion methods from unittest.TestCase to check if our code's output matches the expected outcome.

An assertion is a statement that checks if a condition is true. If it is, the test passes. If not, the test fails.

Let's add a test to check if our add function works with positive numbers. We'll use the self.assertEqual(a, b) method, which checks if a is equal to b.

# test_calculator.py
import unittest
from calculator import add

class TestCalculator(unittest.TestCase):
    def test_add_positive_numbers(self):
        """Test adding two positive numbers."""
        result = add(5, 10)
        self.assertEqual(result, 15)

Assertions and Outcomes

The unittest framework gives us a whole family of assertion methods to check for different conditions. They are the heart of any unit test, as they determine whether a test passes or fails.

Assertion MethodChecks If...
assertEqual(a, b)a == b
assertNotEqual(a, b)a != b
assertTrue(x)bool(x) is True
assertFalse(x)bool(x) is False
assertIsNone(x)x is None
assertIn(a, b)a is in b
assertRaises(exc, func, *args)Calling func(*args) raises exception exc

Good tests cover multiple scenarios, including edge cases. Let's add a few more tests to our test_calculator.py file to handle negative numbers and zeros.

# test_calculator.py
import unittest
from calculator import add

class TestCalculator(unittest.TestCase):
    def test_add_positive_numbers(self):
        self.assertEqual(add(5, 10), 15)

    def test_add_negative_numbers(self):
        self.assertEqual(add(-5, -10), -15)

    def test_add_mixed_numbers(self):
        self.assertEqual(add(5, -10), -5)

    def test_add_with_zero(self):
        self.assertEqual(add(0, 5), 5)

Running Your Tests

Now that we have our test file, how do we run it? The unittest module includes a test discovery tool that can find and run tests automatically. Open your terminal, navigate to the directory containing your project files, and run the following command:

python -m unittest discover

This command looks for files named test*.py in the current directory and subdirectories, finds all the test methods inside them, and runs them. If all assertions pass, you'll see a simple OK message. If any test fails, unittest will provide a detailed report showing which test failed and why.

This cycle of writing code, writing tests, and running them is fundamental to building reliable software. It gives you the confidence to make changes, knowing your tests will catch any accidental breakages.

Ready to check your understanding?

Quiz Questions 1/5

What is the primary purpose of unit testing?

Quiz Questions 2/5

In Python's unittest framework, what class must your test classes inherit from?

By writing small, focused tests for each piece of your code, you create a safety net that makes your applications more robust and easier to maintain.