Skip to content
This repository was archived by the owner on Apr 25, 2023. It is now read-only.

Commit e11ceed

Browse files
committed
Finish testing part
1 parent c635bc3 commit e11ceed

File tree

2 files changed

+171
-15
lines changed

2 files changed

+171
-15
lines changed

img/red-green-refactor.png

34.8 KB
Loading

sessions/02_layout-unit-tests.ipynb

Lines changed: 171 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -163,36 +163,192 @@
163163
"source": [
164164
"### Exercise\n",
165165
"\n",
166-
"1. Create a directory called `test-package`\n",
166+
"1. Create a directory called `nlp-utils`\n",
167167
"2. `git init` inside it\n",
168-
"3. Create a basic `src` layout in it, with `name = test-package` and the source in `src/test_package`\n",
168+
"3. Create a basic `src` layout in it, with `name = nlp-utils` and the source in `src/nlp_utils`\n",
169169
"4. Create a `src/test_package/__init__.py` with a `print(\"Hello, world!\")`\n",
170-
"5. Install it in editable mode using `pip` and test that `>>> import test_package` prints `Hello, world!`\n",
170+
"5. Install it in editable mode using `pip` and test that `>>> import nlp_utils` prints `Hello, world!`\n",
171171
"6. Include a `README.txt` and an appropriate `.gitignore` from http://gitignore.io/\n",
172172
"7. Commit the changes\n",
173173
"8. Create a new GitHub project and push the repository there\n",
174174
"\n",
175175
"🎉"
176176
]
177+
},
178+
{
179+
"cell_type": "markdown",
180+
"metadata": {},
181+
"source": [
182+
"## Testing\n",
183+
"\n",
184+
"Testing is **essential**. Many developers get along without testing their software, but as common wisdom says:\n",
185+
"\n",
186+
"<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">If you use software that lacks automated tests, you are the tests.</p>&mdash; Jenny Bryan (@JennyBryan) <a href=\"https://twitter.com/JennyBryan/status/1043307291909316609?ref_src=twsrc%5Etfw\">September 22, 2018</a></blockquote> <script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\n",
187+
"\n",
188+
"Computers excel at doing repetitive tasks: they basically never make mistakes (the mistake might be in what we told the computer to do). Humans, on the other hand, fail more often, especially under pressure, or on Friday afternoons and Monday mornings. Therefore, instead of letting the humans be the tests, we will use the computer to **frequently verify that our software works as specified**.\n",
189+
"\n",
190+
"### References\n",
191+
"\n",
192+
"* pytest documentation https://docs.pytest.org\n",
193+
"\n",
194+
"### Further reading\n",
195+
"\n",
196+
"* Extreme Programming https://www.wikiwand.com/en/Extreme_programming\n",
197+
"* Obey the Testing Goat! http://www.obeythetestinggoat.com/pages/book.html#toc\n",
198+
"* (Shameless self-plug) Testing and validation approaches for scientific software https://nbviewer.jupyter.org/format/slides/github/poliastro/oscw2018-talk/blob/master/Talk.ipynb"
199+
]
200+
},
201+
{
202+
"cell_type": "markdown",
203+
"metadata": {},
204+
"source": [
205+
"### Test-Driven Development\n",
206+
"\n",
207+
"> Make it work. Make it right. Make it fast.\n",
208+
"\n",
209+
"Test-Driven Development shifts the focus of software development to writing tests. The \"practice of test-first development, planning and writing tests before each micro-increment\" is not new: it was in use at NASA in the early 1960s ([source](https://www.wikiwand.com/en/Extreme_programming)). In the 1990s, Extreme Programming took this concept to the extreme by the use of **small, automated** tests.\n",
210+
"\n",
211+
"The \"test-driven development mantra\" is <span style=\"color: red\">**Red**</span> - <span style=\"color: green\">**Green**</span> - **Refactor**:\n",
212+
"\n",
213+
"![The mantra](../img/red-green-refactor.png)\n",
214+
"\n",
215+
"1. Write a test. <span style=\"color: red\">**Watch it fail**</span>.\n",
216+
"2. Write just enough code to <span style=\"color: green\">**pass the test**</span>.\n",
217+
"3. Improve the code without breaking the test.\n",
218+
"4. Repeat."
219+
]
220+
},
221+
{
222+
"cell_type": "markdown",
223+
"metadata": {},
224+
"source": [
225+
"### Testing in Python\n",
226+
"\n",
227+
"Summary: **use pytest**. Everybody does. It rocks.\n",
228+
"\n",
229+
"[pytest](https://docs.pytest.org/) is a testing framework for Python that makes writing tests extremely easy. It is much more powerful than the standard library equivalent, `unittest`. To use it, you need to install it first:\n",
230+
"\n",
231+
"```\n",
232+
"$ pip install pytest\n",
233+
"```\n",
234+
"\n",
235+
"The simplest test is **a function with an `assert`**. The `assert` statement just fails if the contents are not `True`, and else it does nothing. *It should only be used for testing*."
236+
]
237+
},
238+
{
239+
"cell_type": "code",
240+
"execution_count": 1,
241+
"metadata": {},
242+
"outputs": [],
243+
"source": [
244+
"assert True # Does nothing"
245+
]
246+
},
247+
{
248+
"cell_type": "code",
249+
"execution_count": 2,
250+
"metadata": {},
251+
"outputs": [
252+
{
253+
"ename": "AssertionError",
254+
"evalue": "",
255+
"output_type": "error",
256+
"traceback": [
257+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
258+
"\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
259+
"\u001b[0;32m<ipython-input-2-40f67ddecc26>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;32mFalse\u001b[0m \u001b[0;31m# Fails!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
260+
"\u001b[0;31mAssertionError\u001b[0m: "
261+
]
262+
}
263+
],
264+
"source": [
265+
"assert False # Fails!"
266+
]
267+
},
268+
{
269+
"cell_type": "code",
270+
"execution_count": 3,
271+
"metadata": {},
272+
"outputs": [
273+
{
274+
"ename": "AssertionError",
275+
"evalue": "Math is wrong",
276+
"output_type": "error",
277+
"traceback": [
278+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
279+
"\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
280+
"\u001b[0;32m<ipython-input-3-c2bde6a3219c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Math is wrong\"\u001b[0m \u001b[0;31m# Fails with a message\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
281+
"\u001b[0;31mAssertionError\u001b[0m: Math is wrong"
282+
]
283+
}
284+
],
285+
"source": [
286+
"assert 2 + 2 == 5, \"Math is wrong\" # Fails with a message"
287+
]
288+
},
289+
{
290+
"cell_type": "markdown",
291+
"metadata": {},
292+
"source": [
293+
"### Example\n",
294+
"\n",
295+
"> Write a function that **tokenizes a sentence** (i.e. splits it into a list of words)\n",
296+
"\n",
297+
"First, we write a (failing) test:\n",
298+
"\n",
299+
"```python\n",
300+
"# tests/test_tokenize.py\n",
301+
"from nlp_utils import tokenize # This will fail right away!\n",
302+
"\n",
303+
"def test_tokenize_returns_expected_list():\n",
304+
" sentence = \"This is a sentence\"\n",
305+
" expected_tokens = [\"This\", \"is\", \"a\", \"sentence\"]\n",
306+
"\n",
307+
" tokens = tokenize(sentence)\n",
308+
"\n",
309+
" assert tokens == expected_tokens\n",
310+
"```\n",
311+
"\n",
312+
"and we run it from the command line:\n",
313+
"\n",
314+
"```\n",
315+
"$ pytest\n",
316+
"...\n",
317+
"```\n",
318+
"\n",
319+
"Then we fix the test in the simplest way:\n",
320+
"\n",
321+
"```python\n",
322+
"# src/nlp_utils/__init__.py\n",
323+
"def tokenize(sentence):\n",
324+
" return sentence.split()\n",
325+
"```\n",
326+
"\n",
327+
"And we watch it pass!\n",
328+
"\n",
329+
"```\n",
330+
"$ pytest\n",
331+
"...\n",
332+
"```"
333+
]
334+
},
335+
{
336+
"cell_type": "markdown",
337+
"metadata": {},
338+
"source": [
339+
"### Exercise\n",
340+
"\n",
341+
"1. Add a new test that checks that `tokenize(sentence, lower=True)` returns a list of *lowercase* tokens.\n",
342+
"2. Fix the test *in a way the first one doesn't break*.\n",
343+
"3. *Extra*: Use `@pytest.mark.parametrize` to pass two different sentences to the new test https://docs.pytest.org/en/latest/example/parametrize.html"
344+
]
177345
}
178346
],
179347
"metadata": {
180348
"kernelspec": {
181349
"display_name": "Python 3",
182350
"language": "python",
183351
"name": "python3"
184-
},
185-
"language_info": {
186-
"codemirror_mode": {
187-
"name": "ipython",
188-
"version": 3
189-
},
190-
"file_extension": ".py",
191-
"mimetype": "text/x-python",
192-
"name": "python",
193-
"nbconvert_exporter": "python",
194-
"pygments_lexer": "ipython3",
195-
"version": "3.7.1"
196352
}
197353
},
198354
"nbformat": 4,

0 commit comments

Comments
 (0)