Testing checks that code still behaves correctly after changes.
A useful test calls a small unit of code with known input and compares the result with the expected output.
Start with normal cases, then add edge cases and bugs you never want to repeat.
The unittest module is built into Python and is enough for learning test structure.
import unittest
def total(items):
return sum(items)
class TotalTest(unittest.TestCase):
def test_adds_items(self):
self.assertEqual(total([10, 20, 5]), 35)
unittest.main()
The test passes only when total returns the expected result.
A good test suite checks what should fail, not only what should succeed.
import unittest
def first_name(full_name):
if not full_name.strip():
raise ValueError("name required")
return full_name.split()[0]
class NameTest(unittest.TestCase):
def test_empty_name_fails(self):
with self.assertRaises(ValueError):
first_name(" ")
unittest.main()
The test confirms that invalid input fails in a predictable way.
Use a unit test for an isolated decision, an integration test for a real boundary such as SQLite or the filesystem, and a small end-to-end test for a critical user workflow. The broadest test is not automatically the most useful because failures are slower and harder to localize.
| Boundary | Evidence |
|---|---|
| Unit | A function or object returns the correct result for normal and boundary inputs |
| Integration | Your adapter works with the real library, database, file format, or service contract |
| End to end | The assembled application completes one important workflow |
Prefer a small fake when it represents the collaborator clearly. Use unittest.mock when the interaction itself matters, and assert only calls that are part of the public contract rather than every internal step.
from unittest.mock import Mock
sender = Mock()
sender.send.return_value = "message-42"
message_id = sender.send("maya@example.com", "Task ready")
sender.send.assert_called_once_with("maya@example.com", "Task ready")
assert message_id == "message-42"
A separate integration test should prove the real sender adapter handles its external boundary.
0 of 2 checked
Try this next
0 of 3 completed
Yes, for small functions. Tests build confidence and make refactoring less risky.
It is enough to learn core testing. Many projects later use pytest for a shorter style.
Test calculations, parsing, validation, and functions where a wrong result would be hard to notice by reading.
Explore 500+ free tutorials across 20+ languages and frameworks.