-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcap9.tex
1082 lines (813 loc) · 57.5 KB
/
cap9.tex
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
% ch9.tex
% This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 License.
% To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/nz
% or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
\chapter{Pruebas unitarias}\label{ch:pruebas_unitarias}
\noindent
Nivel de dificultad:\difll
\begin{citaCap}
``La certidumbre no es prueba de que algo sea cierto. \\
Hemos estado tan seguros de tantas cosas que luego no lo eran.'' \\
---\href{http://en.wikiquote.org/wiki/Oliver\_Wendell\_Holmes,\_Jr.}{Oliver Wendell Holmes, Jr}
\end{citaCap}
\section{(Sin) Inmersión}
En este capítulo vas a escribir y depurar un conjunto de funciones de utilidad para convertir números romanos (en ambos sentidos). En el caso de estudio de los números romanos ya vimos cual es la mecánica para construir y validar números romanos. Ahora vamos a volver a él para considerar lo que nos llevaría expandirlo para que funcione como una utilidad en ambos sentidos.
Las reglas para los números romanos sugieren una serie de interesantes observaciones:
\begin{itemize}
\item Solamente existe una forma correcta de representar un número cualquiera con los dígitos romanos.
\item Lo contrario también es verdad: si una cadena de caracteres es un número romano válido, representa a un único número (solamente se puede interpretar de una forma).
\item Existe un rango limitado de valores que se puedan expresar como números romanos, en concreto del \codigo{1} al \codigo{3999}. Los romanos tenían varias maneras de expresar números mayores, por ejemplo poniendo una barra sobre un número para indicar que el valor normal que representaba tenía que multiplicarse por \codigo{1000}. Para los propósitos que perseguimos en este capítulo, vamos a suponer que los números romanos van del \codigo{1} al \codigo{3999}.
\item No existe ninguna forma de representar el \codigo{0} en números romanos.
\item No hay forma de representar números negativos en números romanos.
\item No hay forma de representar fracciones o números no enteros con números romanos.
\end{itemize}
Vamos a comenzar explicando lo que el módulo \codigo{roman.py} debería hacer. Tendrá que tener dos funciones principales \codigo{to\_roman()} y \codigo{from\_romman()}. La función \codigo{to\_roman()} debería tomar como parámetro un número entero de \codigo{1} a \codigo{3999} y devolver su representación en números Romanos como una cadena...
Paremos aquí. Vamos a comenzar haciendo algo un poco inesperado: escribir un caso de prueba que valide si la función \codigo{to\_roman()} hace lo que quieres que haga. Lo has leído bien: vas a escribir código que valide la función que aún no has escrito.
A esto se le llama \emph{desarrollo dirigido por las pruebas ---test driven development, o TDD---}. El conjunto formado por las dos funciones de conversión ---\codigo{to\_roman()}, y \codigo{from\_roman()}--- se puede escribir y probar como una unidad, separadamente de cualquier programa mayor que lo utilice. Python dispone de un marco de trabajo (framework) para pruebas unitarias, el módulo se llama, apropiadamente, \codigo{unittest}.
La prueba unitaria es una parte importante de una estrategia de desarrollo centrada en las pruebas. Si escribes pruebas unitarias es importante escribirlas pronto y mantenerlas actualizadas con el resto del código y con los cambios de requisitos. Muchas personas dicen que las pruebas se escriban antes que el código que se vaya a probar, y este es el estilo que voy a enseñarte en este capítulo. De todos modos, las pruebas unitarias son útiles independientemente de cuando las escribas.
\begin{itemize}
\item Antes de escribir el código, el escribir las pruebas unitarias te obliga a detallar los requisitos de forma útil.
\item Durante la escritura del código, las pruebas unitarias evitan que escribas demasiad código. Cuando pasan los todos los casos de prueba, la función está completa.
\item Cuando se está reestructurando el código\footnote{refactoring}, pueden ayudar a probar que la nueva versión se comporta de igual manera que la vieja.
\item Cuando se mantiene el código, disponer de pruebas te ayuda a cubrirte el trasero cuando alguien viene gritando que tu último cambio en el código ha roto el antiguo que ya funcionaba.
\item Cuando codificas en un equipo, disponer de un juego de pruebas completo disminuye en gran medida la probabilidad de que tu código rompa el de otra persona, ya que puedes ejecutar los casos de prueba antes de introducir cambios (He visto esto pasar en las competiciones de código. Un equipo trocea el trabajo asignado, todo el mundo toma las especificaciones de su tarea, escribe los casos de prueba para ella en primer lugar, luego comparte sus casos de prueba con el resto del equipo. De ese modo, nadie puede perderse demasiado desarrollando código que no funcione bien con el del resto).
\end{itemize}
\section{Una única pregunta}
Un caso de prueba (unitaria) contesta a una única pregunta sobre el código que está probando. Un caso de prueba (unitaria) debería ser capaz de...
\cajaTexto{Toda prueba es una isla.}
\begin{itemize}
\item ...ejecutarse completamente por sí mismo, sin necesidad de ninguna entrada manual. Las pruebas unitarias deben ser automáticas.
\item ...determinar por sí misma si la función que se está probando ha funcionado correctamente o a fallado, sin la necesidad de que exista una persona que interprete los resultados.
\item ...ejecutarse de forma aislada, separada de cualquier otro caso de prueba (incluso aunque estos otros prueben las mismas funciones). Cada caso de prueba es una isla.
\end{itemize}
Dado esto, construyamos un caso de prueba para el primer requisito:
\begin{itemize}
\item La función \codigo{to\_roman()} debería devolver el número Romano que represente a los números enteros del \codigo{1} al \codigo{3999}.
\end{itemize}
Lo que no es obvio es cómo el código siguiente efectúa dicho cálculo. Define una clase que no tiene método \codigo{\_\_init\_\_()}. La clase \emph{tiene} otro método, pero nunca se llama. El código tiene un bloque \codigo{\_\_main\_\_}, pero este bloque ni referencia a la clase ni a su método. Pero hace algo, te lo juro.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# romantest1.py
import roman1
import unittest
class KnownValues(unittest.TestCase):
known_values = ( (1, 'I'), (2, 'II'),
(3, 'III'), (4, 'IV'),
(5, 'V'), (6, 'VI'),
(7, 'VII'), (8, 'VIII'),
(9, 'IX'), (10, 'X'),
(50, 'L'), (100, 'C'),
(500, 'D'), (1000, 'M'),
(31, 'XXXI'), (148, 'CXLVIII'),
(294, 'CCXCIV'), (312, 'CCCXII'),
(421, 'CDXXI'), (528, 'DXXVIII'),
(621, 'DCXXI'), (782, 'DCCLXXXII'),
(870, 'DCCCLXX'), (941, 'CMXLI'),
(1043, 'MXLIII'), (1110, 'MCX'),
(1226, 'MCCXXVI'), (1301, 'MCCCI'),
(1485, 'MCDLXXXV'), (1509, 'MDIX'),
(1607, 'MDCVII'), (1754, 'MDCCLIV'),
(1832, 'MDCCCXXXII'), (1993, 'MCMXCIII'),
(2074, 'MMLXXIV'), (2152, 'MMCLII'),
(2212, 'MMCCXII'), (2343, 'MMCCCXLIII'),
(2499, 'MMCDXCIX'), (2574, 'MMDLXXIV'),
(2646, 'MMDCXLVI'), (2723, 'MMDCCXXIII'),
(2892, 'MMDCCCXCII'), (2975, 'MMCMLXXV'),
(3051, 'MMMLI'), (3185, 'MMMCLXXXV'),
(3250, 'MMMCCL'), (3313, 'MMMCCCXIII'),
(3408, 'MMMCDVIII'), (3501, 'MMMDI'),
(3610, 'MMMDCX'), (3743, 'MMMDCCXLIII'),
(3844, 'MMMDCCCXLIV'), (3888, 'MMMDCCCLXXXVIII'),
(3940, 'MMMCMXL'), (3999, 'MMMCMXCIX'))
def test_to_roman_known_values(self):
'''to_roman should give known result with known input'''
for integer, numeral in self.known_values:
result = roman1.to_roman(integer)
self.assertEqual(numeral, result)
if __name__ == '__main__':
unittest.main()
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} Para escribir un caso de prueba, lo primero es crear una subclase de \codigo{TestCase} del módulo \codigo{unittest}. Esta clase proporciona muchos métodos útiles que se pueden utilizar en los casos de prueba para comprobar condiciones específicas.
\item \emph{Línea 33:} Esto es una lista de pares de números enteros y sus números romanos equivalentes que he verificado manualmente. Incluye a los diez primeros números, el mayor, todos los números que se convierten un único carácter romano, y una muestra aleatoria de otros números válidos. No es necesario probar todas las entradas posibles, pero deberían probarse los casos de prueba límite.
\item \emph{Línea 35:} Cada caso de prueba debe escribirse en su propio método, que no debe tomar parámetros y no debe devolver ningún valor. Si el método finaliza normalmente sin elevar ninguna excepción, se considera que la prueba ha pasado; si el método eleva una excepción, se considera que la prueba ha fallado.
\item \emph{Línea 38:} Este es el punto en el que se llama a la función \codigo{to\_roman()} (bueno, la función aún no se ha escrito, pero cuando esté escrita, esta será la línea que la llamará). Observa que con esto has definido la \codigo{API} de la función \codigo{to\_roman()}: debe tomar como parámetro un número entero (el número a convertir) y devolver una cadena (la representación del número entero en números romanos). Si la \codigo{API} fuese diferente a esta, este test fallará. Observa también que no estamos capturando excepciones cuando llamamos a la función \codigo{to\_roman()}. Es intencionado, \codigo{to\_roman()} no debería devolver una excepción cuando la llames con una entrada válida, y todas las entradas previstas en este caso de prueba son válidas. Si \codigo{to\_roman()} elevase una excepción, esta prueba debería considerarse fallida.
\item \emph{Línea 38:} Asumiendo que la función \codigo{to\_roman()} fuese definida correctamente, llamada correctamente, ejecutada correctamente y retornase un valor, el último paso es validar si el valor retornado es el \emph{correcto}. Esta es una pregunta habitual, y la clase \codigo{TestCase} proporciona un método, \codigo{assertEqual}, para validar si dos valores son iguales. Si el resultado que retorna \codigo{to\_roman() (result)} no coincide con el valor que se estaba esperando (\codigo{numeral}), \codigo{assertEqual} elevará una excepción y la prueba fallará. Si los dos valores son iguales, \codigo{assertEqual} no hará nada. Si todos los valores que retorna \codigo{to\_roman()} coinciden con los valores esperados, la función \codigo{assertEqual} nunca eleva una excepción, por lo que la función \codigo{test\_to\_roman\_known\_values} termina normalmente, lo que significa que la función \codigo{to\_roman()} ha pasado esta prueba.
\end{enumerate}
\cajaTexto{Escribe un caso de prueba que falle, luego codifica hasta que funcione.}
Cuando ya tienes un caso de prueba, puedes comenzar a codificar la función \codigo{to\_roman()}. Primero deberías crearla como una función vacía y asegurarte de que la prueba falla. Si la prueba funciona antes de que hayas escrito ningún código, ¡¡tus pruebas no están probando nada!! La prueba unitaria es como un baile: la prueba va llevando, el código la va siguiendo. Escribe una prueba que falle, luego codifica hasta que el código pase la prueba.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# roman1.py
def to_roman(n):
'''convert integer to Roman numeral'''
pass
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} En este momento debes definir la \codigo{API} de la función \codigo{to\_roman()}, pero no quieres codificarla aún (Las pruebas deben fallar primero). Para conseguirlo, utiliza la palabra reservada de Python \codigo{pass} que, precisamente, sirve para no hacer nada.
\end{enumerate}
Ejecuta \codigo{romantest1.py} en la línea de comando para ejecutar la prueba. Si lo llamas con la opción \codigo{-v} de la línea de comando, te mostrará una salida con más información de forma que puedas ver lo que está sucediendo conforme se ejecutan los casos de prueba. Con suerte, la salida deberá parecerse a esto:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ python3 romantest1.py -v
test_to_roman_known_values (__main__.KnownValues)
to_roman should give known result with known input ... FAIL
======================================================================
FAIL: to_roman should give known result with known input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest1.py", line 73, in test_to_roman_known_values
self.assertEqual(numeral, result)
AssertionError: 'I' != None
----------------------------------------------------------------------
Ran 1 test in 0.016s
FAILED (failures=1)
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 2:} Al ejecutar el programa se ejecuta \codigo{unittest.main()}, que ejecuta el caso de prueba. Cada caso de prueba es un método de una clase en \codigo{romantest.py}. No se requiere ninguna organización de estas clases de prueba; pueden contener un método de prueba cada una o puede existir una única clase con muchos métodos de prueba. El único requisito es que cada clase de prueba debe heredar de \codigo{unittest.TestCase}.
\item \emph{Línea 3:} Para cada caso de prueba, el módulo \codigo{unittest} imprimirá el \codigo{docstring} (cadena de documentación) del méotodo y si la prueba ha pasado o ha fallado. En este caso, como se esperaba, la prueba ha fallado.
\item \emph{Línea 11:} Para cada caso de prueba que falle, \codigo{unittest} muestra la información de traza que muestra exactamente lo que ha sucedido. En este caso la llamada a \codigo{assertEqual()} elevó la excepción \codigo{AssertionError} porque estaba esperando que \codigo{to\_roman(1)} devolviese \codigo{'I'}, y no o ha devuelto (Debido a que no hay valor de retorno explícito, la función devolvió \codigo{None}, el valor nulo de Python).
\item \emph{Línea 14:} Después del detalle de cada prueba, \codigo{unittest} muestra un resumen de los test que se han ejecutado y el tiempo que se ha tardado.
\item \emph{Línea 16:} En conjunto, la ejecución de la prueba ha fallado puesto que al menos un caso de prueba lo ha hecho. Cuando un caso de prueba no pasa, \codigo{unittest} distingue entre fallos y errores. Un fallo se produce cuando se llama a un método \codigo{assertXYZ}, como \codigo{assertEqual} o \codigo{assertRaises}, que fallan porque la condición que se evalúa no sea verdadera o la excepción que se esperaba no ocurrió. Un error es cualquier otra clase de excepción en el código que estás probando o en el propio caso de prueba.
\end{enumerate}
Ahora, finalmente, puedes escribir la función \codigo{to\_roman()}.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
roman_numeral_map = (('M', 1000),
('CM', 900),
('D', 500),
('CD', 400),
('C', 100),
('XC', 90),
('L', 50),
('XL', 40),
('X', 10),
('IX', 9),
('V', 5),
('IV', 4),
('I', 1))
def to_roman(n):
'''convert integer to Roman numeral'''
result = ''
for numeral, integer in roman_numeral_map:
while n >= integer:
result += numeral
n -= integer
return result
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 13:} La variable \codigo{roman\_numeral\_map} es una tupla de tuplas que define tres cosas: las representaciones de caracteres de los números romanos básicos; el orden de los números romanos (en orden descendente, desde \codigo{M} a \codigo{I}), y el valor de cada número romano. Cada tupla interna es una pareja \codigo{(número romano, valor)}. No solamente define los números romanos de un único carácter, también define las parejas de dos caracteres como \codigo{CM} ("cien menos que mil"). Así el código de la función \codigo{to\_roman} se hace más simple.
\item \emph{Línea 19:} Es aquí en donde la estructura de datos \codigo{roman\_numeral\_map} se muestra muy útil, porque no necesitas ninguna lógica especial para controlar la regla de restado. Para convertir números romanos solamente tienes que iterar a través de la tupla \codigo{roman\_numeral\_map} a la búsqueda del mayor valor entero que sea menor o igual al valor introducido. Una vez encontrado, se concatena su representación en número romano al final de la salida, se resta el valor correspondiente del valor inicial y se repite hasta que se consuman todos los elementos de la tupla.
\end{enumerate}
Si aún no ves claro cómo funciona la función \codigo{to\_roman()} añade una llamada a \codigo{print()} al final del bucle \codigo{while}:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
...
while n >= integer:
result += numeral
n -= integer
print('restando {0} de la entrada, sumando {1} a la salida'.format(
integer, numeral)
)
...
\end{lstlisting}
\end{minipage}
Con estas sentencias, la salida es la siguiente:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> import roman1
>>> roman1.to_roman(1424)
restando 1000 de la entrada, sumando M a la salida
restando 400 de la entrada, sumando CD a la salida
restando 10 de la entrada, sumando X a la salida
restando 10 de la entrada, sumando X a la salida
restando 4 de la entrada, sumando IV a la salida
'MCDXXIV'
\end{lstlisting}
\end{minipage}
Por lo que parece que funciona bien la función \codigo{to\_roman()}, al menos en esta prueba manual. Pero, ¿Pasará el caso de prueba que escribimos antes?
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ python3 romantest1.py -v
test_to_roman_known_values (__main__.KnownValues)
to_roman should give known result with known input ... ok
----------------------------------------------------------------------
Ran 1 test in 0.016s
OK
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 3:} ¡Bien! La función \codigo{to\_roman()} pasa los valores conocidos del caso de prueba. No es exhaustivo, pero revisa la función con una amplia variedad de entradas. Incluyendo entradas cuyo resultado son todos los números romanos de un único carácter, el valor mayor (\codigo{3999}) y la entrada que produce el número romano más largo posible (\codigo{3888}). En este punto puedes confiar razonablemente en que la función está bien hecha y funciona para cualquier valor de entrada válido que puedas consultar.
\end{enumerate}
¿Valores "válidos"? ¿Qué es lo que pasará con valores de entrada no válidos?
\section{``Para y préndele fuego''}
\cajaTexto{La forma de parar la ejecución para indicar un fallo es elevar una excepción}
No es suficiente con probar que las funciones pasan las pruebas cuando éstas incluyen valores correctos; tienes que probar que las funciones fallan cuando se les pasa una entrada no válida. Y que el fallo no es uno cualquiera; deben fallar de la forma que esperas.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> import roman1
>>> roman1.to_roman(4000)
'MMMM'
>>> roman1.to_roman(5000)
'MMMMM'
>>> roman1.to_roman(9000)
'MMMMMMMMM'
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 6:} Esto no es lo que querías ---ni siquiera es un número romano válido. De hecho, todos estos números están fuera del rango de los que son aceptables como entrada a la función, pero aún así, la función devuelve valores falsos. Devolver valores ``malos'' sin advertencia alguna es algo \emph{maaaalo}. Si un programa debe fallar, lo mejor es que lo haga lo más cerca del error, rápida y evidentemente. Mejor ``parar y prenderle fuego'' como dice el dicho. La forma de hacerlo en Python es elevar una excepción.
\end{enumerate}
La pregunta que te tienes que hacer es, \emph{¿Cómo puedo expresar esto como un requisito que se pueda probar?} ¿Qué tal esto para comenzar?:
\begin{quote}
La función \codigo{to\_roman()} debería elevar una excepción \codigo{OutOfRangeError} cuando se le pase un número entero mayor que \codigo{3999}.
\end{quote}
¿Cómo sería esta prueba?
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class ToRomanBadInput(unittest.TestCase):
def test_too_large(self):
'''to_roman deber$\ac{i}$a fallar con una entrada muy grande'''
self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 1:} Como en el caso de prueba anterior, has creado una clase que hereda de \codigo{unittest.TestCase}. Puedes crear más de una prueba por cada clase (como verás más adelante en este mismo capítulo), pero aquí he elegido crear una clase nueva porque esta prueba es algo diferente a la anterior. En este ejemplo, mantendremos todas las pruebas sobre entradas válidas en una misma clase y todas las pruebas que validen entradas no válidas en otra clase.
\item \emph{Línea 2:} Como en el ejemplo anterior, la prueba es un método de la clase que tenga un nombre que comience por \codigo{test}.
\item \emph{Línea 4:} La clase \codigo{unittest.TestCase} proporciona el método \codigo{assertRaises}, que toma los siguientes parámetros: la excepción que se debe esperar, la función que se está probando y los parámetros que hay que pasarle a la función (Si la función que estás probando toma más de un parámetro hay que pasarlos todos \codigo{assertRaises} en el orden que los espera la función, para que \codigo{assertRaises} los pueda pasar, a su vez, a la función a probar.
\end{enumerate}
Presta atención a esta última línea de código. En lugar de llamar directamente a la función \codigo{to\_roman()} y validar a mano si eleva una excepción concreta (mediante un bloque \codigo{try ... except}), el método \codigo{assertRaises} se encarga de ello por nosotros. Todo lo que hay que hacer es decirle la excepción que estamos esperado (\codigo{roman.OutOfRangeError}, la función (\codigo{to\_roman()}) y los parámetros de la función (\codigo{4000}). El método \codigo{assertRaises} se encarga de llamar a la función \codigo{to\_roman()} y valida que eleve \codigo{roman2.OutRangeError}.
Observa también que estás pasando la propia función \codigo{to\_roman()} como un parámetro; no estás ejecutándola y no estás pasando el nombre de la función como una cadena. ¿He mencionado ya lo oportuno que es que todo en Python sea un objeto?
¿Qué sucede cuando ejecutas esta ``suite'' de pruebas con esta nueva prueba?
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest2.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman deber$\ac{i}$a fallar con una entrada muy grande ... ERROR
======================================================================
ERROR: to_roman deber$\ac{i}$a fallar con una entrada muy grande
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest2.py", line 78, in test_too_large
self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
AttributeError: 'module' object has no attribute 'OutOfRangeError'
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} Deberías esperar que fallara (puesto que aún no has escrito ningún código que pase la prueba), pero... En realidad no ``falló'', en su lugar se produjo un ``error''. Hay una sutil pero importante diferencia. Una prueba unitaria puede terminar con tres tipos de valores: pasar la prueba, fallar y error. Pasar la prueba significa que el código hace lo que se espera. ``fallar'' es lo que hacía la primera prueba que hicimos (hasta que escribimos el código que permitía pasar la prueba) ---ejecutaba el código pero el resultado no era el esperado. ``error'' significa que el código ni siquiera se ejecutó apropiadamente.
\item \emph{Línea 13:} ¿Porqué el código no se ejecutó correctamente? La traza del error lo indica. El módulo que estás probando no dispone de una excepción denominada \codigo{OutOfRangeError}. Recuerda, has pasado esta excepción al método \codigo{assertRaises()} porque es la excepción que quieres que que la función eleve cuando se le pasa una entrada fuera del rango válido de valores. Pero la excepción aún no existe, por lo que la llamada al método \codigo{assertRaises()} falla. Ni siquiera ha tenido la oportunidad de probar el funcionamiento de la función \codigo{to\_roman()}; no llega tan lejos.
\end{enumerate}
Para resolver este problema necesitas definir la excepción \codigo{OutOfRangeError} en el fichero \codigo{roman2.py}.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class OutOfRangeError(ValueError):
pass
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 1:} Las excepciones son clases. Un error ``fuera de rango'' es un tipo de error del valor ---el parámetro está fuera del rango de los valores aceptables. Por ello esta excepción hereda de la excepción propia de Python \codigo{ValueError}. Esto no es estrictamente necesario (podría heredar directamente de la clase \codigo{Exception}), pero parece más correcto.
\item \emph{Línea 2:} Las excepciones no suelen hacer nada, pero necesitas al menos una línea de código para crear una clase. Al llamar a la sentencia \codigo{pass} conseguimos que no se haga nada y que exista la línea de código necesaria, así que ya tenemos creada la clase de la excepción.
\end{enumerate}
Ahora podemos intentar ejecutar la suite de pruebas de nuevo.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest2.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman deber$\ac{i}$a fallar con una entrada muy grande ... FAIL
======================================================================
FAIL: to_roman deber$\ac{i}$a fallar con una entrada muy grande
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest2.py", line 78, in test_too_large
self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000)
AssertionError: OutOfRangeError not raised by to_roman
----------------------------------------------------------------------
Ran 2 tests in 0.016s
FAILED (failures=1)
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} Aún no pasa la nueva prueba, pero ya no devuelve un error. En su lugar, la prueba falla. ¡Es un avance! Significa que la llamada al método \codigo{assertRaises()} se completó con éxito esta vez, y el entorno de pruebas unitarias realmente comprobó el funcionamiento de la función \codigo{to\_roman()}.
\item \emph{Línea 13:} Es evidente que la función \codigo{to\_roman()} no eleva la excepción \codigo{OutOfRangeError} que acabas de definir, puesto que aún no se lo has dicho. ¡Son noticias excelentes! Significa que es una prueba válida ---falla antes de que escribas el código que hace falta para que pueda pasar.
\end{enumerate}
Ahora toca escribir el código que haga pasar esta prueba satisfactoriamente.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def to_roman(n):
'''convert integer to Roman numeral'''
if n > 3999:
raise OutOfRangeError(
'number out of range (must be less than 4000)')
result = ''
for numeral, integer in roman_numeral_map:
while n >= integer:
result += numeral
n -= integer
return result
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 4:} Es inmediato: si el valor de entrada (\codigo{n}) es mayor de \codigo{3999}, eleva la excepción \codigo{OutOfRangeError}. El caso de prueba no valida la cadena de texto que contiene la excepción, aunque podrías escribir otra prueba que hiciera eso (pero ten cuidado con los problemas de internacionalización de cadenas que pueden hacer que varíen en función del idioma del usuario y de la configuración del equipo).
\end{enumerate}
¿Pasará ahora la prueba? Veámoslo.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest2.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with large input ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} Bien, pasan las dos pruebas. Al haber trabajado iterativamente, yendo y viniendo entre la prueba y el código, puedes estar seguro de que las dos líneas de código que acabas de escribir son las causantes de que una de las pruebas pasara de ``fallar'' a ``pasar''. Esta clase de confianza no es gratis, pero revierte por sí misma conforme vas desarrollando más y más código.
\section{Más paradas, más fuego}
\end{enumerate}
Además de probar con números que son demasiado grandes, también es necesario probar con números demasiado pequeños. Como indicamos al comienzo, en los requisitos funcionales, los números romanos no pueden representar ni el cero, ni los números negativos.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> import roman2
>>> roman2.to_roman(0)
''
>>> roman2.to_roman(-1)
''
\end{lstlisting}
\end{minipage}
No está bien, vamos a añadir pruebas para cada una de estas condiciones.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class ToRomanBadInput(unittest.TestCase):
def test_too_large(self):
'''to_roman should fail with large input'''
self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, 4000)
def test_zero(self):
'''to_roman should fail with 0 input'''
self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, 0)
def test_negative(self):
'''to_roman should fail with negative input'''
self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, -1)
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 4:} El método \codigo{test\_too\_large()} no ha cambiado desde el paso anterior. Se muestra aquí para enseñar el sitio en el que se incorpora el nuevo código.
\item \emph{Línea 8:} Aquí hay una nueva prueba: el método \codigo{test\_zero()}. Como el método anterior, indica al método \codigo{assertRaises()}, definido en \codigo{unittest.TestCase}, que llame a nuestra función \codigo{to\_roma()} con el parámetro \codigo{0}, y valida que se eleve la excepción correcta, \codigo{OutOfRangeError}.
\item \emph{Línea 12:} El método \codigo{test\_negative()} es casi idéntico, excepto que pasa \codigo{-1} a la función \codigo{to\_roman()}. Si alguna d estas pruebas no eleva una excepción \codigo{OutOfRangeError} (O porque la función retorna un valor o porque eleva otra excepción), se considera que la prueba ha fallado.
\end{enumerate}
Vamos a comprobar que la prueba falla:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest3.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_negative ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with negative input ... FAIL
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with large input ... ok
test_zero ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with 0 input ... FAIL
======================================================================
FAIL: to_roman should fail with negative input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest3.py", line 86, in test_negative
self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, -1)
AssertionError: OutOfRangeError not raised by to_roman
======================================================================
FAIL: to_roman should fail with 0 input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest3.py", line 82, in test_zero
self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, 0)
AssertionError: OutOfRangeError not raised by to_roman
----------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED (failures=2)
\end{lstlisting}
\end{minipage}
Estupendo, ambas pruebas fallan como se esperaba. Ahora vamos a volver al código a ver lo que podemos hacer para que pasen las pruebas.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def to_roman(n):
'''convert integer to Roman numeral'''
if not (0 < n < 4000):
raise OutOfRangeError('number out of range (must be 1..3999)')
result = ''
for numeral, integer in roman_numeral_map:
while n >= integer:
result += numeral
n -= integer
return result
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 3:} Esta es una forma muy Pythónica de hacer las cosas: dos comparaciones a la vez. Es equivalente a \codigo{if not ((0<n) and (n<400))}, pero es mucho más fácil de leer. Esta línea de código debería capturar todas las entradas que sean demasiado grandes, negativas o cero.
\item \emph{Línea 4:} Si cambias las condiciones, asegúrate de actualizar las cadenas de texto para que coincidan con la nueva condición. Al paquete \codigo{unittest} no le importará, pero será más difícil depurar el código si lanza excepciones que están descritas de forma incorrecta.
\end{enumerate}
Podría mostrarte una serie completa de ejemplos sin relacionar para enseñarte cómo funcionan las comparaciones múltiples, pero en vez de eso, simplemente ejecutaré unas pruebas unitarias y lo probaré.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest3.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_negative ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with negative input ... ok
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with large input ... ok
test_zero ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with 0 input ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.016s
OK
\end{lstlisting}
\end{minipage}
\section{Y una cosa más...}
Había un requisito funcional más para convertir números a números romanos: tener en cuenta a los números no enteros.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> import roman3
>>> roman3.to_roman(0.5)
''
>>> roman3.to_roman(1.0)
'I'
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 2:} ¡Oh! Qué mal.
\item \emph{Línea 4:} ¡Oh! Incluso peor. Ambos casos deberían lanzar una excepción. En vez de ello, retornan valores falsos.
\end{enumerate}
La validación sobre los no enteros no es difícil. Primero define una excepción \codigo{NotIntegerError}.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# roman4.py
class OutOfRangeError(ValueError): pass
class NotIntegerError(ValueError): pass
\end{lstlisting}
\end{minipage}
Lo siguiente es escribir un caso de prueba que compruebe si se lanza una excepción \codigo{NotIntegerError}.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class ToRomanBadInput(unittest.TestCase):
.
.
.
def test_non_integer(self):
'''to_roman should fail with non-integer input'''
self.assertRaises(roman4.NotIntegerError, roman4.to_roman, 0.5)
\end{lstlisting}
\end{minipage}
Ahora vamos a validar si la prueba falla apropiadamente.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest4.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_negative ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with negative input ... ok
test_non_integer ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with non-integer input ... FAIL
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with large input ... ok
test_zero ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with 0 input ... ok
======================================================================
FAIL: to_roman should fail with non-integer input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest4.py", line 90, in test_non_integer
self.assertRaises(roman4.NotIntegerError, roman4.to_roman, 0.5)
AssertionError: NotIntegerError not raised by to_roman
----------------------------------------------------------------------
Ran 5 tests in 0.000s
FAILED (failures=1)
\end{lstlisting}
\end{minipage}
Escribe ahora el código que haga que se pase la prueba.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def to_roman(n):
'''convert integer to Roman numeral'''
if not (0 < n < 4000):
raise OutOfRangeError('number out of range (must be 1..3999)')
if not isinstance(n, int):
raise NotIntegerError('non-integers can not be converted')
result = ''
for numeral, integer in roman_numeral_map:
while n >= integer:
result += numeral
n -= integer
return result
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 5:} La función interna de Python \codigo{isinstance()} comprueba si una variable es de un determinado tipo (o técnicamente, de cualquier tipo descendiente).
\item \emph{Línea 6:} Si el parámetro \codigo{n} no es \codigo{int} elevará la nueva excepción \codigo{NotIntegerError}.
\end{enumerate}
Por último, vamos a comprobar si realmente el código pasa las pruebas.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest4.py -v
test_to_roman_known_values ($\_\_$main$\_\_$.KnownValues)
to_roman should give known result with known input ... ok
test_negative ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with negative input ... ok
test_non_integer ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with non-integer input ... ok
test_too_large ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with large input ... ok
test_zero ($\_\_$main$\_\_$.ToRomanBadInput)
to_roman should fail with 0 input ... ok
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
\end{lstlisting}
\end{minipage}
La función \codigo{to\_roman} pasa todas las pruebas y no se me ocurren nuevas pruebas, por lo que es el momento de pasar a la función \codigo{from\_roman()}
\section{Una agradable simetría}
Convertir una cadena de texto que representa un número romano a entero parece más difícil que convertir un entero en un número romano. Es cierto que existe el tema de la validación. Es fácil validar si un número entero es mayor que cero, pero un poco más difícil comprobar si una cadena es un número romano válido. Pero ya habíamos construido una expresión regular para comprobar números romanos, por lo que esa parte está hecha.
Eso nos deja con el problema de convertir la cadena de texto. Como veremos en un minuto, gracias a la rica estructura de datos que definimos para mapear los números romanos a números enteros, el núcleo de la función \codigo{from\_roman()} es tan simple como el de la función \codigo{to\_roman()}.
Pero primero hacemos las puertas. Necesitaremos una prueba de valores válidos conocidos para comprobar la precisión de la función. Nuestro juego de pruebas ya contiene una mapa de valores conocidos; vamos a reutilizarlos.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
...
def test_from_roman_known_values(self):
'''from_roman should give known result with known input'''
for integer, numeral in self.known_values:
result = roman5.from_roman(numeral)
self.assertEqual(integer, result)
...
\end{lstlisting}
\end{minipage}
Existe una agradable simetría aquí. Las funciones \codigo{to\_roman()} y \codigo{from\_roman()} son la inversa una de la otra. La primera convierte números enteros a cadenas formateadas que representan números romanos, la segunda convierte cadenas de texto que representan a números romanos a números enteros. En teoría deberíamos ser capaces de hacer ciclos con un número pasándolo a la función \codigo{to\_roman()} para recuperar una cadena de texto, luego pasar esa cadena a la función \codigo{from\_roman()} para recuperar un número entero y finalizar con el mismo número que comenzamos.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
n = from_roman(to_roman(n)) for all values of n
\end{lstlisting}
\end{minipage}
En este caso ``all values'' significa cualquier número entre el \codigo{1..3999}, puesto que es el rango válido de entradas a la función \codigo{to\_roman()}. Podemos expresar esta simetría en un caso de prueba que recorrar todos los valores \codigo{1..3999}, llamar a \codigo{to\_roman()}, llamar a \codigo{from\_roman()} y comprobar que el resultado es el mismo que la entrada original.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class RoundtripCheck(unittest.TestCase):
def test_roundtrip(self):
'''from_roman(to_roman(n))==n for all n'''
for integer in range(1, 4000):
numeral = roman5.to_roman(integer)
result = roman5.from_roman(numeral)
self.assertEqual(integer, result)
\end{lstlisting}
\end{minipage}
Estas pruebas ni siquiera fallarán. No hemos definido aún la función \codigo{from\_\allowbreak roman()} por lo que únicamente se elevarán errores.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest5.py
E.E....
======================================================================
ERROR: test_from_roman_known_values ($\_\_$main$\_\_$.KnownValues)
from_roman should give known result with known input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest5.py", line 78, in test_from_roman_known_values
result = roman5.from_roman(numeral)
AttributeError: 'module' object has no attribute 'from_roman'
======================================================================
ERROR: test_roundtrip ($\_\_$main$\_\_$.RoundtripCheck)
from_roman(to_roman(n))==n for all n
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest5.py", line 103, in test_roundtrip
result = roman5.from_roman(numeral)
AttributeError: 'module' object has no attribute 'from_roman'
----------------------------------------------------------------------
Ran 7 tests in 0.019s
FAILED (errors=2)
\end{lstlisting}
\end{minipage}
Una función vacía resolverá este problema:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# roman5.py
def from_roman(s):
'''convert Roman numeral to integer'''
\end{lstlisting}
\end{minipage}
(¿Te has dado cuenta? He definido una función simplemente poniendo un docstring. Esto es válido en Python. De hecho, algunos programadores lo toman al pie de la letra. ``No crees una función vacía, ¡documéntala!'')
Ahora los casos de prueba sí que fallarán.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest5.py
F.F....
======================================================================
FAIL: test_from_roman_known_values ($\_\_$main$\_\_$.KnownValues)
from_roman should give known result with known input
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest5.py", line 79, in test_from_roman_known_values
self.assertEqual(integer, result)
AssertionError: 1 != None
======================================================================
FAIL: test_roundtrip ($\_\_$main$\_\_$.RoundtripCheck)
from_roman(to_roman(n))==n for all n
----------------------------------------------------------------------
Traceback (most recent call last):
File "romantest5.py", line 104, in test_roundtrip
self.assertEqual(integer, result)
AssertionError: 1 != None
----------------------------------------------------------------------
Ran 7 tests in 0.002s
FAILED (failures=2)
\end{lstlisting}
\end{minipage}
Ahora ya podemos escribir la función \codigo{from\_roman()}.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def from_roman(s):
"""convert Roman numeral to integer"""
result = 0
index = 0
for numeral, integer in roman_numeral_map:
while s[index:index+len(numeral)] == numeral:
result += integer
index += len(numeral)
return result
\end{lstlisting}
\end{minipage}
\begin{enumerate}
\item \emph{Línea 6:} El patrón aquí es el mismo que el de la función \codigo{to\_roman()}. Iteras a través de la estructura de datos de números romanos (la tupla de tuplas), pero en lugar de encontrar el mayor número entero tantas veces como sea posible, compruebas coincidencias del carácter romano más ``alto'' tantas veces como sea posible.
\end{enumerate}
Si no te queda claro cómo funciona \codigo{from\_roman()} añade una sentencia \codigo{print} al final del bucle \codigo{while}:
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def from_roman(s):
"""convert Roman numeral to integer"""
result = 0
index = 0
for numeral, integer in roman_numeral_map:
while s[index:index+len(numeral)] == numeral:
result += integer
index += len(numeral)
print('found', numeral, 'of length', len(numeral),
', adding', integer)
>>> import roman5
>>> roman5.from_roman('MCMLXXII')
found M of length 1, adding 1000
found CM of length 2, adding 900
found L of length 1, adding 50
found X of length 1, adding 10
found X of length 1, adding 10
found I of length 1, adding 1
found I of length 1, adding 1
1972
\end{lstlisting}
\end{minipage}
Es el momento de volver a ejecutar las pruebas.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest5.py
.......
----------------------------------------------------------------------
Ran 7 tests in 0.060s
OK
\end{lstlisting}
\end{minipage}
Tenemos dos buenas noticias aquí. Por un lado que la función \codigo{from\_roman()} pasa las pruebas ante entradas válidas, al menos para los valores conocidos. Por otro, la prueba de ida y vuelta también ha pasado. Combinada con las pruebas de valores conocidos, puedes estar razonablemente seguro de que ambas funciones \codigo{to\_roman()} y \codigo{from\_roman()} funcionan correctamente para todos los valores válidos (Esto no está garantizado: es teóricamente posible que la función \codigo{to\_roman()} tuviera un fallo que produjera un valor erróneo de número romano y que la función \codigo{from\_roman()} tuviera un fallo recíproco que produjera el mismo valor erróneo como número entero. Dependiendo de la aplicación y de los requisitos, esto puede ser más o menos preocupante; si es así, hay que escribir un conjunto de casos de prueba mayor hasta que puedas quedar razonablemente tranquilo en cuanto a la fiabilidad del código desarrollado).
\section{Más entradas erróneas}
Ahora que la función \codigo{from\_roman()} funciona correctamente con entradas válidas es el momento de poner la última pieza del puzzle: hacer que funcione correctamente con entradas incorrectas. Eso significa encontrar un modo de mirar una cadena para determinar si es un número romano correcto. Esto es inherentemente más difícil que validar las entradas numéricas de la función \codigo{to\_roman()}, pero dispones de una poderosa herramienta: las expresiones regulares (si no estás familiarizado con ellas, ahora es un buen momento para leer el capítulo sobre las expresiones regulares).
Como viste en el caso de estudio: números romanos, existen varias reglas simples para construir un número romano utilizando las letras \codigo{M, D, C, L, X, V} e \codigo{I}. Vamos a revisar las reglas:
\begin{itemize}
\item{Algunas veces los caracteres son aditivos, \codigo{I} es \codigo{1}, \codigo{II} es \codigo{2} y \codigo{III} es \codigo{3}. \codigo{VI} es \codigo{6} (literalmente 5 + 1), \codigo{VII} es \codigo{7} (5+1+1) y \codigo{XVIII} es \codigo{18} (10+5+1+1+1).
\item Los caracteres que representan unidades, decenas, centenas y unidades de millar (\codigo{I, X, C y M}) pueden aparecer juntos hasta tres veces como máximo. Para el \codigo{4} debes restar del carácter \codigo{V, L ó D} (cinco, cincuenta, quinientos) que se encuentre más próximo a la derecha. No se puede representar el cuatro como \codigo{IIII}, en su lugar hay que poner \codigo{IV} (5-1). El número \codigo{40} se representa como \codigo{XL} (\codigo{10} menos que \codigo{50}: 50-10). \codigo{41 = XLI}, \codigo{42 = XLII}, \codigo{43 = XLIII} y luego \codigo{44 = XLIV} (diez menos que cincuenta más uno menos que cinco: 50-10+5-1).
\item De forma similar, para el número \codigo{9}, debes restar del número siguiente más próximo que represente unidades, decenas, centenas o unidades de millar (\codigo{I, X, C y M}). \codigo{ 8 = VIII}, pero \codigo{9 = IX} (\codigo{1} menos que \codigo{10}), no \codigo{9 = VIIII} puesto que el carácter \codigo{I} no puede repetirse cuatro veces seguidas. El número \codigo{90} se representa con \codigo{XC} y el \codigo{900} con \codigo{CM}.
\item Los caracteres \codigo{V, L y D} no pueden repetirse; el número \codigo{10} siempre se representa como \codigo{X} y no como \codigo{VV}. El número \codigo{100} siempre se representa como \codigo{C} y nunca como \codigo{LL}.
\item Los números romanos siempre se escriben de los caracteres que representan valores mayores a los menores y se leen de izquierda a derecha por lo que el orden de los caracteres importa mucho. \{DC} es el número \codigo{600}; \codigo{CD} otro número, el \codigo{400} (500 - 100). \codigo{CI} es \codigo{101}, mientras que \codigo{IC} no es un número romano válido porque no puedes restar \codigo{I} del \codigo{C}\footnote{Para representar el \codigo{99} deberías usar: \codigo{XCIL} (100 - 10 + 10 - 1)}.
\end{itemize}
Así, una prueba apropiada podría ser asegurarse de que la función \codigo{from\_roman()} falla cuando pasas una cadena que tiene demasiados caracteres romanos repetidos. Pero, ¿cuánto es ``demasiados'? ...depende del carácter.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
class FromRomanBadInput(unittest.TestCase):
def test_too_many_repeated_numerals(self):
'''from_roman should fail with too many repeated numerals'''
for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
self.assertRaises(roman6.InvalidRomanNumeralError,$\newline$
roman6.from_roman, s)
\end{lstlisting}
\end{minipage}
Otra prueba útil sería comprobar que ciertos patrones no están repetidos. Por ejemplo, \codigo{IX} es \codigo{9}, pero \codigo{IXIX} no es válido nunca.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
...
def test_repeated_pairs(self):
'''from_roman should fail with repeated pairs of numerals'''
for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
self.assertRaises(roman6.InvalidRomanNumeralError, $\newline$
roman6.from_roman, s)
...
\end{lstlisting}
\end{minipage}
Una tercera prueba podría comprobar que los caracteres aparecen en el orden correcto, desde el mayor al menor. Por ejemplo, \codigo{CL} es \codigo{150}, pero \codigo{LC} nunca es válido, porque el carácter para el \codigo{50} nunca puede aparecer antes del carácter del \codigo{100}. Esta prueba incluye un conjunto no válido de conjuntos de caracteres elegidos aleatoriamente: \codigo{I} antes que \codigo{M}, \codigo{V} antes que \codigo{X}, y así sucesivamente.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
...
def test_malformed_antecedents(self):
'''from_roman should fail with malformed antecedents'''
for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
self.assertRaises(roman6.InvalidRomanNumeralError,
roman6.from_roman, s)
...
\end{lstlisting}
\end{minipage}
Cada una de estas pruebas se basan en que la función \codigo{from\_roman()} eleve una excepción, \codigo{InvalidRomanNumeralError}, que aún no hemos definido.
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# roman6.py
class InvalidRomanNumeralError(ValueError): pass
\end{lstlisting}
\end{minipage}
Las tres pruebas deberían fallar, puesto que \codigo{from\_roman()} aún no efectúa ningún tipo de validación de la entrada (Si no fallan ahora, ¿qué demonios están comprobando?).
\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
you@localhost:~/diveintopython3/examples$\$$ python3 romantest6.py
FFF.......
======================================================================
FAIL: test_malformed_antecedents ($\_\_$main$\_\_$.FromRomanBadInput)