Lab 5 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. Version Control Setup
  3. Test Setup
  4. Test Driven Design of a Simple RGBA class

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.

import pytest


def test_ctor(self):
    pixel = RGBA()
    r, g, b, a = pixel.get_rgba()
    assert v.r == 0
    assert v.g == 0
    assert v.b == 0
    assert v.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

Getting started with git

Git is a version control system ( vcs) it can be used in a number of ways both for individual projects or working as a team. For todays example we are going to use it as an individual.

Git is a command line tool but also has a gui and it is also integrated into the IDE (VSCode).

First we need to tell git who we are and give it an email address (this helps git to know who is making the commits and who to blame!).

git config --global user.name " put your name here"
git config --global user.email " put your email here"

I usually recommend using the same username as your github account same for the email address.

To get started we will change into the SimpleImage folder

cd SimpleImage

We can run the following command to get information about the current folder.

git status
fatal: not a git repository (or any of the parent directories): .git

To setup our folder as a git repository we need to run the following

git init
Initialized empty Git repository in /home/jmacey/SimpleImage/.git/

Note this will generate a .git folder which is where all the version control data will be stored.

On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.vscode/
	__init__.py
	__pycache__/
    Image.py
    RGBA.py

nothing added to commit but untracked files present (use "git add" to track)

Adding some files

We are going to add some files to this folder, to start with we will create some empty files and then add them to the version control system.

touch README.md .gitignore
git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        README.md
        Image.py
        RGBA.py
nothing added to commit but untracked files present (use "git add" to track)

To add these files to version control we use the command

git add README.md .gitignore
git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   .gitignore
	new file:   README.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.vscode/
	__pycache__/
    RGBA.py
    Image.py

README.md

The README.md file is used as the front page of the Github webpage, adding one to any folder will also make that a front page. It is written in a syntax called Markdown and in particular GitHub has it’s own version. Markdown can also be used in jupyter notebooks for text so it is well worth learning the basic syntax.

# This is a title
Normal text is here
## Subtitle
This is a subtitle, for the rest of the examples I will edit the file, as this webpage is written in markdown so embedding is harder :-) 

.gitignore

This file is used by git to ignore certain types of file when adding, these are usually language / project specific, however we can add global setting as well.

For ease I usually copy a pre-prepared one from other projects. The contents of which is shown below. GitHub also offers pre-prepared templates here of which the one below is modified from.

# vs code project setup files
.vscode
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
.vscode/
# stubgen files and MyPY
out/
.mypy_cache/
# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
.pytest_cache/
docs/build
# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/


# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/


# Cython debug symbols
cython_debug/

committing

To take a snapshot of the code we use the git commit command. Typically we do this once a significant part of the work, or tests have been completed. There is no real set time to do this but it helps to do this frequently to be able to revert to older versions etc.

All commits usually have a commit message there is an art to writing good commit messages and some companies have style guides on how to do these. In general make them describe what has been done, what bugs are fixed etc.

git commit -am "added first programs to git"
[master (root-commit) 17476a0] added first programs to git
 4 files changed, 88 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 README.md
 create mode 100644 __init__.py
 create mode 100755 RGBA.py
 create mode 100755 Image.py
 

Using GitHub

At present all of this work is on the local machine. By using GitHub we can upload our work to a repository. The easiest way to do this is by creating a new repo in the web interface, then add this as the origin. The instructions are on the webpage and I will do this as a practical example in the lab.

New tests for RGBA

TestDescription
def test_ctor_user(self):test construct from RGBA
def test_colour_access(self):check the red,green,blue and alpha work
def test_from_hex(self):construct from hex
def test_set(self):test pixel.set works
def test_to_hsv(self):test to see if we can convert to hsv
Previous