Unit testing is the “sweet” part of the programming – once you have tested “all the things”, you feel that the code you have submitted somewhere is actually valid and somehow it works. Furthermore, it allows you to nicely brag about it, by saying “Of course, we have a unit test for this case.” and adding a nice Mona Lisa smile. And that is the case, until the program works and the tests are running ok. Which is in the general case between 2 days and 2 weeks initially.
As I have written an article about C# Unit testing some years ago and I have made a live coding video for the xUnit Library in C# and TDD, I have decided to write an article for unit tests in Python with Jupyter Notebook. To continue the tradition in a way.
So, following the “best practices” of VitoshAcademy, we will present the Fibonacci function and we will test it. The class looks like this:
1 2 3 4 5 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 |
class Fibonatics(): def __init__(self, number): self.number = number self.memo = [0] * (number+1) self.quick_count = 0 self.slow_count = 0 def ResetNumber(self, number): self.number = number self.memo = [0] * (number + 1) self.quick_count = 0 self.slow_count = 0 def Fibonacci(self, number): self.quick_count = self.quick_count + 1 if self.memo[number] != 0: return self.memo[number] if number == 0: return 0 if number == 1: return 1 self.memo[number] = self.Fibonacci(number - 1) + self.Fibonacci(number - 2) return self.memo[number] def FibonacciSlow(self, number): self.slow_count = self.slow_count + 1 if number == 0: return 0 if number == 1: return 1 return self.FibonacciSlow(number - 1) + self.FibonacciSlow(number - 2) |
The Fibonacci(self, number) function is actually “Fibonacci on steroids”, using memoisation, while the FibonacciSlow(self, number) is the good old example of Fibonacci with recursion. To present the difference between the different Fibonacci calculations, I have written an article some years ago – C# – Three Algorithms For Fibonacci Numbers. The ResetNumber(self, number) function is actually put there, so I can illustrate what the def setUp(self): and def tearDown(self): in the unittest.TestCase are doing. And its main purpose is to reset the number variable in the class, together with the other counters. Take a look at the difference of the number of calculations, between the function with memoization and the function with recursion – 49 vs 75025 is quite impressive.
So, how do we UnitTests in Python?
- import unittest
- write unit tests
- run the tests
I was quite “lucky” that I had prepared tests from the articles for C#, thus I did not have to come up with something extraordinary. Still, comparing the results of the “slow” and the “quick” Fibonacci and comparing their operations was quite a must:
1 2 3 4 5 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 |
class FibonaticsTests(unittest.TestCase): def setUp(self): self.list_of_values = np.arange(1, 22, 1).tolist() my_calc.Fibonacci(1) def tearDown(self): pass def test_value_10(self): my_calc.ResetNumber(self.list_of_values[9]) expected = my_calc.Fibonacci(my_calc.number) self.assertEqual(expected, 55) def test_for_equality_with_10(self): my_calc.ResetNumber(self.list_of_values[9]) expected_quick = my_calc.Fibonacci(my_calc.number) expected_slow = my_calc.FibonacciSlow(my_calc.number) self.assertEqual(expected_quick, expected_slow) def test_value_11(self): my_calc.ResetNumber(self.list_of_values[10]) expected = my_calc.Fibonacci(my_calc.number) self.assertEqual(expected, 89) def test_for_equality_with_11(self): my_calc.ResetNumber(self.list_of_values[10]) expected_quick = my_calc.Fibonacci(my_calc.number) expected_slow = my_calc.FibonacciSlow(my_calc.number) self.assertEqual(expected_quick, expected_slow) def test_value_slow_20(self): my_calc.ResetNumber(self.list_of_values[19]) expected = my_calc.FibonacciSlow(my_calc.number) self.assertEqual(expected, 6765) def test_negative_value_20(self): my_calc.ResetNumber(self.list_of_values[19]) expected = my_calc.Fibonacci(my_calc.number) self.assertNotEqual(expected, 6766) def test_negative_value_1(self): my_calc.ResetNumber(self.list_of_values[0]) expected = my_calc.Fibonacci(my_calc.number) self.assertFalse(expected == 2) def test_slow_needs_more_steps_with_11(self): my_calc.ResetNumber(self.list_of_values[10]) my_calc.Fibonacci(my_calc.number) my_calc.FibonacciSlow(my_calc.number) quick_steps = my_calc.quick_count slow_steps = my_calc.slow_count self.assertTrue(quick_steps < slow_steps) def test_slow_is_equal_to_quick_at_2(self): my_calc.ResetNumber(self.list_of_values[1]) my_calc.Fibonacci(my_calc.number) my_calc.FibonacciSlow(my_calc.number) quick_steps = my_calc.quick_count slow_steps = my_calc.slow_count self.assertEqual(quick_steps, slow_steps) |
The results of the UnitTests in Jupyter notebook is called by the following line:
1 |
unittest.main(argv=[''], verbosity=3, exit=False) |
And depending on the level of verbosity, you may get one of the following screens. The error comes from adding +1 to this line self.assertEqual(quick_steps+1, slow_steps), thus the explanation of th error is AssertionError: 4 != 3.
Leves of verbosity
Verbosity = 0 (quiet) – get the total numbers of tests executed and the global result
Verbosity = 1 (default) – quiet + a dot for every successful test or a F for every failure
Verbosity = 2 (verbose) – help string of every test and the result
As usual, the whole Jupyter Notebook is available in GitHub:
https://github.com/Vitosh/Python_personal/blob/master/JupyterNotebook/unit_tests_jupyter.ipynb
Enjoy it! 🙂