-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path01-why-test-my-code.Rmd
More file actions
231 lines (160 loc) · 6.88 KB
/
01-why-test-my-code.Rmd
File metadata and controls
231 lines (160 loc) · 6.88 KB
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
67
68
69
70
71
72
73
74
75
76
77
78
79
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
158
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
185
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
---
title: 'Why Test My Code?'
teaching: 10
exercises: 2
---
:::::::::::::::::::::::::::::::::::::: questions
- Why should I test my code?
::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::: objectives
- Understand how testing can help to ensure that code is working as expected
::::::::::::::::::::::::::::::::::::::::::::::::
## What is software testing?
Software testing is the process of checking that code is working as expected.
You may have data processing functions or automations that you use in your work.
How do you know that they are doing what you expect them to do?
Software testing is most commonly done by writing test code that check that
your code works as expected.
This might seem like a lot of effort, so let's go over some of the reasons you
might want to add tests to your project.
## Catching bugs
Whether you are writing the occasional script or developing a large software,
mistakes are inevitable. Sometimes you don't even know when a mistake creeps
into the code, and it gets published.
Consider the following function:
```python
def add(a, b):
return a - b
```
When writing this function, I made a mistake. I accidentally wrote `a - b`
instead of `a + b`. This is a simple mistake, but it could have serious
consequences in a project.
When writing the code, I could have tested this function by manually trying it
with different inputs and checking the output, but:
- This takes time.
- I might forget to test it again when we make changes to the code later on.
- Nobody else in my team knows if I tested it, or how I tested it, and
therefore whether they can trust it.
This is where automated testing comes in.
## Automated testing
Automated testing is where we write code that checks that our code works as
expected. Every time we make a change, we can run our tests to automatically
make sure that our code still works as expected.
If we were writing a test from scratch for the `add` function, think for a
moment on how we would do it.
We would need to write a function that runs the `add` function on a set of
inputs, checking each case to ensure it does what we expect. Let's write a test
for the `add` function and call it `test_add`:
```python
def test_add():
# Check that it adds two positive integers
if add(1, 2) != 3:
print("Test failed!")
# Check that it adds zero
if add(5, 0) != 5:
print("Test failed!")
# Check that it adds two negative integers
if add(-1, -2) != -3:
print("Test failed!")
```
Here we check that the function works for a set of test cases. We ensure that
it works for positive numbers, negative numbers, and zero.
::::::::::::::::::::::::::::::::::::: challenge
## What could go wrong?
When writing functions, sometimes we don't anticipate all the ways that they
could go wrong.
Take a moment to think about what is wrong, or might go wrong with these
functions:
```python
def greet_user(name):
return "Hello" + name + "!"
```
```python
def gradient(x1, y1, x2, y2):
return (y2 - y1) / (x2 - x1)
```
:::::::::::::::::::::::: solution
The first function will incorrectly greet the user, as it is missing a space
after "Hello". It would print `HelloAlice!` instead of `Hello Alice!`.
If we wrote a test for this function, we would have noticed that it was not
working as expected:
```python
def test_greet_user():
if greet_user("Alice") != "Hello Alice!":
print("Test failed!")
```
The second function will crash if `x2 - x1` is zero.
If we wrote a test for this function, it may have helped us to catch this
unexpected behaviour:
```python
def test_gradient():
if gradient(1, 1, 2, 2) != 1:
print("Test failed!")
if gradient(1, 1, 2, 3) != 2:
print("Test failed!")
if gradient(1, 1, 1, 2) != "Undefined":
print("Test failed!")
```
And we could have amended the function:
```python
def gradient(x1, y1, x2, y2):
if x2 - x1 == 0:
return "Undefined"
return (y2 - y1) / (x2 - x1)
```
:::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::
## Finding the root cause of a bug
When a test fails, it can help us to find the root cause of a bug. For example,
consider the following function:
```python
def multiply(a, b):
return a * a
def divide(a, b):
return a / b
def triangle_area(base, height):
return divide(multiply(base, height), 2)
```
There is a bug in this code too, but since we have several functions calling
each other, it is not immediately obvious where the bug is. Also, the bug is
not likely to cause a crash, so we won't get a helpful error message telling us
what went wrong. If a user happened to notice that there was an error, then we
would have to check `triangle_area` to see if the formula we used is right,
then `multiply`, and `divide` to see if they were working as expected too!
However, if we had written tests for these functions, then we would have seen
that both the `triangle_area` and `multiply` functions were not working as
expected, allowing us to quickly see that the bug was in the `multiply`
function without having to check the other functions.
## Increased confidence in code
When you have tests for your code, you can be more confident that it works as
expected. This is especially important when you are working in a team or
producing software for users, as it allows everyone to trust the code. If you
have a test that checks that a function works as expected, then you can be
confident that the function will work as expected, even if you didn't write it
yourself.
## Forcing a more structured approach to coding
When you write tests for your code, you are forced to think more carefully
about how your code behaves and how you will verify that it works as expected.
This can help you to write more structured code, as you will need to think
about how to test it as well as how it could fail.
::::::::::::::::::::::::::::::::::::: challenge
## What could go wrong?
Consider a function that controls a driverless car.
- What checks might we add to make sure it is not dangerous to use?
```python
def drive_car(speed, direction):
... # complex car driving code
return speed, direction, brake_status
```
:::::::::::::::::::::::: solution
- We might want to check that the speed is within a safe range.
- We might want to check that the direction is a valid direction. ie not
towards a tree, and if so, the car should be applying the brakes.
:::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::: keypoints
- Automated testing helps to catch hard to spot errors in code & find the root cause of complex issues.
- Tests reduce the time spent manually verifying (and re-verifying!) that code works.
- Tests help to ensure that code works as expected when changes are made.
- Tests are especially useful when working in a team, as they help to ensure that everyone can trust the code.
::::::::::::::::::::::::::::::::::::::::::::::::