Lab 3 Test Driven Development

Aims

The aim of this lab is to introduce pytest and the process of Test Driven Development, we will do this in the following way.

  1. Use uv to set up a pytest Python environment.
  2. Write unit tests with pytest.
  3. Incrementally build a Vec3 class (3D vector math) following TDD.

Getting started

We place all our code in a simple project folder with the tests and the class we wish to test. In this case we are building a simple example, typically we would generate a python module or package as we will see in later lectures.

mkdir Vec3Class
cd Vec3Class
mkdir tests

This will create our basic folder structure and all work will be done from the root of the folder.

We can now initialize things with uv

uv init
uv add --dev pytest

You will see the following files have been created

main.py  pyproject.toml README.md  uv.lock

Inspecting the pyproject.toml will show that pytest has been added into a dependency group called dev. This allows us to partition what is needed for users vs developers, see Managing dependencies on the uv website for more details.

We can now check to see if pytest runs. To do this we can run it via uv

uv run pytest
================================================== test session starts ===================================================
platform darwin -- Python 3.13.3, pytest-8.4.1, pluggy-1.6.0
rootdir: /Users/jmacey/tmp/Vec3Class
configfile: pyproject.toml
collected 0 items

================================================= no tests ran in 0.00s ==================================================
➜  Vec3Class git:(main) ✗

As we have no test we have 0 collected items.

TDD recap

TDD Cycle = Red → Green → Refactor

  1. Red: Write a failing test (no implementation yet).
  2. Green: Write the minimum code to make it pass.
  3. Refactor: Clean up / improve while keeping tests passing.

pytest discovery

The first thing we will do is create a test in the tests folder, pytest will search for test recursively from the root folder.

By default, pytest discovers tests by filename and by function/class names using either File discovery :-

  • Looks for files matching the glob patterns:
  • test_*.py
  • *_test.py

Test functions are discovered inside those files, pytest collects using the following criteria :

  • Function names start with test_
  • Test methods inside classes also start with test_

In addition we can create Test classes the can be discovered with the following criteria

  • Classes must be named starting with Test (e.g., class TestVec3:).
  • They must not have an init method.
  • Methods inside must follow the test_ prefix rule.

We can create a new file in the tests folder as follows

touch tests/test_vec3.py

We can now add the following code

from vec3 import Vec3

def test_create_vec3():
    v = Vec3(1, 2, 3)
    assert v.x == 1
    assert v.y == 2
    assert v.z == 3

and run pytest

uv run pytest
================================================== test session starts ===================================================
platform darwin -- Python 3.13.3, pytest-8.4.1, pluggy-1.6.0
rootdir: /Users/jmacey/tmp/Vec3Class
configfile: pyproject.toml
collected 0 items / 1 error

========================================================= ERRORS =========================================================
__________________________________________ ERROR collecting tests/test_vec3.py ___________________________________________
ImportError while importing test module '/Users/jmacey/tmp/Vec3Class/tests/test_vec3.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.local/share/uv/python/cpython-3.13.3-macos-aarch64-none/lib/python3.13/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_vec3.py:1: in <module>
    from vec3 import Vec3
E   ModuleNotFoundError: No module named 'vec3'
================================================ short test summary info =================================================
ERROR tests/test_vec3.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
==================================================== 1 error in 0.04s ====================================================

This has failed (the red part of the TDD cycle) as there is no vec3 module to import we can fix this be adding the vec3 class to our project.

touch vec3.py

now add the following

class Vec3:
    def __init__(self, x: float, y: float, z: float):
        self.x = x
        self.y = y
        self.z = z
uv run pytest
================================================== test session starts ===================================================
platform darwin -- Python 3.13.3, pytest-8.4.1, pluggy-1.6.0
rootdir: /Users/jmacey/tmp/Vec3Class
configfile: pyproject.toml
collected 1 item

tests/test_vec3.py .                                                                                               [100%]

=================================================== 1 passed in 0.00s ====================================================

More tests

We should now take the same approach to add more tests first one for equality. First we add

def test_equality():
    assert Vec3(1, 2, 3) == Vec3(1, 2, 3)
    assert Vec3(1, 2, 3) != Vec3(3, 2, 1)

to the test_vec3.py file, running pytest will now fail. We now need to add the following to vec3.py

def __eq__(self, other):
        if not isinstance(other, Vec3):
            return NotImplemented
        return (self.x, self.y, self.z) == (other.x, other.y, other.z)

Exercise

Write tests and code for the following :

  • __add__
  • __sub__
  • magnitude hint (math.sqrt(self.x**2 + self.y**2 + self.z**2))
  • Add scalar multiplication (Vec3 * 2 → Vec3(2x, 2y, 2z)).
  • Implement dot and cross products with tests.
  • Improve equality by allowing float tolerance (math.isclose).

Further Reading

References

Previous
Next