Las afirmaciones son muy comunes en las pruebas de unidad. De hecho, Python tiene una gran colección de afirmaciones personalizadas para las pruebas unitarias. Sin embargo, no hay razón para que esta útil herramienta se utilice simplemente en el mundo de las pruebas.
Las declaraciones de afirmación dentro del código normal también son muy útiles. Estas declaraciones toman una expresión y levantan unAssertionError, junto con un mensaje opcional si la expresión es Falsa.
Supongamos que tenemos la siguiente función que toma valores de un usuario y normalizará el rango especificado de datos en algo entre 0 y 1, que puede ser usado por un nuevo widget más adelante.
12345678910111213141516171819202122232425defnormalize_ranges(colname):""" Normalizar el rango de datos dado a valores en [0 - 1] Devolver al diccionario nuevas claves $0027min$0027 y $0027max$0027 en el rango [0 - 1] """# 1-D numpy array de datos que cargamos la aplicación con original_range = get_base_range(colname) colspan = original_range[$0027datamax$0027]- original_range[$0027datamin$0027]# Datos filtrados por el usuario de la GUI live_data = get_column_data(colname) live_min = numpy. min(datos_vivos) live_max = numpy.max(datos_vivos) ratio ={}try: ratio[$0027min$0027]=(live_min - rango_original[$0027datamin$0027])/colspan ratio[$0027max$0027]=(live_max - rango_original[$0027datamin$0027])/ colspan excepto ZeroDivisionError: ratio[$0027min$0027]=0.0 ratio[$0027max$0027]=0.0retorno ratio
pitón
Si nuestra función anterior afirmaba que siempre devolvía un valor entre 0 – 1. Desafortunadamente, más énfasis en nuestras suposiciones muestra que esto no es cierto:
123; age = numpy.array([-10.0,20.0,30.0,40.0,50.0]); normalize_ranges($0027age$0027){$0027max$0027:1.0,$0027min$0027:-0.5}
pitón
Como pueden imaginar, este escenario podría pasar fácilmente desapercibido durante mucho tiempo y este valor de retorno podría propagarse por todo el código base. Este es precisamente el tipo de error que es imposible de encontrar y lleva a la triste historia que comenzó esta serie de guías.
Podríamos tratar de pensar en todos los valores posibles que nuestros usuarios podrían pasar y manejarlos adecuadamente. De hecho, esto es lo correcto que hay que hacer, pero no hay garantía de que no se nos escape algo. Ya hemos reconocido el hecho de que los programadores son falibles.
Afortunadamente, podemos usar declaraciones de afirmación para codificar contra nuestro yo futuro ahora que hemos aceptado que cometemos errores.
12345678910111213141516171819202122232425262728293031323334defnormalize_ranges(colname):""" Normalizar el rango de datos dado a valores en [0 - 1] Devolver al diccionario nuevas claves $0027min$0027 y $0027max$0027 en el rango [0 - 1] """# 1-D numpy array de datos que cargamos la aplicación con original_range = get_base_range(colname) colspan = original_range[$0027datamax$0027]- original_range[$0027datamin$0027]# Datos filtrados por el usuario de la GUI live_data = get_column_data(colname) live_min = numpy. min(datos_vivos) live_max = numpy. max(datos_vivos) ratio ={}try: ratio[$0027min$0027]=(live_min - rango_original[$0027datamin$0027])/colspan ratio[$0027max$0027]=(live_max - rango_original[$0027datamin$0027])/ colspan excepto ZeroDivisionError: ratio[$0027min$0027]=0.0 ratio[$0027max$0027]=0.0assert0.0<= ratio[$0027min$0027]<=1. 0,($0027"%s" min (%f) no en [0-1] dado (%f) colspan (%f)$0027%( colname, ratio[$0027min$0027], original_range[$0027datamin$0027], colspan))assert0.0<= ratio[$0027max$0027]<=1. 0,($0027"%s" max (%f) no en [0-1] dado (%f) colspan (%f)$0027%( colname, ratio[$0027max$0027], original_range[$0027datamax$0027], colspan))return ratio
pitón
Añadimos algunas declaraciones de afirmación que nos alertarán si no devolvemos los valores dentro del rango esperado. Veamos cómo estas afirmaciones cambian nuestro pequeño caso de prueba:
123;<;edad = numpy.array([-10.0,20.0,30.0,40.0,50.0])<;normalize_ranges($0027age$0027)AssertionError: "age "min(-0.500000)notin[0-1] given (10.000000) colspan(40.000000)
pitón
Este pequeño cambio tiene varios beneficios:
- Sirve como una forma de documentación ejecutable
- Coloca las advertencias más cerca de la raíz del problema
- Incluye valiosa información de depuración sobre los parámetros "inválidos"
1. Sirve como una forma de documentación ejecutable
Típicamente la documentación viene en unos pocos sabores diferentes como los comentarios en línea o en bloque, las cuerdas de acoplamiento y la esfinge. Cada uno de estos sirve a un propósito específico y son casi esenciales para el desarrollo de software. Desafortunadamente, todos ellos sufren del mismo problema: Pueden desincronizarse rápidamente con el código y los requisitos que cambian rápidamente. Esto lleva a una documentación en la que el desarrollador no puede confiar.
Las afirmaciones actúan como documentación con un propósito diferente. Describen clara y concisamente el estado esperado de la aplicación en tiempo de ejecución. Además, la aplicación se quejará si cambiamos nuestras suposiciones sin modificar las afirmaciones para que coincidan con el nuevo comportamiento.
Es mucho más probable que las declaraciones de afirmación se actualicen junto con otros cambios. Por lo tanto, las aseveraciones son más confiables que la documentación no ejecutable. Además, las aseveraciones siguen proporcionando muchos de los beneficios de los comentarios, las cadenas de documentación, etc.
Vale la pena señalar que hay otra forma de documentación ejecutable que es bastante común en el ecosistema de Python conocida como doctests. Los doctests/documentación pueden ser algo feo de mirar, pero su característica clave es que están cerca del código , al igual que las afirmaciones.
2. Coloca las advertencias más cerca de la raíz del problema
Todos hemos estado allí, depuras un problema durante horas y te das cuenta de que el real error no estaba ni siquiera cerca de donde empezaste (ver 5 porqués). Tal vez la causa principal del error estaba lógicamente lejos de donde viste los síntomas por primera vez.
Por ejemplo, encuentras una cadena de bytes en lo profundo de tu sistema, pero asumiste que todo lo interno era cadenas de Unicode. Podría llevar mucho tiempo encontrar dónde se rompió la conversión por primera vez. Es una situación frustrante en la que estar. Sería bueno haber encontrado el error mucho antes o al menos tener más información de depuración.
Las afirmaciones no van a prevenir esta situación, pero ofrecen una oportunidad para mejorarla. Las afirmaciones anteriores nos alertarán en el momento en que esta función no obedezca su contrato para devolver un valor entre 0 - 1. Esto podría darnos una valiosa pista más tarde, si encontramos otro código que tenga un rango inválido. Sabremos que esta función no cumplió con su fin de contrato. Esta única pista podría literalmente ahorrar horas, ya que puede evitar el rastreo desde el síntoma hasta la causa.
3. Incluye valiosa información de depuración sobre los parámetros "inválidos"
Fíjese que nuestras declaraciones de afirmación también incluían información sobre los parámetros de entrada. Esta información será inestimable cuando un usuario se encuentre con el bicho usando datos a los que no tenemos acceso. Además, la información de depuración será especialmente útil cuando el usuario tenga problemas para explicar el escenario de error. Así, estas pocas afirmaciones podrían evitar que seas tú quien marque vergonzosamente el fallo con un estado "no reproducible".
La información de los parámetros de entrada también tiene otros beneficios sutiles:
- Muestra una suposición inválida sobre el tipo de datos que los usuarios están ejecutando.
- Explica el descuido en nuestra documentación sobre el tipo de datos que se espera.
- Expone posibles nuevos casos de uso que no se pueden ejecutar.