-
Notifications
You must be signed in to change notification settings - Fork 1
/
02_fixtures.html
666 lines (531 loc) · 39.8 KB
/
02_fixtures.html
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
<!DOCTYPE html>
<html>
<head><meta charset="utf-8" />
<title>Fixtures</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<style type="text/css">
@import 'oNmeLFPs3BZEPiaf.css'
</style>
<style type="text/css">
@import '7yfL-gIu1itCwQWz.css'
</style>
<style type="text/css">
/* Overrides of notebook CSS for static HTML export */
body {
overflow: visible;
padding: 8px;
}
div#notebook {
overflow: visible;
border-top: none;
}@media print {
div.cell {
display: block;
page-break-inside: avoid;
}
div.output_wrapper {
display: block;
page-break-inside: avoid;
}
div.output {
display: block;
page-break-inside: avoid;
}
}
</style>
<!-- Custom stylesheet, it must be in the same directory as the html file -->
<link rel="stylesheet" href="custom.css">
<!-- Loading mathjax macro -->
<!-- Load mathjax -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_HTML"></script>
<!-- MathJax configuration -->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true,
processEnvironments: true
},
// Center justify equations in code and markdown cells. Elsewhere
// we use CSS to left justify single line equations in code cells.
displayAlign: 'center',
"HTML-CSS": {
styles: {'.MathJax_Display': {"margin": 0}},
linebreaks: { automatic: true }
}
});
</script>
<!-- End of mathjax configuration -->
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body>
<div tabindex="-1" id="notebook" class="border-box-sizing">
<div class="container" id="notebook-container">
<h1 id="main-title">Best practices in software engineering</h1>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h1 id="Input-data-for-tests">Input data for tests<a class="anchor-link" href="#Input-data-for-tests">¶</a></h1><p>As we saw in the last section, when using parametrisation it's often useful to split your test function into two logical parts:</p>
<ol>
<li>The data to be tested</li>
<li>The code to do the test</li>
</ol>
<p>This is because we had a situation where we had one test function and multiple examples to test. The opposite situation also happens where we have multiple test functions, all of which want the same input data.</p>
<p>The name that pytest uses for "data which are provided to test functions" is <em>fixture</em> since it <em>fixes</em> a set of data against which to test.</p>
<p>We'll start with the example of the <code>add_arrays</code> function to explain the syntax but soon we'll need to use a example which demonstates the benefits more.</p>
<p>To make things clearer, we'll trim down the test file back to the basics. Just one test for <code>add_arrays</code> and one for <code>subtract_arrays</code>:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_arrays.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">arrays</span> <span class="kn">import</span> <span class="n">add_arrays</span><span class="p">,</span> <span class="n">subtract_arrays</span>
<span class="k">def</span> <span class="nf">test_add_arrays</span><span class="p">():</span>
<span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
<span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="n">expect</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">9</span><span class="p">]</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">add_arrays</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">output</span> <span class="o">==</span> <span class="n">expect</span>
<span class="k">def</span> <span class="nf">test_subtract_arrays</span><span class="p">():</span>
<span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
<span class="n">b</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="n">expect</span> <span class="o">=</span> <span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">]</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">subtract_arrays</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">output</span> <span class="o">==</span> <span class="n">expect</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Both of these tests use the same values for <code>a</code> and <code>b</code>. In this example it's not really a big problem that we're repeating ourselves but in a more complex test suite the data we're testing over may be very complicated or slow to create.</p>
<p>To avoid repeating ourselves, we will create a fixture which will provide the data to our tests. To create our fixture we define a function which is decorated with the <code>pytest.fixture</code> decorator. Apart from that, all the function needs do is return the data we want to provide to our tests, in this case, the two input lists:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pytest</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">pair_of_lists</span><span class="p">():</span>
<span class="k">return</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
</pre></div>
<p>To make the test functions make use of the fixture, we use the name of the fixture (<code>pair_of_lists</code>) as a parameter of the test function, similar to how we did with parametrisation:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">test_add_arrays</span><span class="p">(</span><span class="n">pair_of_lists</span><span class="p">):</span>
<span class="o">...</span>
</pre></div>
<p>The data are now available inside the function using that name and we can use it however we wish:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">test_add_arrays</span><span class="p">(</span><span class="n">pair_of_lists</span><span class="p">):</span>
<span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">pair_of_lists</span>
<span class="o">...</span>
</pre></div>
<p>This isn't how functions and arguments usually work in Python. pytest is doing something magic here and is matching up the names of things which it knows are fixtures (due to the decorator) with the names of parameters to test functions, automatically running the fixture and passing in the data.</p>
<p>Note that <code>pair_of_lists</code> here is not a test function. It does not contain any <code>assert</code>s and will not explicitly appear in the <code>pytest</code> output.</p>
<p>Putting it all together, we end up with:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_arrays.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">from</span> <span class="nn">arrays</span> <span class="kn">import</span> <span class="n">add_arrays</span><span class="p">,</span> <span class="n">subtract_arrays</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">pair_of_lists</span><span class="p">():</span>
<span class="k">return</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">test_add_arrays</span><span class="p">(</span><span class="n">pair_of_lists</span><span class="p">):</span>
<span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">pair_of_lists</span>
<span class="n">expect</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">9</span><span class="p">]</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">add_arrays</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">output</span> <span class="o">==</span> <span class="n">expect</span>
<span class="k">def</span> <span class="nf">test_subtract_arrays</span><span class="p">(</span><span class="n">pair_of_lists</span><span class="p">):</span>
<span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">pair_of_lists</span>
<span class="n">expect</span> <span class="o">=</span> <span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">]</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">subtract_arrays</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">output</span> <span class="o">==</span> <span class="n">expect</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>When we run the test suite, pytest will automatically run the <code>pair_of_lists</code> function for each test and pass in the result.</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">$</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">pytest</span> <span class="o">-</span><span class="n">v</span>
</pre></div>
</div>
</div>
</div>
<div class="output_wrapper">
<div class="output">
<div class="output_area">
<div class="prompt"></div>
<div class="output_subarea output_stream output_stdout output_text">
<pre><span class="ansi-bold">=================== test session starts ====================</span>
platform linux -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- /home/matt/projects/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/software_engineering_best_practices
plugins: nbval-0.9.3
collected 2 items
test_arrays.py::test_add_arrays <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 50%]</span>
test_arrays.py::test_subtract_arrays <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [100%]</span>
<span class="ansi-green-intense-fg ansi-bold">==================== 2 passed in 0.03s =====================</span>
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h2 id="A-different-example">A different example<a class="anchor-link" href="#A-different-example">¶</a></h2><p>It might be hard to see the benefit of fixtures with this rather contrived example so lets take a look at a more sensible one where using a fixture makes sense.</p>
<p>Make a new file called <code>books.py</code> which contains the following:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">books.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">def</span> <span class="nf">word_count</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">word</span><span class="o">=</span><span class="s1">''</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Count the number of occurences of ``word`` in a string.</span>
<span class="sd"> If ``word`` is not set, count all words.</span>
<span class="sd"> </span>
<span class="sd"> Args:</span>
<span class="sd"> text (str): the text corpus to search through</span>
<span class="sd"> word (str): the word to count instances of</span>
<span class="sd"> Returns:</span>
<span class="sd"> int: the count of ``word`` in ``text``</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">word</span><span class="p">:</span>
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">text_word</span> <span class="ow">in</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">():</span>
<span class="k">if</span> <span class="n">text_word</span> <span class="o">==</span> <span class="n">word</span><span class="p">:</span>
<span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">count</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>To test this function we want a corpus of text to test it on. For the purposes of this example and to simulate a complex data input, we will download the contents of a particularly long novel from Project Gutenberg. Our test function uses <a href="https://docs.python.org/3/library/urllib.request.html"><code>urllib.request</code></a> to download the text, converts it to a string and passes that to the <code>word_count</code> function.</p>
<p>At first we will simply check that the word "hat" appears 33 times in the book:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_books.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="kn">from</span> <span class="nn">books</span> <span class="kn">import</span> <span class="n">word_count</span>
<span class="k">def</span> <span class="nf">test_word_counts</span><span class="p">():</span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"https://www.gutenberg.org/files/2600/2600-0.txt"</span>
<span class="n">book_text</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">word_count</span><span class="p">(</span><span class="n">book_text</span><span class="p">,</span> <span class="s2">"hat"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">33</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">$</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">pytest</span> <span class="o">-</span><span class="n">v</span> <span class="n">test_books</span><span class="o">.</span><span class="n">py</span>
</pre></div>
</div>
</div>
</div>
<div class="output_wrapper">
<div class="output">
<div class="output_area">
<div class="prompt"></div>
<div class="output_subarea output_stream output_stdout output_text">
<pre><span class="ansi-bold">=================== test session starts ====================</span>
platform linux -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- /home/matt/projects/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/software_engineering_best_practices
plugins: nbval-0.9.3
collected 1 item
test_books.py::test_word_counts <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [100%]</span>
<span class="ansi-green-intense-fg ansi-bold">==================== 1 passed in 2.32s =====================</span>
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>The test has passed and it took about two seconds. This is because it takes some time to download the file from the internet. For this example we <em>want</em> it to take some time as it helps demonstrate the point. In reality you will come across test data inputs which take some time (more than a few milliseconds) to create.</p>
<p>This creates a tension between wanting to have a large test suite which covers your code from lots of different angles and being able to run it very quickly and easily. An ideal test suite will run as quickly as possible as it will encourage you to run it more often. It's a good idea to have at least a subset of your tests which run through in some number of seconds rather than hours.</p>
<p>Two seconds is not bad for this test but if we want to test against multiple examples, it could get slow. Let's parametrise the test to add in a bunch more inputs:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_books.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">from</span> <span class="nn">books</span> <span class="kn">import</span> <span class="n">word_count</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'word, count'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'hat'</span><span class="p">,</span> <span class="mi">33</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'freedom'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'electricity'</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'testing'</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Prince'</span><span class="p">,</span> <span class="mi">1498</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'internet'</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Russia'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Pierre'</span><span class="p">,</span> <span class="mi">1260</span><span class="p">),</span>
<span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="mi">566311</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">def</span> <span class="nf">test_word_counts</span><span class="p">(</span><span class="n">word</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"https://www.gutenberg.org/files/2600/2600-0.txt"</span>
<span class="n">book_text</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">word_count</span><span class="p">(</span><span class="n">book_text</span><span class="p">,</span> <span class="n">word</span><span class="p">)</span> <span class="o">==</span> <span class="n">count</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">$</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">pytest</span> <span class="o">-</span><span class="n">v</span> <span class="n">test_books</span><span class="o">.</span><span class="n">py</span>
</pre></div>
</div>
</div>
</div>
<div class="output_wrapper">
<div class="output">
<div class="output_area">
<div class="prompt"></div>
<div class="output_subarea output_stream output_stdout output_text">
<pre><span class="ansi-bold">=================== test session starts ====================</span>
platform linux -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- /home/matt/projects/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/software_engineering_best_practices
plugins: nbval-0.9.3
collected 9 items
test_books.py::test_word_counts[hat-33] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 11%]</span>
test_books.py::test_word_counts[freedom-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 22%]</span>
test_books.py::test_word_counts[electricity-1] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 33%]</span>
test_books.py::test_word_counts[testing-3] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 44%]</span>
test_books.py::test_word_counts[Prince-1498] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 55%]</span>
test_books.py::test_word_counts[internet-0] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 66%]</span>
test_books.py::test_word_counts[Russia-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 77%]</span>
test_books.py::test_word_counts[Pierre-1260] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 88%]</span>
test_books.py::test_word_counts[None-566311] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [100%]</span>
<span class="ansi-green-intense-fg ansi-bold">==================== 9 passed in 24.30s ====================</span>
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>You see here that it took about nine times as long. This is because the file is downloaded afresh for every test example where really, it only <em>needs</em> to be downloaded once.</p>
<p>Let's move the slow setup into a fixture and give that as a parameter of the test function:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_books.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">from</span> <span class="nn">books</span> <span class="kn">import</span> <span class="n">word_count</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">long_book</span><span class="p">():</span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"https://www.gutenberg.org/files/2600/2600-0.txt"</span>
<span class="n">book_text</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">book_text</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'word, count'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'hat'</span><span class="p">,</span> <span class="mi">33</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'freedom'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'electricity'</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'testing'</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Prince'</span><span class="p">,</span> <span class="mi">1498</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'internet'</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Russia'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Pierre'</span><span class="p">,</span> <span class="mi">1260</span><span class="p">),</span>
<span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="mi">566311</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">def</span> <span class="nf">test_word_counts</span><span class="p">(</span><span class="n">long_book</span><span class="p">,</span> <span class="n">word</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>
<span class="k">assert</span> <span class="n">word_count</span><span class="p">(</span><span class="n">long_book</span><span class="p">,</span> <span class="n">word</span><span class="p">)</span> <span class="o">==</span> <span class="n">count</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">$</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">pytest</span> <span class="o">-</span><span class="n">v</span> <span class="n">test_books</span><span class="o">.</span><span class="n">py</span>
</pre></div>
</div>
</div>
</div>
<div class="output_wrapper">
<div class="output">
<div class="output_area">
<div class="prompt"></div>
<div class="output_subarea output_stream output_stdout output_text">
<pre><span class="ansi-bold">=================== test session starts ====================</span>
platform linux -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- /home/matt/projects/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/software_engineering_best_practices
plugins: nbval-0.9.3
collected 9 items
test_books.py::test_word_counts[hat-33] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 11%]</span>
test_books.py::test_word_counts[freedom-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 22%]</span>
test_books.py::test_word_counts[electricity-1] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 33%]</span>
test_books.py::test_word_counts[testing-3] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 44%]</span>
test_books.py::test_word_counts[Prince-1498] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 55%]</span>
test_books.py::test_word_counts[internet-0] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 66%]</span>
test_books.py::test_word_counts[Russia-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 77%]</span>
test_books.py::test_word_counts[Pierre-1260] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 88%]</span>
test_books.py::test_word_counts[None-566311] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [100%]</span>
<span class="ansi-green-intense-fg ansi-bold">==================== 9 passed in 24.42s ====================</span>
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Perhaps surprisingly, it is still taking very long time!</p>
<p>By default a fixture will run once for every test function that uses it. In our case we only need it to run once for all the tests in the test session so we can pass in the <code>scope</code> parameter to <code>pytest.fixture</code> and set it to <code>session</code>:</p>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">test_books.py</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">from</span> <span class="nn">books</span> <span class="kn">import</span> <span class="n">word_count</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">"session"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">long_book</span><span class="p">():</span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"https://www.gutenberg.org/files/2600/2600-0.txt"</span>
<span class="n">book_text</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">book_text</span>
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'word, count'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'hat'</span><span class="p">,</span> <span class="mi">33</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'freedom'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'electricity'</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'testing'</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Prince'</span><span class="p">,</span> <span class="mi">1498</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'internet'</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Russia'</span><span class="p">,</span> <span class="mi">71</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Pierre'</span><span class="p">,</span> <span class="mi">1260</span><span class="p">),</span>
<span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="mi">566311</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">def</span> <span class="nf">test_word_counts</span><span class="p">(</span><span class="n">long_book</span><span class="p">,</span> <span class="n">word</span><span class="p">,</span> <span class="n">count</span><span class="p">):</span>
<span class="k">assert</span> <span class="n">word_count</span><span class="p">(</span><span class="n">long_book</span><span class="p">,</span> <span class="n">word</span><span class="p">)</span> <span class="o">==</span> <span class="n">count</span>
</pre></div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered celltag_nbval-skip">
<div class="input">
<div class="prompt input_prompt">$</div>
<div class="inner_cell">
<div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">pytest</span> <span class="o">-</span><span class="n">v</span> <span class="n">test_books</span><span class="o">.</span><span class="n">py</span>
</pre></div>
</div>
</div>
</div>
<div class="output_wrapper">
<div class="output">
<div class="output_area">
<div class="prompt"></div>
<div class="output_subarea output_stream output_stdout output_text">
<pre><span class="ansi-bold">=================== test session starts ====================</span>
platform linux -- Python 3.7.3, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- /home/matt/projects/software_engineering_best_practices/venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/matt/projects/software_engineering_best_practices
plugins: nbval-0.9.3
collected 9 items
test_books.py::test_word_counts[hat-33] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 11%]</span>
test_books.py::test_word_counts[freedom-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 22%]</span>
test_books.py::test_word_counts[electricity-1] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 33%]</span>
test_books.py::test_word_counts[testing-3] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 44%]</span>
test_books.py::test_word_counts[Prince-1498] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 55%]</span>
test_books.py::test_word_counts[internet-0] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 66%]</span>
test_books.py::test_word_counts[Russia-71] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 77%]</span>
test_books.py::test_word_counts[Pierre-1260] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [ 88%]</span>
test_books.py::test_word_counts[None-566311] <span class="ansi-green-fg">PASSED</span><span class="ansi-cyan-fg"> [100%]</span>
<span class="ansi-green-intense-fg ansi-bold">==================== 9 passed in 3.45s =====================</span>
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Now it only takes about as long as a single test did since the slow part is only being done once.</p>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered exercise"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="Exercise">Exercise<a class="anchor-link" href="#Exercise">¶</a></h3><p>Add some more parameters to the test and check that it doesn't take any longer to run</p>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt">
</div><div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p><a href="Testing.html"><font size="5">Previous</font></a><font size="5"> | </font><a href="Exercise.html"><font size="5">Next</font></a></p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>