| You are here: Inicio > Inmersión en Python > Refactorización | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
A pesar de nuestros mejores esfuerzos para escribir pruebas unitarias exhaustivas, los fallos aparecen. ¿A qué me refiero con “un fallo”? Un fallo es un caso de prueba que aún no hemos escrito.
>>> import roman5 >>> roman5.fromRoman("")0
| ¿Recuerda cuando en la sección anterior vimos que una cadena vacía coincidía con la expresión regular que estábamos usando para los números romanos válidos? Bien, resulta que esto sigue siendo cierto para la versión final de la expresión regular. Y esto es un fallo; queremos que una cadena vacía lance una excepción InvalidRomanNumeralError igual que cualquier otra secuencia de caracteres que no represente un número romano válido. |
Tras reproducir el fallo, y antes de arreglarlo, debería escribir un caso de prueba que falle que ilustre el fallo.
class FromRomanBadInput(unittest.TestCase): # se omiten los casos de prueba anteriores por claridad (no han # cambiado) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, "")![]()
Como el código tiene un fallo, y ahora tenemos un caso de prueba que busca ese fallo, la prueba fallará:
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... FAIL fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ====================================================================== FAIL: fromRoman should fail with blank string ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage6\romantest61.py", line 137, in testBlank self.assertRaises(roman61.InvalidRomanNumeralError, roman61.fromRoman, "") File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ---------------------------------------------------------------------- Ran 13 tests in 2.864s FAILED (failures=1)
Ahora podemos arreglar el fallo.
Este fichero está disponible en py/roman/stage6/ dentro del directorio de ejemplos.
def fromRoman(s): """convert Roman numeral to integer""" if not s:raise InvalidRomanNumeralError, 'Input can not be blank' if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... okfromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------- Ran 13 tests in 2.834s OK
Programar de esta manera no hace más fácil arreglar fallos. Los fallos simples (como éste) precisan casos de prueba sencillos; los fallos complejos precisan casos de prueba complejos. En un entorno centrado en las pruebas puede parecer que se tarda más en corregir un fallo, ya que se necesita articular en el código exactamente cual es el fallo (escribir el caso de prueba) y luego corregir el fallo en sí. Si el caso de prueba no tiene éxito ahora, tendremos que averiguar si el arreglo fue incorrecto o si el propio caso de prueba tiene un fallo. Sin embargo, a la larga, este tira y afloja entre código de prueba y código probado se amortiza por sí solo. Además, ya que podemos ejecutar de nuevo de forma sencilla todos los casos de prueba junto con el código nuevo, es mucho menos probable que rompamos código viejo al arreglar el nuevo. La prueba unitaria de hoy es la prueba de regresión de mañana.
<< roman.py, fase 5 |
| 1 | 2 | 3 | 4 | 5 | |
Tratamiento del cambio de requisitos >> |