forked from UWPCE-PythonCert/ProgrammingInPython
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTestDrivenDevelopment.html
More file actions
1729 lines (1608 loc) · 180 KB
/
Copy pathTestDrivenDevelopment.html
File metadata and controls
1729 lines (1608 loc) · 180 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
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
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="writer-html5" lang="en" data-content_root="../">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test Driven Development — Programming in Python 8.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=e59714d7" />
<script src="../_static/jquery.js?v=5d32c60e"></script>
<script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
<script src="../_static/documentation_options.js?v=bb3927b2"></script>
<script src="../_static/doctools.js?v=9bcbadda"></script>
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="../_static/js/theme.js"></script>
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="next" title="Advanced Testing" href="Testing_advanced.html" />
<link rel="prev" title="Testing" href="Testing.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" style="background: #4b2e83" >
<a href="../index.html">
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="../search.html" method="get">
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Topics in the Program</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="../topics/01-setting_up/index.html">1. Setting up your Environment</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/02-basic_python/index.html">2. Basic Python</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/03-recursion_booleans/index.html">3. Booleans and Recursion</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/04-sequences_iteration/index.html">4. Sequences and Iteration</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/05-text_handling/index.html">5. Basic Text Handling</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/06-exceptions/index.html">6. Exception Handling</a></li>
<li class="toctree-l1 current"><a class="reference internal" href="../topics/07-unit_testing/index.html">7. Unit Testing</a><ul class="current">
<li class="toctree-l2"><a class="reference internal" href="Testing.html">Testing</a></li>
<li class="toctree-l2 current"><a class="current reference internal" href="#">Test Driven Development</a></li>
<li class="toctree-l2"><a class="reference internal" href="Testing_advanced.html">Advanced Testing</a></li>
<li class="toctree-l2"><a class="reference internal" href="../exercises/unit_testing/unit_testing.html">Introduction To Unit Testing</a></li>
<li class="toctree-l2"><a class="reference internal" href="../exercises/mailroom/mailroom_with_tests.html">Mailroom With Unit Tests</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="../topics/08-dicts_sets/index.html">8. Dictionaries and Sets</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/09-files/index.html">9. File Handling</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/10-modules_packages/index.html">10. Modules and Packages</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/11-argument_passing/index.html">11. Advanced Argument Passing</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/12-comprehensions/index.html">12. Comprehensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/13-intro_oo/index.html">13. Intro to Object Oriented Programing</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/14-magic_methods/index.html">14. Properties and Magic Methods</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/15-subclassing/index.html">15. Subclassing and Inheritance</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/16-multiple_inheritance/index.html">16. Multiple Inheritance</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/17-functional_programming/index.html">17. Introduction to Functional Programming</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/18-advanced_testing/index.html">18. Advanced Testing</a></li>
<li class="toctree-l1"><a class="reference internal" href="../topics/99-extras/index.html">19. Extra Topics</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" style="background: #4b2e83" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="../index.html">Programming in Python</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content style-external-links">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="../index.html" class="icon icon-home" aria-label="Home"></a></li>
<li class="breadcrumb-item"><a href="../topics/07-unit_testing/index.html"><span class="section-number">7. </span>Unit Testing</a></li>
<li class="breadcrumb-item active">Test Driven Development</li>
<li class="wy-breadcrumbs-aside">
<a href="../_sources/modules/TestDrivenDevelopment.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul><div class="rst-breadcrumbs-buttons" role="navigation" aria-label="Sequential page navigation">
<a href="Testing.html" class="btn btn-neutral float-left" title="Testing" accesskey="p"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
<a href="Testing_advanced.html" class="btn btn-neutral float-right" title="Advanced Testing" accesskey="n">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="test-driven-development">
<span id="id1"></span><h1>Test Driven Development<a class="headerlink" href="#test-driven-development" title="Link to this heading"></a></h1>
<p>“Testing” is any strategy for making sure your code behaves as expected. “Unit testing” is a particular strategy, that:</p>
<ul class="simple">
<li><p>is easy to run in an automated fashion.</p></li>
<li><p>utilizes isolated tests for each individual function.</p></li>
</ul>
<p>“Test Driven Development” (TDD) is a development strategy that integrates the development of unit tests with the code itself. In particular, you write the tests <em>before</em> you write the code, which seems pretty backward, but it has some real strengths.</p>
<p>We’ll demonstrate this technique with an example.</p>
<p>The following is adapted from Mark Pilgrim’s excellent “Dive into Python”:</p>
<p><a class="reference external" href="https://diveintopython3.problemsolving.io/">https://diveintopython3.problemsolving.io/</a></p>
<p>The primary difference is that this version uses the simpler pytest testing framework, rather than <cite>unittest</cite>, which is discussed in
<a class="reference internal" href="Testing.html#unit-testing"><span class="std std-ref">Testing</span></a></p>
<section id="unit-testing">
<h2>Unit Testing<a class="headerlink" href="#unit-testing" title="Link to this heading"></a></h2>
<blockquote>
<div><div class="line-block">
<div class="line">“Certitude is not the test of certainty. We have been cocksure of
many things that were not so.”</div>
<div class="line">— <a class="reference external" href="http://en.wikiquote.org/wiki/Oliver_Wendell_Holmes,_Jr.">Oliver Wendell Holmes,
Jr.</a></div>
</div>
</div></blockquote>
<section id="not-diving-in">
<h3>(Not) Diving In<a class="headerlink" href="#not-diving-in" title="Link to this heading"></a></h3>
<p>Kids today. So spoiled by these fast computers and fancy “dynamic”
languages. Write first, ship second, debug third (if ever). In my day,
we had discipline. <strong>Discipline, I say!</strong> We had to write programs by
<em>hand</em>, on <em>paper</em>, and feed them to the computer on <em>punchcards</em>. And
we <em>liked it!</em></p>
<p>In this module, you’re going to write and debug a set of utility
functions to convert to and from Roman numerals.</p>
<p>You’ve most likely seen Roman numerals, even if you didn’t recognize them. You may have seen them in copyrights of old movies and television shows (“Copyright MCMXLVI” instead of “Copyright 1946”), or on the dedication walls of libraries or universities (“established MDCCCLXXXVIII” instead of “established 1888”). You may also have seen them in outlines and bibliographical references. It’s a system of representing numbers that really does date back to the ancient Roman empire (hence the name).</p>
</section>
<section id="the-rules-for-roman-numerals">
<h3>The Rules for Roman Numerals<a class="headerlink" href="#the-rules-for-roman-numerals" title="Link to this heading"></a></h3>
<p>In Roman numerals, there are seven characters that are repeated and combined in various ways to represent numbers.</p>
<div class="line-block">
<div class="line">I = 1</div>
<div class="line">V = 5</div>
<div class="line">X = 10</div>
<div class="line">L = 50</div>
<div class="line">C = 100</div>
<div class="line">D = 500</div>
<div class="line">M = 1000</div>
</div>
<p>The following are some general rules for constructing Roman numerals:</p>
<ul class="simple">
<li><p>Sometimes characters are additive. I is 1, II is 2, and III is 3. VI is 6 (literally, “5 and 1”), VII is 7, and VIII is 8.</p></li>
<li><p>The tens characters (I, X, C, and M) can be repeated up to three times. At 4, you need to subtract from the next highest fives character. You can’t represent 4 as IIII; instead, it is represented as IV (“1 less than 5”). 40 is written as XL (“10 less than 50”), 41 as XLI, 42 as XLII, 43 as XLIII, and then 44 as XLIV (“10 less than 50, then 1 less than 5”).</p></li>
<li><p>Sometimes characters are … the opposite of additive. By putting certain characters before others, you subtract from the final value. For example, at 9, you need to subtract from the next highest tens character: 8 is VIII, but 9 is IX (“1 less than 10”), not VIIII (since the I character can not be repeated four times). 90 is XC, 900 is CM.</p></li>
<li><p>The fives characters can not be repeated. 10 is always represented as X, never as VV. 100 is always C, never LL.</p></li>
<li><p>Roman numerals are read left to right, so the order of characters matters very much. DC is 600; CD is a completely different number (400, “100 less than 500”). CI is 101; IC is not even a valid Roman numeral (because you can’t subtract 1 directly from 100; you would need to write it as XCIX, “10 less than 100, then 1 less than 10”).</p></li>
</ul>
<p>The rules for Roman numerals lead to a number of interesting observations:</p>
<ol class="arabic simple">
<li><p>There is only one correct way to represent a particular number as a
Roman numeral.</p></li>
<li><p>The converse is also true: if a string of characters is a valid Roman
numeral, it represents only one number (that is, it can only be
interpreted one way).</p></li>
<li><p>There is a limited range of numbers that can be expressed as Roman
numerals, specifically <code class="docutils literal notranslate"><span class="pre">1</span></code> through <code class="docutils literal notranslate"><span class="pre">3999</span></code>. The Romans did have
several ways of expressing larger numbers, for instance by having a
bar over a numeral to represent that its normal value should be
multiplied by <code class="docutils literal notranslate"><span class="pre">1000</span></code>. For the purposes of this exercise, let’s
stipulate that Roman numerals go from <code class="docutils literal notranslate"><span class="pre">1</span></code> to <code class="docutils literal notranslate"><span class="pre">3999</span></code>.</p></li>
<li><p>There is no way to represent 0 in Roman numerals.</p></li>
<li><p>There is no way to represent negative numbers in Roman numerals.</p></li>
<li><p>There is no way to represent fractions or non-integer numbers in
Roman numerals.</p></li>
</ol>
<p>Let’s start mapping out what a <code class="docutils literal notranslate"><span class="pre">roman.py</span></code> module should do. It will
have two main functions, <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> and <code class="docutils literal notranslate"><span class="pre">from_roman()</span></code>. The
<code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function should take an integer from <code class="docutils literal notranslate"><span class="pre">1</span></code> to <code class="docutils literal notranslate"><span class="pre">3999</span></code>
and return the Roman numeral representation as a string …</p>
<p>Stop right there. Now let’s do something a little unexpected: write a
test case that checks whether the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function does what you
want it to. You read that right: you’re going to write code that tests
code that you haven’t written yet.</p>
<p>This is called <em>test-driven development</em>, or TDD. The set of two
conversion functions — <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code>, and later <code class="docutils literal notranslate"><span class="pre">from_roman()</span></code> — can
be written and tested as a unit, separate from any larger program that
uses them.</p>
<p>Technically, you can write unit tests with plain Python – recall the <code class="docutils literal notranslate"><span class="pre">assert</span></code> statement that you have already used to write simple tests. But it is very helpful to use a framework to make it easier to write and run your tests. In this program, we use the <cite>pytest</cite> package: it is both very easy to get started with, and provides a lot of powerful features to aid in testing complex systems.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">pytest</span></code> does not come with Python out of the box. But it is easily installable via <cite>pip</cite> (or conda, if you are using conda):</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python -m pip install pytest
</pre></div>
</div>
</div>
<p>Once installed, you should have the pytest command available in your terminal.</p>
<p>Unit testing is an important part of an overall testing-centric
development strategy. If you write unit tests, it is important to write
them early and to keep them updated as code and requirements change.
Many people advocate writing tests before they write the code they’re
testing, and that’s the style I’m going to demonstrate here.</p>
<p>But unit tests are beneficial, even critical, no matter when you write them.</p>
<ul class="simple">
<li><p>Before writing code, writing unit tests forces you to detail your
requirements in a useful fashion.</p></li>
<li><p>While writing code, unit tests keep you from over-coding. When all
the test cases pass, the function is complete.</p></li>
<li><p>When refactoring code, they can help prove that the new version
behaves the same way as the old version.</p></li>
<li><p>When maintaining code, having tests will help you cover your ass when
someone comes screaming that your latest change broke their old code.
(“But <em>sir</em>, all the unit tests passed when I checked it in…”)</p></li>
<li><p>When writing code in a team, having a comprehensive test suite
dramatically decreases the chances that your code will break someone
else’s code, because you can run their unit tests first. (I’ve seen
this sort of thing in code sprints. A team breaks up the assignment,
everybody takes the specs for their task, writes unit tests for it,
then shares their unit tests with the rest of the team. That way,
nobody goes off too far into developing code that doesn’t play well
with others.)</p></li>
</ul>
</section>
<section id="a-single-question">
<h3>A Single Question<a class="headerlink" href="#a-single-question" title="Link to this heading"></a></h3>
<p class="centered">
<strong><strong>Every Test is an Island</strong></strong></p><p>A test case answers a single question about the code it is testing. A
test case should be able to…</p>
<ul class="simple">
<li><p>Run completely by itself, without any human input. Unit testing is
about automation.</p></li>
<li><p>Determine by itself whether the function it is testing has passed
or failed, without a human interpreting the results.</p></li>
<li><p>Run in isolation, separate from any other test cases (even if they
test the same functions). Each test case is an island.</p></li>
</ul>
<p>Given that, let’s build a test case for the first requirement:</p>
<ol class="arabic simple">
<li><p>The <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function should return the Roman numeral
representation for all integers <code class="docutils literal notranslate"><span class="pre">1</span></code> to <code class="docutils literal notranslate"><span class="pre">3999</span></code>.</p></li>
</ol>
<p>Let’s take a look at
<a class="reference download internal" download="" href="../_downloads/d969cdad151d9d616317d2834767eb1f/roman.py"><code class="xref download docutils literal notranslate"><span class="pre">roman.py</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="sd">"""</span>
<span class="linenos"> 2</span><span class="sd">roman.py</span>
<span class="linenos"> 3</span>
<span class="linenos"> 4</span><span class="sd">A Roman numeral to Arabic numeral (and back!) converter</span>
<span class="linenos"> 5</span>
<span class="linenos"> 6</span><span class="sd">complete with tests</span>
<span class="linenos"> 7</span>
<span class="linenos"> 8</span><span class="sd">tests are expected to be able to be run with the pytest system</span>
<span class="linenos"> 9</span><span class="sd">"""</span>
<span class="linenos">10</span>
<span class="linenos">11</span> <span class="c1">## Tests for roman numeral conversion</span>
<span class="linenos">12</span>
<span class="linenos">13</span> <span class="n">KNOWN_VALUES</span> <span class="o">=</span> <span class="p">(</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'I'</span><span class="p">),</span>
<span class="linenos">14</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'II'</span><span class="p">),</span>
<span class="linenos">15</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'III'</span><span class="p">),</span>
<span class="linenos">16</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'IV'</span><span class="p">),</span>
<span class="linenos">17</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">'V'</span><span class="p">),</span>
<span class="linenos">18</span> <span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="s1">'VI'</span><span class="p">),</span>
<span class="linenos">19</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">'VII'</span><span class="p">),</span>
<span class="linenos">20</span> <span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="s1">'VIII'</span><span class="p">),</span>
<span class="linenos">21</span> <span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="s1">'IX'</span><span class="p">),</span>
<span class="linenos">22</span> <span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="s1">'X'</span><span class="p">),</span>
<span class="linenos">23</span> <span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="s1">'L'</span><span class="p">),</span>
<span class="linenos">24</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="s1">'C'</span><span class="p">),</span>
<span class="linenos">25</span> <span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s1">'D'</span><span class="p">),</span>
<span class="linenos">26</span> <span class="p">(</span><span class="mi">1000</span><span class="p">,</span> <span class="s1">'M'</span><span class="p">),</span>
<span class="linenos">27</span> <span class="p">(</span><span class="mi">31</span><span class="p">,</span> <span class="s1">'XXXI'</span><span class="p">),</span>
<span class="linenos">28</span> <span class="p">(</span><span class="mi">148</span><span class="p">,</span> <span class="s1">'CXLVIII'</span><span class="p">),</span>
<span class="linenos">29</span> <span class="p">(</span><span class="mi">294</span><span class="p">,</span> <span class="s1">'CCXCIV'</span><span class="p">),</span>
<span class="linenos">30</span> <span class="p">(</span><span class="mi">312</span><span class="p">,</span> <span class="s1">'CCCXII'</span><span class="p">),</span>
<span class="linenos">31</span> <span class="p">(</span><span class="mi">421</span><span class="p">,</span> <span class="s1">'CDXXI'</span><span class="p">),</span>
<span class="linenos">32</span> <span class="p">(</span><span class="mi">528</span><span class="p">,</span> <span class="s1">'DXXVIII'</span><span class="p">),</span>
<span class="linenos">33</span> <span class="p">(</span><span class="mi">621</span><span class="p">,</span> <span class="s1">'DCXXI'</span><span class="p">),</span>
<span class="linenos">34</span> <span class="p">(</span><span class="mi">782</span><span class="p">,</span> <span class="s1">'DCCLXXXII'</span><span class="p">),</span>
<span class="linenos">35</span> <span class="p">(</span><span class="mi">870</span><span class="p">,</span> <span class="s1">'DCCCLXX'</span><span class="p">),</span>
<span class="linenos">36</span> <span class="p">(</span><span class="mi">941</span><span class="p">,</span> <span class="s1">'CMXLI'</span><span class="p">),</span>
<span class="linenos">37</span> <span class="p">(</span><span class="mi">1043</span><span class="p">,</span> <span class="s1">'MXLIII'</span><span class="p">),</span>
<span class="linenos">38</span> <span class="p">(</span><span class="mi">1110</span><span class="p">,</span> <span class="s1">'MCX'</span><span class="p">),</span>
<span class="linenos">39</span> <span class="p">(</span><span class="mi">1226</span><span class="p">,</span> <span class="s1">'MCCXXVI'</span><span class="p">),</span>
<span class="linenos">40</span> <span class="p">(</span><span class="mi">1301</span><span class="p">,</span> <span class="s1">'MCCCI'</span><span class="p">),</span>
<span class="linenos">41</span> <span class="p">(</span><span class="mi">1485</span><span class="p">,</span> <span class="s1">'MCDLXXXV'</span><span class="p">),</span>
<span class="linenos">42</span> <span class="p">(</span><span class="mi">1509</span><span class="p">,</span> <span class="s1">'MDIX'</span><span class="p">),</span>
<span class="linenos">43</span> <span class="p">(</span><span class="mi">1607</span><span class="p">,</span> <span class="s1">'MDCVII'</span><span class="p">),</span>
<span class="linenos">44</span> <span class="p">(</span><span class="mi">1754</span><span class="p">,</span> <span class="s1">'MDCCLIV'</span><span class="p">),</span>
<span class="linenos">45</span> <span class="p">(</span><span class="mi">1832</span><span class="p">,</span> <span class="s1">'MDCCCXXXII'</span><span class="p">),</span>
<span class="linenos">46</span> <span class="p">(</span><span class="mi">1993</span><span class="p">,</span> <span class="s1">'MCMXCIII'</span><span class="p">),</span>
<span class="linenos">47</span> <span class="p">(</span><span class="mi">2074</span><span class="p">,</span> <span class="s1">'MMLXXIV'</span><span class="p">),</span>
<span class="linenos">48</span> <span class="p">(</span><span class="mi">2152</span><span class="p">,</span> <span class="s1">'MMCLII'</span><span class="p">),</span>
<span class="linenos">49</span> <span class="p">(</span><span class="mi">2212</span><span class="p">,</span> <span class="s1">'MMCCXII'</span><span class="p">),</span>
<span class="linenos">50</span> <span class="p">(</span><span class="mi">2343</span><span class="p">,</span> <span class="s1">'MMCCCXLIII'</span><span class="p">),</span>
<span class="linenos">51</span> <span class="p">(</span><span class="mi">2499</span><span class="p">,</span> <span class="s1">'MMCDXCIX'</span><span class="p">),</span>
<span class="linenos">52</span> <span class="p">(</span><span class="mi">2574</span><span class="p">,</span> <span class="s1">'MMDLXXIV'</span><span class="p">),</span>
<span class="linenos">53</span> <span class="p">(</span><span class="mi">2646</span><span class="p">,</span> <span class="s1">'MMDCXLVI'</span><span class="p">),</span>
<span class="linenos">54</span> <span class="p">(</span><span class="mi">2723</span><span class="p">,</span> <span class="s1">'MMDCCXXIII'</span><span class="p">),</span>
<span class="linenos">55</span> <span class="p">(</span><span class="mi">2892</span><span class="p">,</span> <span class="s1">'MMDCCCXCII'</span><span class="p">),</span>
<span class="linenos">56</span> <span class="p">(</span><span class="mi">2975</span><span class="p">,</span> <span class="s1">'MMCMLXXV'</span><span class="p">),</span>
<span class="linenos">57</span> <span class="p">(</span><span class="mi">3051</span><span class="p">,</span> <span class="s1">'MMMLI'</span><span class="p">),</span>
<span class="linenos">58</span> <span class="p">(</span><span class="mi">3185</span><span class="p">,</span> <span class="s1">'MMMCLXXXV'</span><span class="p">),</span>
<span class="linenos">59</span> <span class="p">(</span><span class="mi">3250</span><span class="p">,</span> <span class="s1">'MMMCCL'</span><span class="p">),</span>
<span class="linenos">60</span> <span class="p">(</span><span class="mi">3313</span><span class="p">,</span> <span class="s1">'MMMCCCXIII'</span><span class="p">),</span>
<span class="linenos">61</span> <span class="p">(</span><span class="mi">3408</span><span class="p">,</span> <span class="s1">'MMMCDVIII'</span><span class="p">),</span>
<span class="linenos">62</span> <span class="p">(</span><span class="mi">3501</span><span class="p">,</span> <span class="s1">'MMMDI'</span><span class="p">),</span>
<span class="linenos">63</span> <span class="p">(</span><span class="mi">3610</span><span class="p">,</span> <span class="s1">'MMMDCX'</span><span class="p">),</span>
<span class="linenos">64</span> <span class="p">(</span><span class="mi">3743</span><span class="p">,</span> <span class="s1">'MMMDCCXLIII'</span><span class="p">),</span>
<span class="linenos">65</span> <span class="p">(</span><span class="mi">3844</span><span class="p">,</span> <span class="s1">'MMMDCCCXLIV'</span><span class="p">),</span>
<span class="linenos">66</span> <span class="p">(</span><span class="mi">3888</span><span class="p">,</span> <span class="s1">'MMMDCCCLXXXVIII'</span><span class="p">),</span>
<span class="linenos">67</span> <span class="p">(</span><span class="mi">3940</span><span class="p">,</span> <span class="s1">'MMMCMXL'</span><span class="p">),</span>
<span class="linenos">68</span> <span class="p">(</span><span class="mi">3999</span><span class="p">,</span> <span class="s1">'MMMCMXCIX'</span><span class="p">),</span>
<span class="linenos">69</span> <span class="p">)</span>
<span class="linenos">70</span>
<span class="linenos">71</span>
<span class="linenos">72</span><span class="k">def</span><span class="w"> </span><span class="nf">test_to_roman_known_values</span><span class="p">():</span>
<span class="linenos">73</span><span class="w"> </span><span class="sd">"""</span>
<span class="linenos">74</span><span class="sd"> to_roman should give known result with known input</span>
<span class="linenos">75</span><span class="sd"> """</span>
<span class="linenos">76</span> <span class="k">for</span> <span class="n">integer</span><span class="p">,</span> <span class="n">numeral</span> <span class="ow">in</span> <span class="n">KNOWN_VALUES</span><span class="p">:</span>
<span class="linenos">77</span> <span class="n">result</span> <span class="o">=</span> <span class="n">to_roman</span><span class="p">(</span><span class="n">integer</span><span class="p">)</span>
<span class="linenos">78</span> <span class="k">assert</span> <span class="n">numeral</span> <span class="o">==</span> <span class="n">result</span>
</pre></div>
</div>
<p>It is not immediately obvious how this code does … well, <em>anything</em>.
It defines a big data structure full of examples and a single function.</p>
<p>The entire script has no <code class="docutils literal notranslate"><span class="pre">__main__</span></code> block, so even that one function won’t run. But it does do something, I promise.</p>
<p><cite>KNOWN_VALUES</cite> is a big tuple of integer/numeral pairs that were verified manually. It includes the lowest ten numbers, the highest number, every number
that translates to a single-character Roman numeral, and a random sampling of other valid numbers.
You don’t need to test every possible input, but you should try to test all the obvious edge cases.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>This is a major challenge of unit testing – how to catch all the edge cases, without over testing every little thing.</p>
</div>
<p><cite>pytest</cite> makes it really simple to write a test case: simply define a function named <code class="docutils literal notranslate"><span class="pre">test_anything</span></code>. pytest will identify any function with: “<code class="docutils literal notranslate"><span class="pre">test_</span></code>” at the start of the name as a test function.</p>
<ul class="simple">
<li><p>Every individual test is its own function. A test function takes no parameters, returns no value, and must have a name beginning with the five letters <code class="docutils literal notranslate"><span class="pre">test_</span></code>.
If a test function exits normally without a failing assertion or other exception, the test is considered passed; if the function raises a failed assertion, failed.</p></li>
</ul>
<p>In the <code class="docutils literal notranslate"><span class="pre">test_to_roman_known_values</span></code> function, you call the actual <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function. (Well, the function hasn’t been written yet, but once it is, this is the line that will call it).
Notice that you have now defined the API for the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function: it must take an integer (the number to convert) and return a string (the Roman numeral representation). If the API is different than that, this test is considered failed.</p>
<p>Assuming the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function was defined correctly, called
correctly, completed successfully, and returned a value, the last
step is to check whether it returned the <em>right</em> value. This is
accomplished with a simple assertion that the returned value is
equal to the known correct value:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">assert</span> <span class="n">numeral</span> <span class="o">==</span> <span class="n">result</span>
</pre></div>
</div>
<p>If the assertion fails, the test fails.</p>
<p>Note that in this case, we are looping through all the known values, testing each one in the loop. If any of the known values fails, the test will fail, and end the test function – the rest of the values will not be tested.</p>
<p>If every value returned from <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> matches the known value you expect, the assert will never fail, and <code class="docutils literal notranslate"><span class="pre">test_to_roman_known_values</span></code>
eventually exits normally, which means <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> has passed this
test.</p>
<section id="write-a-test-that-fails-then-code-until-it-passes">
<h4>Write a test that fails, then code until it passes.<a class="headerlink" href="#write-a-test-that-fails-then-code-until-it-passes" title="Link to this heading"></a></h4>
<p>Once you have a test case, you can start coding the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code>
function. First, you should stub it out as an empty function and make
sure the tests fail. If the tests succeed before you’ve written any
code, your tests aren’t testing your code at all! TDD is a
dance: tests lead, code follows. Write a test that fails, then code
until it passes.</p>
<p>For a small system like this, we can put the code and the tests in the same file. But as you build larger systems, it is customary to put the tests in a separate file – more on that later.</p>
<p>You can actually try your tests out before even writing any code!</p>
<p>To run tests with pytest, you pass in the test file on the command line:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ pytest roman.py
=========================== test session starts ===========================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development
collected 1 item
roman.py F [100%]
================================ FAILURES =================================
_______________________ test_to_roman_known_values ________________________
def test_to_roman_known_values():
"""
to_roman should give known result with known input
"""
for integer, numeral in KNOWN_VALUES:
> result = to_roman(integer)
E NameError: name 'to_roman' is not defined
roman.py:75: NameError
========================= short test summary info =========================
FAILED roman.py::test_to_roman_known_values - NameError: name 'to_roman'...
============================ 1 failed in 0.15s ============================
</pre></div>
</div>
<p>There’s a lot going on here! pytest has found your test function, set itself up, and run the tests it finds (in this case only the one).
Then it runs the test (which in this case fails), and reports the failure(s).
Along with the fact that it fails, it tells you why it failed (a <code class="docutils literal notranslate"><span class="pre">NameError</span></code>) where it failed (line 75 of the file), and shows you the code before the test failure.
This may seem like a lot of information for such a simple case, but it can be invaluable in a more complex system.</p>
<p>We got a NameError, because there is no <code class="docutils literal notranslate"><span class="pre">to_roman</span></code> function defined in the file. So let’s add that now:</p>
<p>(<a class="reference download internal" download="" href="../_downloads/002778a2a65b40093409693b6043a07d/roman1.py"><code class="xref download docutils literal notranslate"><span class="pre">roman1.py</span></code></a>)</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># roman1.py</span>
<span class="k">def</span><span class="w"> </span><span class="nf">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">'''convert an integer to Roman numeral'''</span>
<span class="k">pass</span>
</pre></div>
</div>
<p>At this stage, you want to define the API of the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function, but you don’t want to code it yet (your tests need to fail first).
To stub it out, use the Python reserved word <code class="docutils literal notranslate"><span class="pre">pass</span></code>, which does precisely nothing.</p>
<p>Now run pytest again, with the function defined:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ pytest roman1.py
=========================== test session starts ===========================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development
collected 1 item
roman1.py F [100%]
================================ FAILURES =================================
_______________________ test_to_roman_known_values ________________________
def test_to_roman_known_values():
"""
to_roman should give known result with known input
"""
for integer, numeral in KNOWN_VALUES:
result = to_roman(integer)
> assert numeral == result
E AssertionError: assert 'I' == None
roman1.py:84: AssertionError
========================= short test summary info =========================
FAILED roman1.py::test_to_roman_known_values - AssertionError: assert 'I...
============================ 1 failed in 0.15s ============================
</pre></div>
</div>
<p>Again, pytest has found the test, run it, and again it failed.
But this time, it failed with an <code class="docutils literal notranslate"><span class="pre">AssertionError</span></code> – one of the known values did not equal what was expected.
In addition to the line number where the failure occurred, pytest tells you exactly what the values being compared were.
In this case, ‘I’ does not equal <code class="docutils literal notranslate"><span class="pre">None</span></code> – obviously not. But why did you get a <code class="docutils literal notranslate"><span class="pre">None</span></code> there? because Python returns None when a function does not explicitly return another value. In this case, the only content in the function is <code class="docutils literal notranslate"><span class="pre">pass</span></code>, so <code class="docutils literal notranslate"><span class="pre">None</span></code> was returned implicitly.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>It may seem silly, and a waste of time, to go through this process when you <em>know</em> that it will fail: you haven’t written the code yet!
But this is, in fact, a useful process.
You have learned that your test is running and that it really does fail when the function does nothing.
This may seem trivial, and, of course, experienced practitioners don’t <em>always</em> run tests against a do-nothing function.
But when a system gets large, with many hundreds of tests, it’s easy for things to get lost – it really is useful to know for sure that your tests are working before you start to rely on them.</p>
</div>
<p>Overall, the test run failed because at least one test case did not pass.
When a test case doesn’t pass, pytest distinguishes between failures and errors.
A failure is a failed assertion that fails because the asserted condition is not true.
An error is any other sort of exception raised in the code you’re testing or the test code itself.</p>
<p><em>Now</em>, finally, you can write the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function.</p>
<p><a class="reference download internal" download="" href="../_downloads/be3e0f48a8715ea51d96aa45c4451685/roman2.py"><code class="xref download docutils literal notranslate"><span class="pre">roman2.py</span></code></a></p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="sd">"""</span>
<span class="linenos"> 2</span><span class="sd">roman.py</span>
<span class="linenos"> 3</span>
<span class="linenos"> 4</span><span class="sd">A Roman numeral to arabic numeral (and back!) converter</span>
<span class="linenos"> 5</span>
<span class="linenos"> 6</span><span class="sd">complete with tests</span>
<span class="linenos"> 7</span>
<span class="linenos"> 8</span><span class="sd">tests are expected to be able to be run with the pytest system</span>
<span class="linenos"> 9</span><span class="sd">"""</span>
<span class="linenos"> 10</span>
<span class="linenos"> 11</span><span class="n">roman_numeral_map</span> <span class="o">=</span> <span class="p">((</span><span class="s1">'M'</span><span class="p">,</span> <span class="mi">1000</span><span class="p">),</span>
<span class="linenos"> 12</span> <span class="p">(</span><span class="s1">'CM'</span><span class="p">,</span> <span class="mi">900</span><span class="p">),</span>
<span class="linenos"> 13</span> <span class="p">(</span><span class="s1">'D'</span><span class="p">,</span> <span class="mi">500</span><span class="p">),</span>
<span class="linenos"> 14</span> <span class="p">(</span><span class="s1">'CD'</span><span class="p">,</span> <span class="mi">400</span><span class="p">),</span>
<span class="linenos"> 15</span> <span class="p">(</span><span class="s1">'C'</span><span class="p">,</span> <span class="mi">100</span><span class="p">),</span>
<span class="linenos"> 16</span> <span class="p">(</span><span class="s1">'XC'</span><span class="p">,</span> <span class="mi">90</span><span class="p">),</span>
<span class="linenos"> 17</span> <span class="p">(</span><span class="s1">'L'</span><span class="p">,</span> <span class="mi">50</span><span class="p">),</span>
<span class="linenos"> 18</span> <span class="p">(</span><span class="s1">'XL'</span><span class="p">,</span> <span class="mi">40</span><span class="p">),</span>
<span class="linenos"> 19</span> <span class="p">(</span><span class="s1">'X'</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
<span class="linenos"> 20</span> <span class="p">(</span><span class="s1">'IX'</span><span class="p">,</span> <span class="mi">9</span><span class="p">),</span>
<span class="linenos"> 21</span> <span class="p">(</span><span class="s1">'V'</span><span class="p">,</span> <span class="mi">5</span><span class="p">),</span>
<span class="linenos"> 22</span> <span class="p">(</span><span class="s1">'IV'</span><span class="p">,</span> <span class="mi">4</span><span class="p">),</span>
<span class="linenos"> 23</span> <span class="p">(</span><span class="s1">'I'</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
<span class="linenos"> 24</span>
<span class="linenos"> 25</span>
<span class="linenos"> 26</span><span class="k">def</span><span class="w"> </span><span class="nf">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="linenos"> 27</span><span class="w"> </span><span class="sd">'''convert integer to Roman numeral'''</span>
<span class="linenos"> 28</span> <span class="n">result</span> <span class="o">=</span> <span class="s1">''</span>
<span class="linenos"> 29</span> <span class="k">for</span> <span class="n">numeral</span><span class="p">,</span> <span class="n">integer</span> <span class="ow">in</span> <span class="n">roman_numeral_map</span><span class="p">:</span>
<span class="linenos"> 30</span> <span class="k">while</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">integer</span><span class="p">:</span>
<span class="linenos"> 31</span> <span class="n">result</span> <span class="o">+=</span> <span class="n">numeral</span>
<span class="linenos"> 32</span> <span class="n">n</span> <span class="o">-=</span> <span class="n">integer</span>
<span class="linenos"> 33</span> <span class="k">return</span> <span class="n">result</span>
<span class="linenos"> 34</span>
<span class="linenos"> 35</span>
<span class="linenos"> 36</span><span class="c1">## Tests for roman numeral conversion</span>
<span class="linenos"> 37</span>
<span class="linenos"> 38</span><span class="n">KNOWN_VALUES</span> <span class="o">=</span> <span class="p">(</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'I'</span><span class="p">),</span>
<span class="linenos"> 39</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'II'</span><span class="p">),</span>
<span class="linenos"> 40</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'III'</span><span class="p">),</span>
<span class="linenos"> 41</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'IV'</span><span class="p">),</span>
<span class="linenos"> 42</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s1">'V'</span><span class="p">),</span>
<span class="linenos"> 43</span> <span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="s1">'VI'</span><span class="p">),</span>
<span class="linenos"> 44</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">'VII'</span><span class="p">),</span>
<span class="linenos"> 45</span> <span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="s1">'VIII'</span><span class="p">),</span>
<span class="linenos"> 46</span> <span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="s1">'IX'</span><span class="p">),</span>
<span class="linenos"> 47</span> <span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="s1">'X'</span><span class="p">),</span>
<span class="linenos"> 48</span> <span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="s1">'L'</span><span class="p">),</span>
<span class="linenos"> 49</span> <span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="s1">'C'</span><span class="p">),</span>
<span class="linenos"> 50</span> <span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s1">'D'</span><span class="p">),</span>
<span class="linenos"> 51</span> <span class="p">(</span><span class="mi">1000</span><span class="p">,</span> <span class="s1">'M'</span><span class="p">),</span>
<span class="linenos"> 52</span> <span class="p">(</span><span class="mi">31</span><span class="p">,</span> <span class="s1">'XXXI'</span><span class="p">),</span>
<span class="linenos"> 53</span> <span class="p">(</span><span class="mi">148</span><span class="p">,</span> <span class="s1">'CXLVIII'</span><span class="p">),</span>
<span class="linenos"> 54</span> <span class="p">(</span><span class="mi">294</span><span class="p">,</span> <span class="s1">'CCXCIV'</span><span class="p">),</span>
<span class="linenos"> 55</span> <span class="p">(</span><span class="mi">312</span><span class="p">,</span> <span class="s1">'CCCXII'</span><span class="p">),</span>
<span class="linenos"> 56</span> <span class="p">(</span><span class="mi">421</span><span class="p">,</span> <span class="s1">'CDXXI'</span><span class="p">),</span>
<span class="linenos"> 57</span> <span class="p">(</span><span class="mi">528</span><span class="p">,</span> <span class="s1">'DXXVIII'</span><span class="p">),</span>
<span class="linenos"> 58</span> <span class="p">(</span><span class="mi">621</span><span class="p">,</span> <span class="s1">'DCXXI'</span><span class="p">),</span>
<span class="linenos"> 59</span> <span class="p">(</span><span class="mi">782</span><span class="p">,</span> <span class="s1">'DCCLXXXII'</span><span class="p">),</span>
<span class="linenos"> 60</span> <span class="p">(</span><span class="mi">870</span><span class="p">,</span> <span class="s1">'DCCCLXX'</span><span class="p">),</span>
<span class="linenos"> 61</span> <span class="p">(</span><span class="mi">941</span><span class="p">,</span> <span class="s1">'CMXLI'</span><span class="p">),</span>
<span class="linenos"> 62</span> <span class="p">(</span><span class="mi">1043</span><span class="p">,</span> <span class="s1">'MXLIII'</span><span class="p">),</span>
<span class="linenos"> 63</span> <span class="p">(</span><span class="mi">1110</span><span class="p">,</span> <span class="s1">'MCX'</span><span class="p">),</span>
<span class="linenos"> 64</span> <span class="p">(</span><span class="mi">1226</span><span class="p">,</span> <span class="s1">'MCCXXVI'</span><span class="p">),</span>
<span class="linenos"> 65</span> <span class="p">(</span><span class="mi">1301</span><span class="p">,</span> <span class="s1">'MCCCI'</span><span class="p">),</span>
<span class="linenos"> 66</span> <span class="p">(</span><span class="mi">1485</span><span class="p">,</span> <span class="s1">'MCDLXXXV'</span><span class="p">),</span>
<span class="linenos"> 67</span> <span class="p">(</span><span class="mi">1509</span><span class="p">,</span> <span class="s1">'MDIX'</span><span class="p">),</span>
<span class="linenos"> 68</span> <span class="p">(</span><span class="mi">1607</span><span class="p">,</span> <span class="s1">'MDCVII'</span><span class="p">),</span>
<span class="linenos"> 69</span> <span class="p">(</span><span class="mi">1754</span><span class="p">,</span> <span class="s1">'MDCCLIV'</span><span class="p">),</span>
<span class="linenos"> 70</span> <span class="p">(</span><span class="mi">1832</span><span class="p">,</span> <span class="s1">'MDCCCXXXII'</span><span class="p">),</span>
<span class="linenos"> 71</span> <span class="p">(</span><span class="mi">1993</span><span class="p">,</span> <span class="s1">'MCMXCIII'</span><span class="p">),</span>
<span class="linenos"> 72</span> <span class="p">(</span><span class="mi">2074</span><span class="p">,</span> <span class="s1">'MMLXXIV'</span><span class="p">),</span>
<span class="linenos"> 73</span> <span class="p">(</span><span class="mi">2152</span><span class="p">,</span> <span class="s1">'MMCLII'</span><span class="p">),</span>
<span class="linenos"> 74</span> <span class="p">(</span><span class="mi">2212</span><span class="p">,</span> <span class="s1">'MMCCXII'</span><span class="p">),</span>
<span class="linenos"> 75</span> <span class="p">(</span><span class="mi">2343</span><span class="p">,</span> <span class="s1">'MMCCCXLIII'</span><span class="p">),</span>
<span class="linenos"> 76</span> <span class="p">(</span><span class="mi">2499</span><span class="p">,</span> <span class="s1">'MMCDXCIX'</span><span class="p">),</span>
<span class="linenos"> 77</span> <span class="p">(</span><span class="mi">2574</span><span class="p">,</span> <span class="s1">'MMDLXXIV'</span><span class="p">),</span>
<span class="linenos"> 78</span> <span class="p">(</span><span class="mi">2646</span><span class="p">,</span> <span class="s1">'MMDCXLVI'</span><span class="p">),</span>
<span class="linenos"> 79</span> <span class="p">(</span><span class="mi">2723</span><span class="p">,</span> <span class="s1">'MMDCCXXIII'</span><span class="p">),</span>
<span class="linenos"> 80</span> <span class="p">(</span><span class="mi">2892</span><span class="p">,</span> <span class="s1">'MMDCCCXCII'</span><span class="p">),</span>
<span class="linenos"> 81</span> <span class="p">(</span><span class="mi">2975</span><span class="p">,</span> <span class="s1">'MMCMLXXV'</span><span class="p">),</span>
<span class="linenos"> 82</span> <span class="p">(</span><span class="mi">3051</span><span class="p">,</span> <span class="s1">'MMMLI'</span><span class="p">),</span>
<span class="linenos"> 83</span> <span class="p">(</span><span class="mi">3185</span><span class="p">,</span> <span class="s1">'MMMCLXXXV'</span><span class="p">),</span>
<span class="linenos"> 84</span> <span class="p">(</span><span class="mi">3250</span><span class="p">,</span> <span class="s1">'MMMCCL'</span><span class="p">),</span>
<span class="linenos"> 85</span> <span class="p">(</span><span class="mi">3313</span><span class="p">,</span> <span class="s1">'MMMCCCXIII'</span><span class="p">),</span>
<span class="linenos"> 86</span> <span class="p">(</span><span class="mi">3408</span><span class="p">,</span> <span class="s1">'MMMCDVIII'</span><span class="p">),</span>
<span class="linenos"> 87</span> <span class="p">(</span><span class="mi">3501</span><span class="p">,</span> <span class="s1">'MMMDI'</span><span class="p">),</span>
<span class="linenos"> 88</span> <span class="p">(</span><span class="mi">3610</span><span class="p">,</span> <span class="s1">'MMMDCX'</span><span class="p">),</span>
<span class="linenos"> 89</span> <span class="p">(</span><span class="mi">3743</span><span class="p">,</span> <span class="s1">'MMMDCCXLIII'</span><span class="p">),</span>
<span class="linenos"> 90</span> <span class="p">(</span><span class="mi">3844</span><span class="p">,</span> <span class="s1">'MMMDCCCXLIV'</span><span class="p">),</span>
<span class="linenos"> 91</span> <span class="p">(</span><span class="mi">3888</span><span class="p">,</span> <span class="s1">'MMMDCCCLXXXVIII'</span><span class="p">),</span>
<span class="linenos"> 92</span> <span class="p">(</span><span class="mi">3940</span><span class="p">,</span> <span class="s1">'MMMCMXL'</span><span class="p">),</span>
<span class="linenos"> 93</span> <span class="p">(</span><span class="mi">3999</span><span class="p">,</span> <span class="s1">'MMMCMXCIX'</span><span class="p">),</span>
<span class="linenos"> 94</span> <span class="p">)</span>
<span class="linenos"> 95</span>
<span class="linenos"> 96</span>
<span class="linenos"> 97</span><span class="k">def</span><span class="w"> </span><span class="nf">test_to_roman_known_values</span><span class="p">():</span>
<span class="linenos"> 98</span><span class="w"> </span><span class="sd">"""</span>
<span class="linenos"> 99</span><span class="sd"> to_roman should give known result with known input</span>
<span class="linenos">100</span><span class="sd"> """</span>
<span class="linenos">101</span> <span class="k">for</span> <span class="n">integer</span><span class="p">,</span> <span class="n">numeral</span> <span class="ow">in</span> <span class="n">KNOWN_VALUES</span><span class="p">:</span>
<span class="linenos">102</span> <span class="n">result</span> <span class="o">=</span> <span class="n">to_roman</span><span class="p">(</span><span class="n">integer</span><span class="p">)</span>
<span class="linenos">103</span> <span class="k">assert</span> <span class="n">numeral</span> <span class="o">==</span> <span class="n">result</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">roman_numeral_map</span></code> is a tuple of tuples which defines three
things: the character representations of the most basic Roman
numerals; the order of the Roman numerals (in descending value order,
from <code class="docutils literal notranslate"><span class="pre">M</span></code> all the way down to <code class="docutils literal notranslate"><span class="pre">I</span></code>); the value of each Roman
numeral. Each inner tuple is a pair of <code class="docutils literal notranslate"><span class="pre">(numeral,</span> <span class="pre">value)</span></code>. It’s not
just single-character Roman numerals; it also defines two-character
pairs like <code class="docutils literal notranslate"><span class="pre">CM</span></code> (“one hundred less than one thousand”). This makes
the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function code simpler.</p>
<p>Here’s where the rich data structure of <code class="docutils literal notranslate"><span class="pre">roman_numeral_map</span></code> pays
off, because you don’t need any special logic to handle the
subtraction rule. To convert to Roman numerals, simply iterate
through <code class="docutils literal notranslate"><span class="pre">roman_numeral_map</span></code> looking for the largest integer value
less than or equal to the input. Once found, add the Roman numeral
representation to the end of the output, subtract the corresponding
integer value from the input, lather, rinse, repeat.</p>
<p>If you’re still not clear how the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function works, add a
<code class="docutils literal notranslate"><span class="pre">print()</span></code> call to the end of the <code class="docutils literal notranslate"><span class="pre">while</span></code> loop:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">while</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">integer</span><span class="p">:</span>
<span class="n">result</span> <span class="o">+=</span> <span class="n">numeral</span>
<span class="n">n</span> <span class="o">-=</span> <span class="n">integer</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'subtracting </span><span class="si">{</span><span class="n">integer</span><span class="si">}</span><span class="s1"> from input, adding </span><span class="si">{</span><span class="n">numeral</span><span class="si">}</span><span class="s1"> to output'</span><span class="p">)</span>
</pre></div>
</div>
<p>With the debug <code class="docutils literal notranslate"><span class="pre">print()</span></code> statements, the output looks like this:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [3]: </span><span class="n">run</span> <span class="n">roman2</span><span class="o">.</span><span class="n">py</span>
<span class="gp">In [4]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">1424</span><span class="p">)</span>
<span class="go">subtracting 1000 from input, adding M to output</span>
<span class="go">subtracting 400 from input, adding CD to output</span>
<span class="go">subtracting 10 from input, adding X to output</span>
<span class="go">subtracting 10 from input, adding X to output</span>
<span class="go">subtracting 4 from input, adding IV to output</span>
<span class="gh">Out[4]: </span><span class="go">'MCDXXIV'</span>
</pre></div>
</div>
<p>So the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function appears to work, at least in this manual
spot check. But will it pass the test case you wrote?</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>In [7]: ! pytest roman2.py
========================= test session starts =========================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development
collected 1 item
roman2.py . [100%]
========================== 1 passed in 0.01s ==========================
</pre></div>
</div>
<p>Hooray! The <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function passes the “known values” test case. It’s not comprehensive, but it does put the function through
its paces with a variety of inputs, including inputs that produce
every single-character Roman numeral, the largest possible input
(<code class="docutils literal notranslate"><span class="pre">3999</span></code>), and the input that produces the longest possible Roman
numeral (<code class="docutils literal notranslate"><span class="pre">3888</span></code>). At this point, you can be reasonably confident
that the function works for any good input value you could throw at
it.</p>
<p>“Good” input? Hmm. What about bad input?</p>
</section>
</section>
<section id="halt-and-catch-fire">
<h3>“Halt And Catch Fire”<a class="headerlink" href="#halt-and-catch-fire" title="Link to this heading"></a></h3>
<p>The Pythonic way to halt and catch fire is to raise an exception.</p>
<p>It is not enough to test that functions succeed when given good input;
you must also test that they fail when given bad input. And not just any
sort of failure; they must fail in the way you expect.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [10]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">3000</span><span class="p">)</span>
<span class="gh">Out[10]: </span><span class="go">'MMM'</span>
<span class="gp">In [11]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">4000</span><span class="p">)</span>
<span class="gh">Out[11]: </span><span class="go">'MMMM'</span>
<span class="gp">In [12]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">5000</span><span class="p">)</span>
<span class="gh">Out[12]: </span><span class="go">'MMMMM'</span>
<span class="gp">In [13]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">9000</span><span class="p">)</span>
<span class="gh">Out[13]: </span><span class="go">'MMMMMMMMM'</span>
</pre></div>
</div>
<p>That’s definitely <em>not</em> what you wanted — that’s not even a valid Roman
numeral!
In fact, after 3000, each of these numbers is outside the range of
acceptable input, but the function returns a bogus value anyway.
Silently returning bad values is <em>baaaaaaad</em>; if a program is going
to fail, it is far better if it fails quickly and noisily. “Halt and
catch fire,” as the saying goes. In Python, the way to halt and catch
fire is to raise an exception.</p>
<p>The question to ask yourself is, “How can I express this as a testable
requirement?” How’s this for starters:</p>
<blockquote>
<div><p>The <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function should raise an <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> when
given an integer greater than <code class="docutils literal notranslate"><span class="pre">3999</span></code>.</p>
</div></blockquote>
<p>Why a ValueError? I think it’s a good idea to use one of the standard built-in exceptions is there is one that fits your use case. In this case, it is the <em>value</em> of the argument that is the problem – it is too large. So a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> is appropriate.</p>
<p>So how do we test for an exception? What would that test look like?</p>
<p><a class="reference download internal" download="" href="../_downloads/892493f25fa8e6cf5d0a42dec8d7ac1f/roman3.py"><code class="xref download docutils literal notranslate"><span class="pre">roman.py</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
<span class="k">def</span><span class="w"> </span><span class="nf">test_too_large</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""</span>
<span class="sd"> to_roman should raise an ValueError when passed</span>
<span class="sd"> values over 3999</span>
<span class="sd"> """</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">to_roman</span><span class="p">(</span><span class="mi">4000</span><span class="p">)</span>
</pre></div>
</div>
<p>Like the previous test case, the test itself is a function with a name starting with <code class="docutils literal notranslate"><span class="pre">test_</span></code>. pytest will know that it’s a test due to the name.</p>
<p>The test function has a docstring, letting us know what it is testing.</p>
<p>Now look at the body of that function; what the heck is that <code class="docutils literal notranslate"><span class="pre">with</span></code> statement? <code class="docutils literal notranslate"><span class="pre">with</span></code> is how we invoke a “context manager” – the code indented after the <code class="docutils literal notranslate"><span class="pre">with</span></code> is run in the “context” created, in this case, by the <code class="docutils literal notranslate"><span class="pre">pytest.raises</span></code> function. What <code class="docutils literal notranslate"><span class="pre">pytest.raises</span></code> does is check to make sure that the Exception specified is raised by the following code. So in this example, if <code class="docutils literal notranslate"><span class="pre">to_roman(4000)</span></code> raises an <code class="docutils literal notranslate"><span class="pre">ValueError</span></code>, the test will pass, and if it does not raise an Exception, or raises a different Exception, the test will fail.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Context managers are a powerful and sometimes complex feature
of Python. They will be covered later in detail, but for now, you only need to know that the code inside the with block runs in a special way controlled by what follows the <code class="docutils literal notranslate"><span class="pre">with</span></code> statement, including exception handling.
You will see <code class="docutils literal notranslate"><span class="pre">with</span></code> when working with files (<a class="reference internal" href="Files.html#files"><span class="std std-ref">File Reading and Writing</span></a>), and you can read more about it in: <a class="reference internal" href="ContextManagers.html#context-managers"><span class="std std-ref">Context Managers</span></a></p>
</div>
<p>CAUTION: you are now using a utility from the <code class="docutils literal notranslate"><span class="pre">pytest</span></code> package, so you need to make sure to import pytest first:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [18]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman3.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 2 items</span>
<span class="go">roman3.py .F [100%]</span>
<span class="go">============================== FAILURES ===============================</span>
<span class="go">___________________________ test_too_large ____________________________</span>
<span class="go"> def test_too_large():</span>
<span class="go"> """</span>
<span class="go"> to_roman should raise an ValueError when passed</span>
<span class="go"> values over 3999</span>
<span class="go"> """</span>
<span class="go"> with pytest.raises(ValueError):</span>
<span class="go">> to_roman(4000)</span>
<span class="go">E Failed: DID NOT RAISE <class 'ValueError'></span>
<span class="go">roman3.py:115: Failed</span>
<span class="go">======================= short test summary info =======================</span>
<span class="go">FAILED roman3.py::test_too_large - Failed: DID NOT RAISE <class 'Val...</span>
<span class="go">===================== 1 failed, 1 passed in 0.08s =====================</span>
</pre></div>
</div>
<p>You should have expected this to fail since you haven’t written any
code to pass it yet. Did it fail in the way you expected?</p>
<p>Yes! <code class="docutils literal notranslate"><span class="pre">pytest.raises</span></code> did its job – a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> was not raised, and the test failed.</p>
<p>Of course, the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function isn’t raising the <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> because you haven’t told it to do that yet.
That’s excellent news! It means this is a valid test case — it fails before you write the code to make it pass.</p>
<p>Now you can write the code to make this test pass.</p>
<p><a class="reference download internal" download="" href="../_downloads/3e29de91d4027f47dcec7e9c4da617dc/roman4.py"><code class="xref download docutils literal notranslate"><span class="pre">roman4.py</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">'''convert integer to Roman numeral'''</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">></span> <span class="mi">3999</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"number out of range (must be less than 4000)"</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="s1">''</span>
<span class="k">for</span> <span class="n">numeral</span><span class="p">,</span> <span class="n">integer</span> <span class="ow">in</span> <span class="n">roman_numeral_map</span><span class="p">:</span>
<span class="k">while</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">integer</span><span class="p">:</span>
<span class="n">result</span> <span class="o">+=</span> <span class="n">numeral</span>
<span class="n">n</span> <span class="o">-=</span> <span class="n">integer</span>
<span class="k">return</span> <span class="n">result</span>
</pre></div>
</div>
<p>This is straightforward: if the given input (<code class="docutils literal notranslate"><span class="pre">n</span></code>) is greater than
<code class="docutils literal notranslate"><span class="pre">3999</span></code>, raise a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> exception.
The unit test does not check the human-readable string that accompanies the exception,
although you could write another test that did check it if you wanted to be sure
(but watch out for internationalization issues for strings that vary by the user’s language or environment).</p>
<p>Does this make the test pass? Let’s find out.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [19]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman4.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 2 items</span>
<span class="go">roman4.py .. [100%]</span>
<span class="go">========================== 2 passed in 0.01s ==========================</span>
</pre></div>
</div>
<p>Hooray! Both tests pass. Because you worked iteratively, bouncing
back and forth between testing and coding, you can be sure that the
two lines of code you just wrote were the cause of that one test
going from “fail” to “pass.” That kind of confidence doesn’t come
cheap, but it will pay for itself over the lifetime of your code.</p>
</section>
<section id="more-halting-more-fire">
<h3>More Halting, More Fire<a class="headerlink" href="#more-halting-more-fire" title="Link to this heading"></a></h3>
<p>Along with testing numbers that are too large, you need to test numbers
that are too small.
As we noted in our functional requirements, Roman numerals cannot express zero or negative numbers.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [20]: </span><span class="n">run</span> <span class="n">roman4</span><span class="o">.</span><span class="n">py</span>
<span class="gp">In [21]: </span><span class="n">to_roman</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="gh">Out[21]: </span><span class="go">''</span>
<span class="gp">In [22]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="gh">Out[22]: </span><span class="go">''</span>
</pre></div>
</div>
<p>Well <em>that’s</em> not good – it happily accepted the input and returned an empty string. Now let’s add tests for each of these conditions, to make sure they raise an exception instead of silently giving an non-answer.</p>
<p><a class="reference download internal" download="" href="../_downloads/5859f3c42ca7d8674b8b2a98d2c7fc14/roman5.py"><code class="xref download docutils literal notranslate"><span class="pre">roman5.py</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_zero</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""to_roman should raise an ValueError with 0 input"""</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">to_roman</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">test_negative</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""to_roman should raise an ValueError with negative input"""</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">to_roman</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</pre></div>
</div>
<p>The first new test is the <code class="docutils literal notranslate"><span class="pre">test_zero()</span></code> function. Like the
<code class="docutils literal notranslate"><span class="pre">test_too_large()</span></code> function, it it uses the <code class="docutils literal notranslate"><span class="pre">pytest.raises</span></code> context manager to call our <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function with a parameter of 0, and check that it raises the appropriate exception: <code class="docutils literal notranslate"><span class="pre">ValueError</span></code>.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">test_negative()</span></code> function is almost identical, except it passes
<code class="docutils literal notranslate"><span class="pre">-1</span></code> to the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function. If either of these new tests
does <em>not</em> raise an <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> (either because the function
returns an actual value, or because it raises some other exception),
the test is considered failed.</p>
<p>Now check that the tests fail:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [24]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman5.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 4 items</span>
<span class="go">roman5.py ..FF [100%]</span>
<span class="go">============================== FAILURES ===============================</span>
<span class="go">______________________________ test_zero ______________________________</span>
<span class="go"> def test_zero():</span>
<span class="go"> """to_roman should raise an ValueError with 0 input"""</span>
<span class="go"> with pytest.raises(ValueError):</span>
<span class="go">> to_roman(0)</span>
<span class="go">E Failed: DID NOT RAISE <class 'ValueError'></span>
<span class="go">roman5.py:123: Failed</span>
<span class="go">____________________________ test_negative ____________________________</span>
<span class="go"> def test_negative():</span>
<span class="go"> """to_roman should raise an ValueError with negative input"""</span>
<span class="go"> with pytest.raises(ValueError):</span>
<span class="go">> to_roman(-1)</span>
<span class="go">E Failed: DID NOT RAISE <class 'ValueError'></span>
<span class="go">roman5.py:129: Failed</span>
<span class="go">======================= short test summary info =======================</span>
<span class="go">FAILED roman5.py::test_zero - Failed: DID NOT RAISE <class 'ValueErr...</span>
<span class="go">FAILED roman5.py::test_negative - Failed: DID NOT RAISE <class 'Valu...</span>
<span class="go">===================== 2 failed, 2 passed in 0.09s =====================</span>
</pre></div>
</div>
<p>Excellent. Both tests failed, as expected. Now let’s switch over to the
code and see what we can do to make them pass.</p>
<p><a class="reference download internal" download="" href="../_downloads/d569f91fb5d6e6a0fd37c835012c0804/roman6.py"><code class="xref download docutils literal notranslate"><span class="pre">roman6.py</span></code></a>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""convert integer to Roman numeral"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="mi">0</span> <span class="o"><</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">4000</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"number out of range (must be 1..3999)"</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="s1">''</span>
<span class="k">for</span> <span class="n">numeral</span><span class="p">,</span> <span class="n">integer</span> <span class="ow">in</span> <span class="n">roman_numeral_map</span><span class="p">:</span>
<span class="k">while</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">integer</span><span class="p">:</span>
<span class="n">result</span> <span class="o">+=</span> <span class="n">numeral</span>
<span class="n">n</span> <span class="o">-=</span> <span class="n">integer</span>
<span class="k">return</span> <span class="n">result</span>
</pre></div>
</div>
<p>Note the <code class="docutils literal notranslate"><span class="pre">not</span> <span class="pre">(0</span> <span class="pre"><</span> <span class="pre">n</span> <span class="pre"><</span> <span class="pre">4000)</span></code> This is a nice Pythonic shortcut: multiple comparisons at once.
This is equivalent to <code class="docutils literal notranslate"><span class="pre">not</span> <span class="pre">((0</span> <span class="pre"><</span> <span class="pre">n)</span> <span class="pre">and</span> <span class="pre">(n</span> <span class="pre"><</span> <span class="pre">4000))</span></code>, but it’s much
easier to read. This one line of code should catch inputs that are
too large, negative, or zero.</p>
<p>If you change your conditions, make sure to update your
human-readable error strings to match. pytest won’t care,
but it’ll make it difficult to do manual debugging if
your code is throwing incorrectly-described exceptions.</p>
<p>I could show you a whole series of unrelated examples to show that the
multiple-comparisons-at-once shortcut works, but instead I’ll just run
the unit tests and prove it.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [26]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman6.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 4 items</span>
<span class="go">roman6.py .... [100%]</span>
<span class="go">========================== 4 passed in 0.01s ==========================</span>
</pre></div>
</div>
<p>Excellent! The tests all pass – your code is working! Remember that you still have the “too large” test – and all the tests of converting numbers. So you know you haven’t inadvertently broken anything else.</p>
</section>
<section id="and-one-more-thing">
<h3>And One More Thing …<a class="headerlink" href="#and-one-more-thing" title="Link to this heading"></a></h3>
<p>There was one more functional requirement for converting numbers to Roman numerals: dealing with non-integers.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [30]: </span><span class="n">run</span> <span class="n">roman6</span><span class="o">.</span><span class="n">py</span>
<span class="gp">In [31]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="gh">Out[31]: </span><span class="go">''</span>
</pre></div>
</div>
<p>Oh, that’s bad.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [32]: </span><span class="n">to_roman</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span>
<span class="gh">Out[32]: </span><span class="go">'I'</span>
</pre></div>
</div>
<p>What about that? technically, 1.0 is a float type, not an integer. But it does have an integer value, and Python considers them equal:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [35]: </span><span class="mi">1</span> <span class="o">==</span> <span class="mf">1.0</span>
<span class="gh">Out[35]: </span><span class="go">True</span>
</pre></div>
</div>
<p>So I’d say that we want 1.0 to be convertible, but not 0.5 (or 1.00000001 for that matter)</p>
<p>Testing for non-integers is not difficult. Simply write a test case that checks that a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> is raised if you pass in a non-integer value.</p>
<p><a class="reference download internal" download="" href="../_downloads/ba31ee4e6e338fd38619c3cec6019f2d/roman7.py"><code class="xref download docutils literal notranslate"><span class="pre">roman7.py</span></code></a>.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_non_integer</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""to_roman should raise an ValueError with non-integer input"""</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">to_roman</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
</pre></div>
</div>
<p>And while we are at it, test a float type that happens to be an integer.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_float_with_integer_value</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""to_roman should work for floats with integer values"""</span>
<span class="k">assert</span> <span class="n">to_roman</span><span class="p">(</span><span class="mf">3.0</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"III"</span>
</pre></div>
</div>
<p>Why a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> rather than a <code class="docutils literal notranslate"><span class="pre">TypeError</span></code>? because it’s the value that matters, not the type. It’s OK to pass in a float type, as long as the value is an integer.</p>
<p>Now check that the test fails properly.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [36]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman7.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 6 items</span>
<span class="go">roman7.py ....F. [100%]</span>
<span class="go">============================== FAILURES ===============================</span>
<span class="go">__________________________ test_non_integer ___________________________</span>
<span class="go"> def test_non_integer():</span>
<span class="go"> """to_roman should raise an ValueError with non-integer input"""</span>
<span class="go"> with pytest.raises(ValueError):</span>
<span class="go">> to_roman(0.5)</span>
<span class="go">E Failed: DID NOT RAISE <class 'ValueError'></span>
<span class="go">roman7.py:135: Failed</span>
<span class="go">======================= short test summary info =======================</span>
<span class="go">FAILED roman7.py::test_non_integer - Failed: DID NOT RAISE <class 'V...</span>
<span class="go">===================== 1 failed, 5 passed in 0.10s =====================</span>
</pre></div>
</div>
<p>Yup – it failed.</p>
<div class="admonition hint">
<p class="admonition-title">Hint</p>
<p>when you add a new test, and see that it fails, also check that there are <em>more</em> tests than there were before. In this case, 1 failed, and 5 passed. In the previous run, 4 passed – so you know there are, in fact, two additional tests, one of which passed. Why might there not be? because we all like to copy-and-paste, and then edit. If you forget to rename the test function, it will overwrite the previous one – and we want all our tests to be preserved.</p>
</div>
<p>So now write the code that makes the test pass.</p>
<p><a class="reference download internal" download="" href="../_downloads/81cae2301fa776de9a4af1ad0afc2e86/roman8.py"><code class="xref download docutils literal notranslate"><span class="pre">roman8.py</span></code></a>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""convert integer to Roman numeral"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="mi">0</span> <span class="o"><</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">4000</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"number out of range (must be 1..3999)"</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">int</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">!=</span> <span class="n">n</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"Only integers can be converted to Roman numerals"</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="s1">''</span>
<span class="k">for</span> <span class="n">numeral</span><span class="p">,</span> <span class="n">integer</span> <span class="ow">in</span> <span class="n">roman_numeral_map</span><span class="p">:</span>
<span class="k">while</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">integer</span><span class="p">:</span>
<span class="n">result</span> <span class="o">+=</span> <span class="n">numeral</span>
<span class="n">n</span> <span class="o">-=</span> <span class="n">integer</span>
<span class="k">return</span> <span class="n">result</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">int(n)</span> <span class="pre">!=</span> <span class="pre">n</span></code> is checking that when you convert the value to an integer, it doesn’t change. We need to do that, because simply checking if you can convert to an integer isn’t enough – when a float is converted to an integer, the fractional part is truncated:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [37]: </span><span class="nb">int</span><span class="p">(</span><span class="mf">1.00001</span><span class="p">)</span>
<span class="gh">Out[37]: </span><span class="go">1</span>
</pre></div>
</div>
<p>If the result of converting to an integer is equal to the original, then it had an integral value. Note that this will work with all the built numerical types:</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [42]: </span><span class="nb">int</span><span class="p">(</span><span class="n">Decimal</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span> <span class="o">==</span> <span class="mi">3</span>
<span class="gh">Out[42]: </span><span class="go">True</span>
<span class="gp">In [43]: </span><span class="nb">int</span><span class="p">(</span><span class="n">Decimal</span><span class="p">(</span><span class="mf">3.5</span><span class="p">))</span> <span class="o">==</span> <span class="mf">3.5</span>
<span class="gh">Out[43]: </span><span class="go">False</span>
</pre></div>
</div>
<p>Finally, check that the code does indeed make the test pass.</p>
<div class="highlight-ipython notranslate"><div class="highlight"><pre><span></span><span class="gp">In [44]: </span><span class="o">!</span><span class="w"> </span>pytest<span class="w"> </span>roman8.py
<span class="go">========================= test session starts =========================</span>
<span class="go">platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1</span>
<span class="go">rootdir: /Users/chris.barker/Personal/UWPCE/Python210CourseMaterials/source/examples/test_driven_development</span>
<span class="go">collected 6 items</span>
<span class="go">roman8.py ...... [100%]</span>
<span class="go">========================== 6 passed in 0.02s ==========================</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function passes all of its tests, and I can’t think
of any more tests, so it’s time to move on to <code class="docutils literal notranslate"><span class="pre">from_roman()</span></code>.</p>
</section>
<section id="a-pleasing-symmetry">
<h3>A Pleasing Symmetry<a class="headerlink" href="#a-pleasing-symmetry" title="Link to this heading"></a></h3>
<p>Converting a string from a Roman numeral to an integer sounds more
difficult than converting an integer to a Roman numeral. Certainly there
is the issue of validation. It’s easy to check if an integer is greater
than 0, but a bit harder to check whether a string is a valid Roman
numeral. But we can at least make sure that correct Roman numerals convert correctly.</p>
<p>So we have the problem of converting the string itself. As we’ll see in
a minute, thanks to the rich data structure we defined to map individual
Roman numerals to integer values, the nitty-gritty of the
<code class="docutils literal notranslate"><span class="pre">from_roman()</span></code> function is as straightforward as the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code>
function.</p>
<p>But first, the tests. We’ll need a “known values” test to spot-check for
accuracy. Our test suite already contains a mapping of known
values: let’s reuse that.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_from_roman_known_values</span><span class="p">():</span>
<span class="w"> </span><span class="sd">"""from_roman should give known result with known input"""</span>
<span class="k">for</span> <span class="n">integer</span><span class="p">,</span> <span class="n">numeral</span> <span class="ow">in</span> <span class="n">KNOWN_VALUES</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">from_roman</span><span class="p">(</span><span class="n">numeral</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">integer</span> <span class="o">==</span> <span class="n">result</span>
</pre></div>
</div>
<p>There’s a pleasing symmetry here. The <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> and
<code class="docutils literal notranslate"><span class="pre">from_roman()</span></code> functions are inverses of each other. The first
converts integers to specially-formatted strings, the second converts
specially-formated strings to integers. In theory, we should be able to
“round-trip” a number by passing to the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function to get a
string, then passing that string to the <code class="docutils literal notranslate"><span class="pre">from_roman()</span></code> function to get
an integer, and end up with the same number.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">n</span> <span class="o">=</span> <span class="n">from_roman</span><span class="p">(</span><span class="n">to_roman</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="k">for</span> <span class="nb">all</span> <span class="n">values</span> <span class="n">of</span> <span class="n">n</span>
</pre></div>
</div>
<p>In this case, “all values” means any number between <code class="docutils literal notranslate"><span class="pre">1..3999</span></code>, since
that is the valid range of inputs to the <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code> function. We can
express this symmetry in a test case that runs through all the values
<code class="docutils literal notranslate"><span class="pre">1..3999</span></code>, calls <code class="docutils literal notranslate"><span class="pre">to_roman()</span></code>, calls <code class="docutils literal notranslate"><span class="pre">from_roman()</span></code>, and checks
that the output is the same as the original input.</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_roundtrip</span><span class="p">():</span>
<span class="w"> </span><span class="sd">'''from_roman(to_roman(n))==n for all n'''</span>
<span class="k">for</span> <span class="n">integer</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4000</span><span class="p">):</span>
<span class="n">numeral</span> <span class="o">=</span> <span class="n">to_roman</span><span class="p">(</span><span class="n">integer</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">from_roman</span><span class="p">(</span><span class="n">numeral</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">integer</span> <span class="o">==</span> <span class="n">result</span>
</pre></div>
</div>