Lab 3 Test Driven Development

Aims

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.

  1. Project Setup
  2. Test Setup
  3. Test Driven Design of a Simple RGBA class
  4. using Version control

Storing Pixels

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

The module we are developing is to teach certain principles, most of what we are doing is already available in other 3rd party modules such as pillow OpenImageIO or PySide
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"
Previous
Next