Vladimir Paun committed Mar 28, 2017 1 ``````#MO101 - Test-Driven Development and unit testing in Python with git flavour. `````` Vladimir Paun committed Mar 25, 2017 2 `````` `````` Vladimir Paun committed Mar 25, 2017 3 4 ``````## Rationale Binary numbers are rather easy to understand, even if becoming familiar with them requires some time. `````` Vladimir Paun committed Mar 25, 2017 5 `````` `````` Vladimir Paun committed Mar 25, 2017 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 ``````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: ``` python >>> b = Binary('0101110001') >>> hex(b) '0x171' >>> int(b) 369 >>> b[0] '1' >>> b[9] '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 ``` python >>> 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 ``` python >>> 0b101[0] Traceback (most recent call last): File "", line 1, in TypeError: 'int' object is not subscriptable ``` You can also use a different base when converting things to integers, through the `base` parameter ``` python >>> 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. `````` Vladimir Paun committed Mar 28, 2017 74 75 ``````## 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. `````` Vladimir Paun committed Mar 25, 2017 76 `````` `````` Vladimir Paun committed Mar 28, 2017 77 ``````**Initialization** `````` Vladimir Paun committed Mar 25, 2017 78 79 `````` `````` Vladimir Paun committed Mar 28, 2017 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 `````````python 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](https://docs.python.org/3/library/unittest.html). ```python 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" git config --global user.email "vladimir-alexandru.paun@ensta-paristech.fr" ``` ##### Create a new repository ``` mkdir test_proj cd test_proj git init touch README.md git add README.md git commit -m "first commit" git remote add origin git@gitlab.ensta.fr:paun/test_proj.git git push -u origin master ``` ##### Push an existing Git repository ``` cd existing_git_repo git remote add origin git@gitlab.ensta.fr:paun/test_proj.git git push -u origin master ``` ## Project management `````` Vladimir Paun committed Mar 28, 2017 158 ``````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. `````` Vladimir Paun committed Mar 28, 2017 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 `````` ### 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). ### Q1 Create a gitLab account The ENSTA [gitLab](https://gitlab.ensta.fr/) address. Please follow the intructions provided on the website. ### Q2 Create a new gitLab project Chose a name and create a new gitLab project. `````` Vladimir Paun committed Mar 28, 2017 185 ``````### Q3 Clone the git project on your local session. `````` Vladimir Paun committed Mar 28, 2017 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 `````` ### 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_addition_int() test_binary_addition_binary() test_binary_division_int() test_binary_division_rem_int() test_binary_get_bit() test_binary_not() test_binary_and() test_binary_shl_pos() `````` Vladimir Paun committed Mar 25, 2017 238 239 `````` ``` `````` Vladimir Paun committed Mar 28, 2017 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 `````` Have a look [here](https://docs.python.org/3.4/library/operator.html) 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 ``` python >>> b = Binary('01101010') >>> b[4:7] (110) >>> b[1:3] (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. ## Resources 1. [git-cheat-sheet](http://users.humboldt.edu/smtuttle/s12cis492/492guide-to-git.pdf)``````