Lab 3 Test Driven Development
The aim of this lab is to introduce the process of test driven development (TDD) and develop some simple tests, we will use the following steps.
- Project Setup
- Test Setup
- Test Driven Design of a Simple RGBA class
- using Version control
It is common to store image pixel data using a single unsigned int32_t data type. In C/C++ this is quite simple, however in python these things are a little harder as we don’t have the same data types. For our example we are going to use a single integer value called pixel to store our RGBA values but we will need to encode them using some logic.
The diagrams on this page demonstrate this model.
The basic algorithm to encode is as follows
Given input red, green, blue and alpha We encode by alpha | (blue << 8) | (green << 16) | (red << 24) where | is a logical or and << is a shift to the right.
To decode we need mask out elements not for the r,g,b,a value and shift to the left to get the value
red_mask = 0xFF000000 green_mask = 0x00FF0000 blue_mask = 0x0000FF00 alpha_mask = 0x000000FF red = (pixel & red_mask) >> 24 green = (pixel & green_mask) >> 16 blue = (pixel & blue_mask) >> 8 alpha = (pixel & alpha_mask)
Basic project setup
For this project we are going to develop a simple python module with tests. In future labs we will use this module for other projects and simulations. We are going to develop 2 classes RGBA.py and Image.py
mkdir SimpleImage cd SimpleImage mkdir tests docs touch __init__.py touch RGBA.py tests/test_RGBA.py tests/__init__.py
We can now start developing our code by using TDD principles.
First we will edit the test_RGBA.py file and add the following
import unittest from RGBA import * class TestRGBA(unittest.TestCase): pass
We can now run
python -m unittest to run the test which results in
python -m unittest ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK
This is ok for basic testing and is built into python. For better testing and nicer output we can install the pytest module (which I reccomend).
pip install pytest pytest ===================================== test session starts ===================================== platform darwin -- Python 3.9.7, pytest-7.0.1, pluggy-1.0.0 rootdir: /Volumes/teaching/Code/ASEAIM/Lab3/Solution/SimpleImage collected 0 items ==================================== no tests ran in 0.01s ===================================
Now we have our framework in place we can start to develop our code. First we start by developing a failing unit test.
In the test_RGBA.py file we will add the following
class TestRGBA(unittest.TestCase): def test_ctor(self): pixel = RGBA()
running pytest gives us a fail
============================= test session starts ============================== platform darwin -- Python 3.9.7, pytest-7.0.1, pluggy-1.0.0 rootdir: /Volumes/teaching/Code/ASEAIM/Lab3/Solution/SimpleImage collected 1 item tests/test_RGBA.py F [100%] =================================== FAILURES =================================== ______________________________ TestRGBA.test_ctor ______________________________ self = <SimpleImage.tests.test_RGBA.TestRGBA testMethod=test_ctor> def test_ctor(self): > pixel = RGBA() E NameError: name 'RGBA' is not defined tests/test_RGBA.py:8: NameError =========================== short test summary info ============================ FAILED tests/test_RGBA.py::TestRGBA::test_ctor - NameError: name 'RGBA' is no... ============================== 1 failed in 0.02s ===============================
We now write enough code to make this failing test pass, in RGBA.py we add the following
class RGBA: pass
Our test now passes but it’s not actually testing anything. It is usual practice when developing classes to have some default values. In our case we will default the pixel to black (0,0,0) with full opacity (alpha = 255).
We can add this to our test.
def test_ctor(self): pixel = RGBA() r, g, b, a = pixel.get_rgba() self.assertEqual(r, 0) self.assertEqual(g, 0) self.assertEqual(b, 0) self.assertEqual(a, 255)
This will now fail our test and we need to add a fair bit to get it to pass. We will develop this bit by bit in the lab, however the final code should look like this.
from typing import Tuple class RGBA: red_mask = 0xFF000000 green_mask = 0x00FF0000 blue_mask = 0x0000FF00 alpha_mask = 0x000000FF def __init__(self, r: int = 0, g: int = 0, b: int = 0, a: int = 255): self.pixel = a | (b << 8) | (g << 16) | (r << 24) def get_rgba(self) -> Tuple[int, int, int, int]: red = (self.pixel & self.red_mask) >> 24 green = (self.pixel & self.green_mask) >> 16 blue = (self.pixel & self.blue_mask) >> 8 alpha = self.pixel & self.alpha_mask return red, green, blue, alpha
Adding to version control
Now we have a working test it is a good time to add this to version control. First we can copy the .gitignore file from the last lab session into this new folder or create a new one.
git init git add . git commit -am "new project for the RGBA demo"