Pruebas unitarias de modificaciones de archivos

Una tarea común en los progtwigs en los que he estado trabajando últimamente es modificar un archivo de texto de alguna manera. (Hola, estoy en Linux. Todo es un archivo. Y lo hago administrador de sistemas a gran escala).

Pero el archivo que modifica el código puede no existir en mi caja de escritorio. Y probablemente no quiero modificarlo si está en mi escritorio.

He leído acerca de las pruebas de unidad en Dive Into Python, y está bastante claro lo que quiero hacer al probar una aplicación que convierte decimales en números romanos (el ejemplo en DintoP). La prueba está muy bien autocontenida. No necesita verificar que el progtwig IMPRIMA lo correcto, solo necesita verificar que las funciones están devolviendo la salida correcta a una entrada determinada.

En mi caso, sin embargo, tenemos que probar que el progtwig está modificando su entorno correctamente. Esto es lo que he encontrado:

1) Cree el archivo “original” en una ubicación estándar, tal vez / tmp.

2) Ejecute la función que modifica el archivo, pasándole la ruta al archivo en / tmp.

3) Verifique que el archivo en / tmp fue cambiado correctamente; prueba de unidad de aprobado / reprobado en consecuencia

Esto me parece torpe. (Obtiene incluso kludgier si desea verificar que las copias de respaldo del archivo se crean correctamente, etc.) ¿Alguien ha encontrado una mejor manera?

Estás hablando de probar demasiado a la vez. Si comienza a intentar atacar un problema de prueba diciendo “Vamos a verificar que modifica su entorno correctamente”, está condenado al fracaso. Los entornos tienen docenas, tal vez incluso millones de variaciones potenciales.

En su lugar, mira las piezas (“unidades”) de tu progtwig. Por ejemplo, ¿va a tener una función que determine dónde se deben escribir los archivos? ¿Cuáles son las entradas a esa función? Tal vez una variable de entorno, tal vez algunos valores leídos de un archivo de configuración? Pruebe esa función y no haga nada que modifique el sistema de archivos. No le pases valores “realistas”, pasa valores que sean fáciles de verificar. Cree un directorio temporal, setUp con archivos en el método de setUp su prueba.

Luego prueba el código que escribe los archivos. Sólo asegúrese de que está escribiendo el contenido del archivo de contenido correcto. ¡Ni siquiera escribas en un sistema de archivos real! No necesita crear objetos de archivo “falsos” para esto, solo use los prácticos módulos StringIO Python; son implementaciones “reales” de la interfaz de “archivo”, simplemente no son las que su progtwig realmente va a escribir.

En última instancia, tendrá que probar la función de nivel superior final, todo está enganchado para la realidad real que pasa la variable de entorno real y el archivo de configuración real y pone todo junto. Pero no te preocupes por eso para empezar. Por un lado, comenzarás a aprender trucos a medida que escribas pruebas individuales para funciones más pequeñas y la creación de simulacros de prueba, falsificaciones y talones se convertirá en algo natural para ti. Por otro lado: incluso si no puede descubrir cómo probar esa llamada de una función, tendrá un nivel muy alto de confianza de que todo lo que está llamando funciona perfectamente. Además, notará que el desarrollo basado en pruebas lo obliga a hacer que sus API sean más claras y flexibles. Por ejemplo: es mucho más fácil probar algo que llama a un método open() en un objeto que proviene de un lugar abstracto, que probar algo que llama os.open en una cadena que lo pasa. El método open es flexible; se puede falsificar, se puede implementar de manera diferente, pero una cadena es una cadena y os.open no le da ningún margen para detectar qué métodos se llaman en ella.

También puede crear herramientas de prueba para facilitar las tareas repetitivas. Por ejemplo, twisted proporciona recursos para crear archivos temporales para pruebas integradas en su herramienta de prueba . No es raro que las herramientas de prueba o los proyectos más grandes con sus propias bibliotecas de prueba tengan una funcionalidad como esta.

Tienes dos niveles de prueba.

  1. Filtrado y modificación de contenido. Estas son operaciones de “bajo nivel” que en realidad no requieren E / S de archivos físicos. Estas son las pruebas, la toma de decisiones, las alternativas, etc. La “lógica” de la aplicación.

  2. Operaciones del sistema de archivos. Crea, copia, renombra, elimina, copia de seguridad. Lo sentimos, pero esas son operaciones correctas del sistema de archivos que, bueno, requieren un sistema de archivos adecuado para las pruebas.

Para este tipo de pruebas, a menudo usamos un objeto “Mock”. Puede diseñar una clase “FileSystemOperations” que incorpora las diversas operaciones del sistema de archivos. Usted prueba esto para asegurarse de que hace lectura básica, escritura, copia, cambio de nombre, etc. No hay lógica real en esto. Solo métodos que invocan las operaciones del sistema de archivos.

A continuación, puede crear un MockFileSystem que simula las diferentes operaciones. Puedes usar este objeto simulado para probar tus otras clases.

En algunos casos, todas las operaciones del sistema de archivos se encuentran en el módulo os. Si ese es el caso, puede crear un módulo MockOS con una versión simulada de las operaciones que realmente utiliza.

Coloque su módulo MockOS en PYTHONPATH y podrá ocultar el módulo del sistema operativo real.

Para las operaciones de producción, utiliza sus clases “lógicas” bien probadas más su clase FileSystemOperations (o el módulo del sistema operativo real).

Para los lectores posteriores que solo quieren una manera de probar que el código escrito en archivos funciona correctamente, aquí hay un “fake_open” que parchea la versión abierta de un módulo para usar StringIO. fake_open devuelve un dictado de archivos abiertos que pueden examinarse en una prueba de unidad o doctest, todo sin necesidad de un sistema de archivos real.

 def fake_open(module): """Patch module's `open` builtin so that it returns StringIOs instead of creating real files, which is useful for testing. Returns a dict that maps opened file names to StringIO objects.""" from contextlib import closing from StringIO import StringIO streams = {} def fakeopen(filename,mode): stream = StringIO() stream.close = lambda: None streams[filename] = stream return closing(stream) module.open = fakeopen return streams 

Cuando toco archivos en mi código, tiendo a preferir burlarme de la lectura y escritura reales del archivo … para poder dar a mis clases el contenido exacto que quiero en la prueba y luego afirmar que la prueba está escribiendo la Contenidos que espero.

He hecho esto en Java, y me imagino que es bastante simple en Python … pero puede requerir diseñar tus clases / funciones de tal manera que sea FÁCIL simular el uso de un archivo real.

Para esto, puede intentar pasar secuencias y luego simplemente pasar una secuencia de entrada / salida de cadena simple que no se escribirá en un archivo, o tener una función que haga la “escritura de esta cadena en un archivo” o “lea esto cadena de un archivo “, y luego reemplace esa función en sus pruebas.

Creo que estás en el camino correcto. Dependiendo de lo que necesite hacer, chroot puede ayudarlo a configurar un entorno para sus escrituras que “parezca” real, pero no lo es.

Si eso no funciona, entonces podría escribir sus scripts para tomar una ruta ‘raíz’ como argumento.

En una ejecución de producción, la ruta raíz es simplemente /. Para las pruebas, crea un entorno sombra en / tmp / prueba y luego ejecuta sus scripts con una ruta raíz de / tmp / prueba.

Es posible que desee configurar la prueba para que se ejecute dentro de una jaula chroot, para tener todo el entorno que la prueba necesita, incluso si las rutas y las ubicaciones de los archivos están codificadas en el código [no es realmente una buena práctica, pero a veces uno obtiene el archivo ubicaciones de otros lugares …] y luego verifique los resultados a través del código de salida.