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.
- Use uv to set up a pytest Python environment.
- Write unit tests with pytest.
- 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
- Red: Write a failing test (no implementation yet).
- Green: Write the minimum code to make it pass.
- 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__magnitudehint (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
- https://nccastaff.bournemouth.ac.uk/jmacey/post/PythonClasses/pyclasses/
- https://nccastaff.bournemouth.ac.uk/jmacey/post/AutoTest/autotest/