| You are here: Inicio > Inmersión en Python > Pruebas unitarias (Unit Testing) > Pruebas de cordura | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
A menudo se encontrará con que un código unitario contiene un conjunto de funciones recíprocas normalmente en forma de funciones de conversión en que una convierte de A a B y la otra de B a A. En estos casos es útil crear “pruebas de cordura” (sanity checks para asegurarse de que puede convertir de A a B y de vuelta a A sin perder precisión, incurrir en errores de redondeo o encontrar ningún otro tipo de fallo.
Considere este requisito:
class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 4000):![]()
numeral = roman.toRoman(integer) result = roman.fromRoman(numeral) self.assertEqual(integer, result)
| Hemos visto antes la función range, pero aquí la invocamos con dos argumentos, que devuelven una lista de enteros que empiezan en el primero (1) y avanzan consecutivamente hasta el segundo argumento (4000) sin incluirlo. O sea, 1..3999, que es el rango válido para convertir a números romanos. | |
| Sólo quiero mencionar de pasada que integer no es una palabra reservada de Python; aquí es un nombre de variable como cualquier otro. | |
| La lógica real de la prueba es muy simple: tomamos un número (integer), lo convertimos en número romano (numeral), luego lo convertimos de vuelta en número (result) y nos aseguramos de que es el mismo número con que empezamos. Si no, assertEqual lanzará una excepción y se considerará inmediatamente la prueba fallida. Si todos los números coinciden, assertEqual volverá siempre silenciosa, lo mismo hará eventualmente el método testSanity, y se considerará pasada la prueba. |
Los dos últimos requisitos son diferentes de los otros porque ambos son arbitrarios y triviales:
Ciertamente, son bastante arbitrarios. Podríamos por ejemplo haber estipulado que fromRoman acepte entradas en minúsculas y en una mezcla de ambas. Pero no son completamente arbitrarias; si toRoman devuelve siempre una salida en mayúsculas, entonces fromRoman debe al menos aceptar entrada en mayúsculas, o fallaría la “prueba de cordura” (6º requisito). El hecho de que sólo acepte entradas en mayúscula es arbitrario, pero como le dirá cualquier integrador de sistemas, la caja de las letras[15] siempre importa así que merece la pena especificar el comportamiento de entrada. Y si vale la pena especificarlo, también probarlo.
class CaseCheck(unittest.TestCase): def testToRomanCase(self): """toRoman should always return uppercase""" for integer in range(1, 4000): numeral = roman.toRoman(integer) self.assertEqual(numeral, numeral.upper())def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 4000): numeral = roman.toRoman(integer) roman.fromRoman(numeral.upper())
![]()
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, numeral.lower())
| Lo más interesante de este caso de prueba son todas las cosas que no prueba. No prueba si el valor devuelto por toRoman es correcto o incluso consistente; estas cuestiones las resuelven otros casos de prueba. Tenemos un caso de prueba sólo para probar las mayúsculas. Podría tentarle combinar esto con la prueba de cordura, ya que ambas pasan por todo el rango de valores y llaman a toRoman.[16] Pero eso violaría una de las reglas fundamentales: cada caso de prueba debería responder sólo una cuestión. Imagine que combinase este prueba de mayúsculas con la de cordura, y que falla el caso de prueba. Necesitaríamos hacer más análisis para averiguar qué parte del caso de prueba fallo para determinar el problema. Si necesita analizar los resultados de las pruebas unitarias sólo para averiguar qué significan, es un caso seguro de que ha desarrollado mal sus casos de prueba. | |
| Aquí se puede aprender una lección similar: incluso aunque “sepa” que toRoman siempre devuelve mayúsculas, estamos convirtiendo explícitamente su valor de retorno a mayúsculas aquí para probar que fromRoman acepta entrada en mayúsculas. ¿Por qué? Porque el hecho de que toRoman siempre devuelve mayúsculas es un requisito independiente. Si cambiamos ese requisito a, por ejemplo, que siempre devuelva minúsculas, habría que cambiar la prueba testToRomanCase, pero este caso debería seguir funcionando. Ésta es otra de las reglas fundamentales: cada prueba debe ser capaz de funcionar aisladamente de las otras. Cada caso de prueba es una isla. | |
| Observe que no estamos asignando el valor de retorno de fromRoman a nada. Esto es sintaxis válida en Python; si una función devuelve un valor pero nadie está a la escucha, Python simplemente descarta el valor devuelto. En este caso, es lo que queremos. Este caso de prueba no tiene nada que ver con el valor de retorno; simplemente comprueba que fromRoman acepta la entrada en mayúsculas sin lanzar ninguna excepción. | |
| Esta línea es complicada, pero es muy similar a lo que hicimos en las pruebas ToRomanBadInput y FromRomanBadInput. Estamos probando para aseguranos de que llamar a una función en particular (roman.fromRoman) con un valor en particular (numeral.lower(), la versión en minúsculas del valor en romanos actual en el bucle) lanza una excepción en particular (roman.InvalidRomanNumeralError). Si lo hace (cada vez en el bucle), la prueba es válida; si al menos una vez hace cualquier otra cosa (como lanzar una excepción diferente, o devolver un valor sin lanzar una excepción), la prueba falla. |
En el siguiente capítulo, veremos cómo escribir código que pasa estas pruebas.
<< Prueba de fallo |
| 1 | 2 | 3 | 4 | 5 | 6 | |
Programación Test-First >> |