|
| 1 | +--- |
| 2 | +title: 'Why Test My Code?' |
| 3 | +teaching: 10 |
| 4 | +exercises: 2 |
| 5 | +--- |
| 6 | + |
| 7 | +:::::::::::::::::::::::::::::::::::::: questions |
| 8 | + |
| 9 | +- Why should I test my code? |
| 10 | + |
| 11 | +:::::::::::::::::::::::::::::::::::::::::::::::: |
| 12 | + |
| 13 | +::::::::::::::::::::::::::::::::::::: objectives |
| 14 | + |
| 15 | +- Understand how testing can help to ensure that code is working as expected |
| 16 | + |
| 17 | +:::::::::::::::::::::::::::::::::::::::::::::::: |
| 18 | + |
| 19 | +## What is software testing? |
| 20 | + |
| 21 | +Software testing is the process of checking that code is working as expected. |
| 22 | +You may have data processing functions or automations that you use in your work. |
| 23 | +How do you know that they are doing what you expect them to do? |
| 24 | + |
| 25 | +Software testing is most commonly done by writing test code that check that |
| 26 | +your code works as expected. |
| 27 | + |
| 28 | +This might seem like a lot of effort, so let's go over some of the reasons you |
| 29 | +might want to add tests to your project. |
| 30 | + |
| 31 | + |
| 32 | +## Catching bugs |
| 33 | + |
| 34 | +Whether you are writing the occasional script or developing a large software, |
| 35 | +mistakes are inevitable. Sometimes you don't even know when a mistake creeps |
| 36 | +into the code, and it gets published. |
| 37 | + |
| 38 | +Consider the following function: |
| 39 | + |
| 40 | +```python |
| 41 | +def add(a, b): |
| 42 | + return a - b |
| 43 | +``` |
| 44 | + |
| 45 | +When writing this function, I made a mistake. I accidentally wrote `a - b` |
| 46 | +instead of `a + b`. This is a simple mistake, but it could have serious |
| 47 | +consequences in a project. |
| 48 | + |
| 49 | +When writing the code, I could have tested this function by manually trying it |
| 50 | +with different inputs and checking the output, but: |
| 51 | + |
| 52 | +- This takes time. |
| 53 | +- I might forget to test it again when we make changes to the code later on. |
| 54 | +- Nobody else in my team knows if I tested it, or how I tested it, and |
| 55 | + therefore whether they can trust it. |
| 56 | + |
| 57 | +This is where automated testing comes in. |
| 58 | + |
| 59 | +## Automated testing |
| 60 | + |
| 61 | +Automated testing is where we write code that checks that our code works as |
| 62 | +expected. Every time we make a change, we can run our tests to automatically |
| 63 | +make sure that our code still works as expected. |
| 64 | + |
| 65 | +If we were writing a test from scratch for the `add` function, think for a |
| 66 | +moment on how we would do it. |
| 67 | + |
| 68 | +We would need to write a function that runs the `add` function on a set of |
| 69 | +inputs, checking each case to ensure it does what we expect. Let's write a test |
| 70 | +for the `add` function and call it `test_add`: |
| 71 | + |
| 72 | +```python |
| 73 | +def test_add(): |
| 74 | + # Check that it adds two positive integers |
| 75 | + if add(1, 2) != 3: |
| 76 | + print("Test failed!") |
| 77 | + # Check that it adds zero |
| 78 | + if add(5, 0) != 5: |
| 79 | + print("Test failed!") |
| 80 | + # Check that it adds two negative integers |
| 81 | + if add(-1, -2) != -3: |
| 82 | + print("Test failed!") |
| 83 | +``` |
| 84 | + |
| 85 | +Here we check that the function works for a set of test cases. We ensure that |
| 86 | +it works for positive numbers, negative numbers, and zero. |
| 87 | + |
| 88 | + |
| 89 | +::::::::::::::::::::::::::::::::::::: challenge |
| 90 | + |
| 91 | +## What could go wrong? |
| 92 | + |
| 93 | +When writing functions, sometimes we don't anticipate all the ways that they |
| 94 | +could go wrong. |
| 95 | + |
| 96 | +Take a moment to think about what is wrong, or might go wrong with these |
| 97 | +functions: |
| 98 | + |
| 99 | +```python |
| 100 | +def greet_user(name): |
| 101 | + return "Hello" + name + "!" |
| 102 | +``` |
| 103 | + |
| 104 | +```python |
| 105 | +def gradient(x1, y1, x2, y2): |
| 106 | + return (y2 - y1) / (x2 - x1) |
| 107 | +``` |
| 108 | + |
| 109 | +:::::::::::::::::::::::: solution |
| 110 | + |
| 111 | +The first function will incorrectly greet the user, as it is missing a space |
| 112 | +after "Hello". It would print `HelloAlice!` instead of `Hello Alice!`. |
| 113 | + |
| 114 | +If we wrote a test for this function, we would have noticed that it was not |
| 115 | +working as expected: |
| 116 | + |
| 117 | +```python |
| 118 | +def test_greet_user(): |
| 119 | + if greet_user("Alice") != "Hello Alice!": |
| 120 | + print("Test failed!") |
| 121 | +``` |
| 122 | + |
| 123 | +The second function will crash if `x2 - x1` is zero. |
| 124 | + |
| 125 | +If we wrote a test for this function, it may have helped us to catch this |
| 126 | +unexpected behaviour: |
| 127 | + |
| 128 | +```python |
| 129 | +def test_gradient(): |
| 130 | + if gradient(1, 1, 2, 2) != 1: |
| 131 | + print("Test failed!") |
| 132 | + if gradient(1, 1, 2, 3) != 2: |
| 133 | + print("Test failed!") |
| 134 | + if gradient(1, 1, 1, 2) != "Undefined": |
| 135 | + print("Test failed!") |
| 136 | +``` |
| 137 | + |
| 138 | +And we could have amended the function: |
| 139 | + |
| 140 | +```python |
| 141 | +def gradient(x1, y1, x2, y2): |
| 142 | + if x2 - x1 == 0: |
| 143 | + return "Undefined" |
| 144 | + return (y2 - y1) / (x2 - x1) |
| 145 | +``` |
| 146 | + |
| 147 | +::::::::::::::::::::::::::::::::: |
| 148 | +:::::::::::::::::::::::::::::::::::: |
| 149 | + |
| 150 | + |
| 151 | +## Finding the root cause of a bug |
| 152 | + |
| 153 | +When a test fails, it can help us to find the root cause of a bug. For example, |
| 154 | +consider the following function: |
| 155 | + |
| 156 | +```python |
| 157 | + |
| 158 | +def multiply(a, b): |
| 159 | + return a * a |
| 160 | + |
| 161 | +def divide(a, b): |
| 162 | + return a / b |
| 163 | + |
| 164 | +def triangle_area(base, height): |
| 165 | + return divide(multiply(base, height), 2) |
| 166 | +``` |
| 167 | + |
| 168 | +There is a bug in this code too, but since we have several functions calling |
| 169 | +each other, it is not immediately obvious where the bug is. Also, the bug is |
| 170 | +not likely to cause a crash, so we won't get a helpful error message telling us |
| 171 | +what went wrong. If a user happened to notice that there was an error, then we |
| 172 | +would have to check `triangle_area` to see if the formula we used is right, |
| 173 | +then `multiply`, and `divide` to see if they were working as expected too! |
| 174 | + |
| 175 | +However, if we had written tests for these functions, then we would have seen |
| 176 | +that both the `triangle_area` and `multiply` functions were not working as |
| 177 | +expected, allowing us to quickly see that the bug was in the `multiply` |
| 178 | +function without having to check the other functions. |
| 179 | + |
| 180 | + |
| 181 | +## Increased confidence in code |
| 182 | + |
| 183 | +When you have tests for your code, you can be more confident that it works as |
| 184 | +expected. This is especially important when you are working in a team or |
| 185 | +producing software for users, as it allows everyone to trust the code. If you |
| 186 | +have a test that checks that a function works as expected, then you can be |
| 187 | +confident that the function will work as expected, even if you didn't write it |
| 188 | +yourself. |
| 189 | + |
| 190 | +## Forcing a more structured approach to coding |
| 191 | + |
| 192 | +When you write tests for your code, you are forced to think more carefully |
| 193 | +about how your code behaves and how you will verify that it works as expected. |
| 194 | +This can help you to write more structured code, as you will need to think |
| 195 | +about how to test it as well as how it could fail. |
| 196 | + |
| 197 | +::::::::::::::::::::::::::::::::::::: challenge |
| 198 | + |
| 199 | +## What could go wrong? |
| 200 | + |
| 201 | +Consider a function that controls a driverless car. |
| 202 | + |
| 203 | +- What checks might we add to make sure it is not dangerous to use? |
| 204 | + |
| 205 | +```python |
| 206 | +def drive_car(speed, direction): |
| 207 | + |
| 208 | + ... # complex car driving code |
| 209 | + |
| 210 | + return speed, direction, brake_status |
| 211 | +``` |
| 212 | + |
| 213 | +:::::::::::::::::::::::: solution |
| 214 | + |
| 215 | + |
| 216 | +- We might want to check that the speed is within a safe range. |
| 217 | +- We might want to check that the direction is a valid direction. ie not |
| 218 | + towards a tree, and if so, the car should be applying the brakes. |
| 219 | + |
| 220 | +::::::::::::::::::::::::::::::::::::: |
| 221 | +:::::::::::::::::::::::::::::::::::::::: |
| 222 | + |
| 223 | +::::::::::::::::::::::::::::::::::::: keypoints |
| 224 | + |
| 225 | +- Automated testing helps to catch hard to spot errors in code & find the root cause of complex issues. |
| 226 | +- Tests reduce the time spent manually verifying (and re-verifying!) that code works. |
| 227 | +- Tests help to ensure that code works as expected when changes are made. |
| 228 | +- Tests are especially useful when working in a team, as they help to ensure that everyone can trust the code. |
| 229 | + |
| 230 | +:::::::::::::::::::::::::::::::::::::::::::::::: |
| 231 | + |
0 commit comments