Realmente estoy luchando por escribir pruebas unitarias efectivas para un gran proyecto de Django. Tengo una cobertura de prueba razonablemente buena, pero me he dado cuenta de que las pruebas que he estado escribiendo son definitivamente pruebas de integración / aceptación, no pruebas unitarias en absoluto, y tengo partes críticas de mi aplicación que no se están probando de manera efectiva. Quiero arreglar esto lo antes posible.
Aquí está mi problema. Mi esquema es profundamente relacional y está muy orientado al tiempo, lo que le da a mi objeto modelo un alto acoplamiento interno y mucho estado. Muchos de mis métodos modelo consultan en función de los intervalos de tiempo, y tengo muchas actividades auto_now_add
en campos con marca de tiempo. Entonces, tome un método que se vea así, por ejemplo:
def summary(self, startTime=None, endTime=None):
# ... logic to assign a proper start and end time
# if none was provided, probably using datetime.now()
objects = self.related_model_set.manager_method.filter(...)
return sum(object.key_method(startTime, endTime) for object in objects)
¿Cómo se aborda probar algo como esto?
Aquí es donde estoy hasta ahora. Se me ocurre que el objetivo de la prueba de unidad debe tener un comportamiento simulado by key_method
en sus argumentos, ¿se está summary
filtrando / agregando correctamente para producir un resultado correcto?
Burlarse de datetime.now () es bastante sencillo, pero ¿cómo puedo burlarme del resto del comportamiento?
- Podría usar accesorios, pero he escuchado los pros y los contras de usar accesorios para construir mis datos (la falta de mantenimiento es una estafa que me afecta).
- También podría configurar mis datos a través del ORM, pero eso puede ser limitante, porque entonces también tengo que crear objetos relacionados. Y el ORM no le permite jugar con los
auto_now_add
campos manualmente. - Burlarse del ORM es otra opción, pero no solo es complicado burlarse de los métodos ORM profundamente anidados, sino que la lógica en el código ORM se burla de la prueba, y la burla parece hacer que la prueba realmente dependa de las partes internas y las dependencias del función bajo prueba.
Las tuercas más difíciles de descifrar parecen ser las funciones como esta, que se encuentran en algunas capas de modelos y funciones de nivel inferior y dependen mucho del tiempo, aunque estas funciones no sean muy complicadas. Mi problema general es que no importa cómo parezca dividirlo, mis pruebas se ven mucho más complejas que las funciones que están probando.
fuente
Respuestas:
Voy a seguir adelante y registrar una respuesta para lo que se me ocurrió hasta ahora.
Mi hipótesis es que para una función con acoplamiento y estado profundos, la realidad es que simplemente se necesitarán muchas líneas para controlar su contexto externo.
Así es aproximadamente mi caso de prueba, confiando en la biblioteca Mock estándar:
datetime
y subvierto losauto_now_add
tiempos para que se ajusten a una línea de tiempo fija de mi diseño. Pensé que el ORM no permitía esto, pero funciona bien.from datetime import datetime
para poder parcheardatetime.now()
solo esa función (si me burlo de toda ladatetime
clase, el ORM se adapta).object.key_method()
, con una funcionalidad simple pero bien definida que depende de los argumentos. Quiero que dependa de los argumentos, porque de lo contrario podría no saber si la lógica de la función bajo prueba está funcionando. En mi caso, simplemente devuelve el número de segundos entrestartTime
yendTime
. Lo parcheo envolviéndolo en una lambda y parcheando directamente paraobject.key_method()
usar elnew_callable
kwarg depatch
.summary
con diferentes argumentos para verificar la igualdad con los resultados calculados a mano esperados que representan el comportamiento dado del simulacrokey_method
No hace falta decir que esto es significativamente más largo y más complicado que la función en sí. Depende de la base de datos, y realmente no se siente como una prueba unitaria. Pero también está bastante desacoplado de los elementos internos de la función, solo su firma y dependencias. Así que creo que aún podría ser una prueba unitaria.
En mi aplicación, la función es bastante fundamental y está sujeta a refactorización para optimizar su rendimiento. Así que creo que el problema lo vale, a pesar de la complejidad. Pero todavía estoy abierto a mejores ideas sobre cómo abordar esto. Todo es parte de mi largo viaje hacia un estilo de desarrollo más basado en pruebas ...
fuente