|
| 1 | +--- |
| 2 | +layout: page |
| 3 | +title: Testing |
| 4 | +subtitle: The Unittest Module |
| 5 | +minutes: 10 |
| 6 | +--- |
| 7 | +> ## Learning Objectives {.objectives} |
| 8 | +> |
| 9 | +> - Understand how to use Unittest to write and run test cases |
| 10 | +> - Understand that Unittest can be used for many styles of test, not just unit tests |
| 11 | +> - |
| 12 | +> - |
| 13 | +
|
| 14 | +The Python standard library provides a module for writing and running tests, called `unittest`, which is documented for [Python3.6](https://docs.python.org/3.6/library/unittest.html?highlight=unittest#module-unittest) and [Python2.7](https://docs.python.org/2/library/unittest.html?highlight=unittest#module-unittest). |
| 15 | + |
| 16 | +This module is useful for writing all styles of test, including unit tests and functional tests. |
| 17 | + |
| 18 | +## Functional Tests |
| 19 | + |
| 20 | +Functional testing is a way of checking software to ensure that it has all the required functionality that's specified within its functional requirements. This will test many methods and may interact with dependencies like file access. |
| 21 | + |
| 22 | +Functional tests are often the first tests that are written as part of writing a program. They can link closely to a requirement: "This programme shall ...". |
| 23 | + |
| 24 | +These tests are testing overall behaviour. |
| 25 | + |
| 26 | +They are especially useful if code is being restructured or re-factored, to build confidence that the key behaviour is maintained during the restructuring. |
| 27 | + |
| 28 | +Often true unit tests, as described in the previous page, test the implementation and may require changing at the same time as the code during a refactor. |
| 29 | + |
| 30 | +## Unittest TestCases |
| 31 | + |
| 32 | +unittest is part of the Python core library; it defines a class, unittest.TestCase which provides lots of useful machinery for writing tests. We define classes which inherit from unittest.TestCase, bringing in their useful behaviour for us to apply to our tests. |
| 33 | + |
| 34 | +The reuse of objects in this way is a really useful programming approach. Don't worry if it is new to you. For testing with unittest, you can just think of it as a framework to fit your test cases into. |
| 35 | + |
| 36 | +We can rewrite the tests from the previous page, using the Unittest approach. |
| 37 | + |
| 38 | +~~~ {.python} |
| 39 | +import unittest |
| 40 | +
|
| 41 | +def mean(num_list): |
| 42 | + try: |
| 43 | + return sum(num_list)/len(num_list) |
| 44 | + except ZeroDivisionError : |
| 45 | + return 0 |
| 46 | + except TypeError as detail : |
| 47 | + msg = ("The algebraic mean of an non-numerical list is undefined." |
| 48 | + " Please provide a list of numbers.") |
| 49 | + raise TypeError(detail.__str__() + "\n" + msg) |
| 50 | +
|
| 51 | +class TestMean(unittest.TestCase): |
| 52 | + def test_ints(self): |
| 53 | + num_list = [1,2,3,4,5] |
| 54 | + obs = mean(num_list) |
| 55 | + exp = 3 |
| 56 | + self.assertEqual(obs, exp) |
| 57 | +
|
| 58 | + def test_zero(self): |
| 59 | + num_list=[0,2,4,6] |
| 60 | + obs = mean(num_list) |
| 61 | + exp = 3 |
| 62 | + self.assertEqual(obs, exp) |
| 63 | +
|
| 64 | + def test_double(self): |
| 65 | + # This one will fail in Python 2 |
| 66 | + num_list=[1,2,3,4] |
| 67 | + obs = mean(num_list) |
| 68 | + exp = 2.5 |
| 69 | + self.assertEqual(obs, exp) |
| 70 | +
|
| 71 | + def test_long(self): |
| 72 | + big = 100000000 |
| 73 | + obs = mean(range(1,big)) |
| 74 | + exp = big/2.0 |
| 75 | + self.assertEqual(obs, exp) |
| 76 | +
|
| 77 | + def test_complex(self): |
| 78 | + num_list = [2 + 3j, 3 + 4j, -32 + 2j] |
| 79 | + obs = mean(num_list) |
| 80 | + exp = -9 + 3j |
| 81 | + self.assertEqual(obs, exp) |
| 82 | +
|
| 83 | +if __name__ == '__main__': |
| 84 | + unittest.main() |
| 85 | +~~~ |
| 86 | + |
| 87 | +The naming of `class` and `def` entities is important. All classes should be named beginning `Test` and all methods should be named beginning `test-`. This enables the test runner to identify test cases to run. |
| 88 | + |
| 89 | +Save this code to a file, e.g. `test_mean.py`, again beginning the name with `test_`. This module can be run directly with python: |
| 90 | + |
| 91 | +~~~ {.bash} |
| 92 | +python test_mean.py |
| 93 | +~~~ |
| 94 | + |
| 95 | +~~~ {.output} |
| 96 | +..... |
| 97 | +---------------------------------------------------------------------- |
| 98 | +Ran 5 tests in 2.053s |
| 99 | +
|
| 100 | +OK |
| 101 | +
|
| 102 | +~~~ |
| 103 | + |
| 104 | +Unittest reports failures and errors on test cases, which we can see if we run Python2, as one of our tests only passes in Python3: |
| 105 | + |
| 106 | +~~~ {.bash} |
| 107 | +python2 test_mean.py |
| 108 | +~~~ |
| 109 | + |
| 110 | +~~~ {.output} |
| 111 | +..... |
| 112 | +---------------------------------------------------------------------- |
| 113 | +Ran 5 tests in 2.053s |
| 114 | +
|
| 115 | +OK |
| 116 | +
|
| 117 | +~~~ |
0 commit comments