Integers
In this chapter, we'll learn about working with integers in Python while practicing TDD. We'll build an add function and along the way learn about:
- Writing tests for mathematical functions
- Function documentation with docstrings
- Type hints for better code documentation
- Named arguments for improved readability
Write the test first
Create a new directory for this chapter and add a test file test_adder.py:
from adder import add
def test_add():
got = add(2, 2)
want = 4
assert got == want
Try to run the test
pytest test_adder.py
You'll get an error:
ModuleNotFoundError: No module named 'adder'
Write the minimal code to make the test run
Create a file called adder.py:
def add(x, y):
pass
Now run the test again:
AssertionError: assert None == 4
The test fails because we're returning None (implicitly).
Write enough code to make it pass
def add(x, y):
return x + y
Run the test - it should pass now.
Refactor
There's not much to refactor with such simple code. Let's instead make our code clearer by adding documentation and type hints.
Docstrings
Python uses docstrings to document functions. Add a docstring to your function:
def add(x, y):
"""Add two integers together and return the sum.
Args:
x: The first number.
y: The second number.
Returns:
The sum of x and y.
"""
return x + y
Docstrings are strings at the beginning of a function, class, or module. They're accessible via the __doc__ attribute and used by tools like help().
Type hints
Python supports optional type hints that make code more self-documenting:
def add(x: int, y: int) -> int:
"""Add two integers together and return the sum.
Args:
x: The first number.
y: The second number.
Returns:
The sum of x and y.
"""
return x + y
Type hints don't change runtime behavior, but they:
- Make code easier to read
- Enable static type checking with tools like
mypy - Help IDEs provide better autocomplete
Examples are documentation
Let's add an example to our docstring using the doctest format:
def add(x: int, y: int) -> int:
"""Add two integers together and return the sum.
Args:
x: The first number.
y: The second number.
Returns:
The sum of x and y.
Examples:
>>> add(1, 1)
2
>>> add(5, 7)
12
"""
return x + y
You can run these examples as tests with:
python -m doctest adder.py -v
Write more tests
Let's add more test cases to verify our function works with different inputs:
from adder import add
def test_add():
got = add(2, 2)
want = 4
assert got == want
def test_add_negative_numbers():
got = add(-1, -1)
want = -2
assert got == want
def test_add_zero():
got = add(0, 0)
want = 0
assert got == want
def test_add_large_numbers():
got = add(1_000_000, 2_000_000)
want = 3_000_000
assert got == want
Notice the use of underscores in large numbers (1_000_000). Python allows underscores in numeric literals for readability.
Using parametrize to reduce duplication
When you have many similar tests, pytest's @pytest.mark.parametrize decorator can help:
import pytest
from adder import add
@pytest.mark.parametrize("x, y, expected", [
(2, 2, 4),
(-1, -1, -2),
(0, 0, 0),
(1_000_000, 2_000_000, 3_000_000),
(5, -3, 2),
])
def test_add(x, y, expected):
assert add(x, y) == expected
This runs the same test with different inputs, making it easy to add new test cases.
A more complex example: Subtract
Let's practice the full TDD cycle with a subtract function.
Write the test first
def test_subtract():
got = subtract(10, 5)
want = 5
assert got == want
Make it compile
def subtract(x: int, y: int) -> int:
pass
Make it pass
def subtract(x: int, y: int) -> int:
"""Subtract y from x and return the result.
Args:
x: The number to subtract from.
y: The number to subtract.
Returns:
The difference (x - y).
"""
return x - y
Named arguments
Python supports named (keyword) arguments, which can make function calls more readable:
# Positional arguments - order matters
result = subtract(10, 5) # 10 - 5 = 5
# Named arguments - order doesn't matter
result = subtract(x=10, y=5) # 10 - 5 = 5
result = subtract(y=5, x=10) # Still 10 - 5 = 5
Named arguments are especially useful when:
- A function has many parameters
- Parameters are of the same type and easy to confuse
- You want to skip optional parameters
Integer operations
Python supports all standard mathematical operations on integers:
# Arithmetic operators
a + b # Addition
a - b # Subtraction
a * b # Multiplication
a / b # Division (returns float)
a // b # Floor division (returns int)
a % b # Modulo (remainder)
a ** b # Exponentiation
-a # Negation
# Comparison operators
a == b # Equal
a != b # Not equal
a < b # Less than
a > b # Greater than
a <= b # Less than or equal
a >= b # Greater than or equal
Wrapping up
In this chapter, we've covered:
- Writing and running tests for mathematical functions
- Docstrings - Document your functions with triple-quoted strings
- Type hints - Add
x: intto parameters and-> intto return types - Doctests - Include executable examples in your docstrings
- Parametrized tests - Use
@pytest.mark.parametrizeto test multiple cases - Named arguments - Call functions with
func(x=1, y=2)for clarity - Python's integer operations - The full range of mathematical operators
The TDD process
We continued practicing the TDD discipline:
- Write a test
- Make it fail (and understand the failure)
- Write enough code to make it pass
- Refactor
As your code becomes more complex, this discipline becomes more valuable. Tests give you confidence that your changes don't break existing functionality.