2010-11-03 12 views
10

Acabo de comenzar a aprender sobre TDD, y estoy desarrollando un programa usando una GUI de Tkinter. El único problema es que una vez que se llama al método .mainloop(), el banco de pruebas se cuelga hasta que se cierra la ventana.¿Cómo ejecuto unittest en una aplicación Tkinter?

Aquí es un ejemplo de mi código:

# server.py 
import Tkinter as tk 

class Server(tk.Tk): 
    def __init__(self): 
     tk.Tk.__init__(self) 
     self.mainloop() 

# test.py 
import unittest 
import server 

class ServerTestCase(unittest.TestCase): 
    def testClassSetup(self): 
     server.Server() 
     # and of course I can't call any server.whatever functions here 

if __name__ == '__main__': 
    unittest.main() 

Entonces, ¿cuál es la forma adecuada de aplicaciones de pruebas Tkinter? ¿O es simplemente 'no'?

Gracias!

Respuesta

1

Una cosa que puede hacer es generar el mainloop en un hilo separado y utilizar el hilo principal para ejecutar las pruebas reales; mira el hilo del lazo principal por así decirlo. Asegúrese de verificar el estado de la ventana Tk antes de hacer sus afirmaciones.

Multithreading cualquier código es difícil. Es posible que desee dividir su programa Tk en partes comprobables en lugar de probar todas las unidades a la vez (lo que realmente no es una prueba unitaria).

Me finalmente sugerir las pruebas al menos en el nivel de control si no más bajo para su programa, que le ayudará enormemente.

+0

Después de pensarlo unos días (y trabajar en otros proyectos) tiene mucho más sentido: tener una clase de procesamiento con la que la GUI interactúa debería facilitar la prueba, y quizás también sea más fácil de escribir. –

+0

El ciclo de eventos Tk debe ejecutarse en el hilo principal. Entonces use el hilo generado para las pruebas. – ankostis

+0

¿No es inseguro el hilo Tk? –

2

Hay una técnica llamada mono-parches, mediante el cual se cambia el código en tiempo de ejecución.

Usted podría mono-parchear la clase TK, de modo que en realidad no mainloop iniciar el programa.

Algo como esto en su test.py (no probado!):

import tk 
class FakeTk(object): 
    def mainloop(self): 
     pass 

tk.__dict__['Tk'] = FakeTk 
import server 

def test_server(): 
    s = server.Server() 
    server.mainloop() # shouldn't endless loop on you now... 

Un marco de burla como mock hace esto mucho menos doloroso.

+1

esta es la mejor ruta. Eventualmente llegué a sospechar que Tk no se cierra limpiamente entre pruebas unitarias. (Utilizo el módulo unittest de la biblioteca estándar.) Se vuelve un poco más complicado si la clase es un complejo de widgets tkinter. En TDD, tienes que crear una instancia del objeto para la prueba mientras que el mono remienda cualquier intento de iniciar Tk. Esto incluso afecta la implementación de '__str__' y' __repr__'. – lemi57ssss

+0

Sin la lógica Tk involucrada, no puede estar seguro de que sus acciones tengan el efecto deseado en la interfaz de usuario. –

+0

De acuerdo, pero eso no es una prueba unitaria. Lo probaría de otra manera, quizás con un script que automatice la interfaz de usuario. –

2

@Ryan La respuesta de Ginstrom es falsa. Esta receta de estado activo muestra cómo el simulacro evita el problema de OP del conjunto de pruebas colgantes: Robust Unittesting of Tkinter Menu Items with Mocking. El conjunto de pruebas en esta receta no incluye ninguna llamada al mainloop.

+0

El propio autor del mensaje reconoce que _ "El enfoque finalmente falló debido a efectos secundarios inconmovibles que no pudieron eliminarse en el método tearDown". _ –

+0

Además, parece que no ejecuta el bucle de eventos, por lo que los controladores de eventos no se activarán. En el [código de "mi aproximación actual"] (https://github.com/trin5tensa/pigjar/blob/master/pigjar/test/test_guidialogs.py) vinculado allí, solo prueba el estado inicial. –

+0

Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace de referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia. - [De la crítica] (/ review/low-quality-posts/18960614) –

1

Resultado final: bombee los eventos con el siguiente código después de una acción que causa un evento UI, antes de una acción posterior que necesita el efecto de ese evento.


IPython ofrece una solución elegante sin hilos que su aplicación gui tk comando mágico que se encuentra en terminal/pt_inputhooks/tk.py.

En lugar de root.mainloop(), ejecuta root.dooneevent() es un bucle, verificando la condición de salida (una entrada interactiva que llega) en cada iteración. De esta forma, el bucle par no se ejecuta cuando IPython está ocupado procesando un comando.

Con las pruebas, no hay eventos externos que esperar, y la prueba siempre está "ocupada", por lo que uno tiene que ejecutar manualmente (o semiautomáticamente) el ciclo en "momentos apropiados". ¿Qué son?

Las pruebas muestran que sin un bucle de evento, uno puede cambiar los widgets directamente (con <widget>.tk.call() y todo lo que lo envuelve), pero los controladores de eventos nunca se activan. Por lo tanto, el ciclo debe ejecutarse cada vez que ocurre un evento y necesitamos su efecto, es decir, después de cualquier operación que cambie algo.

El código, derivado del procedimiento IPython antes mencionado, sería:

def pump_events(root): 
    while root.dooneevent(_tkinter.ALL_EVENTS|_tkinter.DONT_WAIT): 
     pass 

Eso sería procesar (ejecutar manejadores de) todos los eventos pendientes, y todos los eventos que resulten directamente de estas.

(tkinter.Tk.dooneevent() delegados a Tcl_DoOneEvent().)

Cuestiones relacionadas