M
mo101 agile project test – README.md

#MO101 - Test-Driven Development and unit testing in Python with git flavour.

Rationale

Binary numbers are rather easy to understand, even if becoming familiar with them requires some time.

The package we are going to write will provide a class that represents binary numbers (Binary) and a class that represents binary numbers with a given bit size (SizeBinary). They shall provide basic binary operations like logical (and, or, xor), arithmetic (addition, subtraction, multiplication, division), shifts and indexing.

A quick example of what the package shall do:

>>> b = Binary('0101110001')
>>> hex(b)
'0x171'
>>> int(b)
369
>>> b
'1'
>>> b
'0'
>>> b.SHR()
'10111000'

Run with:

python -m unittest test_name

Python and bases

Binary system is just a representation of numbers with base 2, just like hexadecimal (base 16) and decimal (base 10). Python can already natively deal with different bases, even if internally numbers are always stored as decimal integers. Let us check it

>>> a = 5
>>> a
5
>>> a = 0x5
>>> a
5
>>> a = 0b101
>>> a
5
>>> hex(0b101)
'0x5'
>>> bin(5)
'0b101'

As you can see Python understands some common bases out of the box, using the 0x prefix for hexadecimal numbers and the 0b for binary ones (and 0o) for octals). However the number is always printed in its base-10 form (5 in this case). This means however that a binary number cannot be indexed, since integers does not provide support for this operation

>>> 0b101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not subscriptable

You can also use a different base when converting things to integers, through the base parameter

>>> a = int('101', base=2)
>>> a
5
>>> a = int('10', base=5)
>>> a
5

Test-driven development

Simple tasks are the best way to try and use new development methodologies, so this is a good occasion to start working with the so-called test-driven approach. Test-driven Development (TDD) basically means that the first thing you do when developing is to write some tests, that is programs that use what you are going to develop. The purpose of those programs is to test that your final product complies with a given behaviour. So they provide

• Documentation for your API: they are examples of use of your package.
• Regression checks: when you change the code to develop new features they shall not break the behaviour of the previous package versions.
• TODO list: until all tests run successfully you have something still waiting to be implemented.

Writing some tests

So now we will pretend we have already developed our Binary class and write some tests that check its behaviour. You will find the whole file in the Resources section at the end of the post, I will show here just some snippets.

Initialization

import unittest

#define Test class as a derived class of the unittest.TestCase base class
class Test(unittest.TestCase):
def setUp(self):
pass

def test_init(self):
pass

Runing the test

You can use the -m unittest option when interpreting scripts when extending the base class unittest.TestCase. Eitherwise you can always add a main block and call inside all the test methods/functions.

You may find the documentation for unit testing here.

import unittest
from binary import Binary

#define Test class as a derived class of the unittest.TestCase base class
class Test(unittest.TestCase):
def setUp(self):
self.binary = Binary(6)

def test_binary_init_int(self):
binary = Binary(6)
assert int(binary) == 6

From the command line use the unittest command to run your tests:

python3 -m unittest test_random

Git Command line instructions

These instructions are present on the gitLab site also after creating a new project.

Git global setup
git config --global user.name "Paun Vladimir Alexandru"
Create a new repository
mkdir test_proj
cd test_proj
git init
git commit -m "first commit"
git push -u origin master
Push an existing Git repository
cd existing_git_repo
git push -u origin master

Project management

For this project you will work in teams of two. Most of the code will be written together, as a group. One person will write the production code but both decide the content.

Roles

One person from the team will be responsible with the production of the test, following the TDD approach. Test are writen before the production code, write just enaugh code to validate the last added code.

1. [R1] - writing the tests
2. [R2] - writing production code to validate the tests

Workflow

For this assignment you will work in teams of two on separate computers moat of the time. Every time a team member pushes results on the git repository, the other member will retreive the modifications, add just enaugh code to validate the added test(s) and pushes the results.

Create the test class and a first test method (R1). Add/commit the source to the git and push when it's ready. Get the updates and write the code needed to validate the new test cases (R2).

Q2 Create a new gitLab project

Chose a name and create a new gitLab project.

Q4 Initialization

First of all we import the class from the binary.py file (which doesn't exists yet). The test_binary_init_int() function shall (as the name suggests) initialize a Binary with an integer. The assertion checks that the newly created binary variable has a consistent integer representation, which is the number we used to initialize it.

Q5 Test all the initialisations one by one

We want to be able to initialize a Binary with a wide range of values: bit strings ('110'), binary strings ('0b110'), hexadecimal strings ('0x6'), hexadecimal values (0x6), lists of integers ([1,1,0]) and list of strings (['1','1','0']). The following tests check all those cases, please add them progresively in order to practice the commit/push/pull cycles.

• test_binary_init_bitstr()

• test_binary_init_binstr()

• test_binary_init_hexstr()

• test_binary_init_hex()

• test_binary_init_intseq()

• test_binary_init_strseq()

Q6 Negative numbers

Let us now check that our Binary class cannot be initialized with a negative number. So for simple binaries we just discard negative numbers.

Q7 Conversions

We want to check that my binary numbers can be correctly converted to integers (through int()), binary strings (through bin()), hexadecimals (through hex()) and to strings (through str()). We want the string representation to be a plain sequence of zeros and ones, that is the binary string representation without the 0b prefix.

Q8 Binary operations

Now it is time to add new features to our Binary class. As already said, the TDD methodology wants us to first write the tests, then to write the code. Our class is missing some basic arithmetic and binary operations so the tests are

test_binary_division_int()

test_binary_division_rem_int()

test_binary_get_bit()

test_binary_not()

test_binary_and()

test_binary_shl_pos()

Have a look here for the syntax used in python to override operators.

Q9 Slicing

We want Binary to support slicing, just like lists. The difference between lists and my Binary type is that for the latter indexes start from the rightmost element. So when we get bits 3:7 of an 8-bit Binary we obtain the four leftmost ones. The desired behaviour is thus exemplified by

>>> b = Binary('01101010')
>>> b[4:7]
<binary.Binary object at 0x...> (110)
>>> b[1:3]
<binary.Binary object at 0x...> (101)

Q10 Splitting binaries

The last feature we want to add is a split() function that divides the binary number in two binaries. The rightmost one shall have the given size in bits, while the leftmost just contains the remaining bits.