Skip to main content

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: int to parameters and -> int to return types
  • Doctests - Include executable examples in your docstrings
  • Parametrized tests - Use @pytest.mark.parametrize to 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:

  1. Write a test
  2. Make it fail (and understand the failure)
  3. Write enough code to make it pass
  4. Refactor

As your code becomes more complex, this discipline becomes more valuable. Tests give you confidence that your changes don't break existing functionality.