代码之家  ›  专栏  ›  技术社区  ›  Wayne Werner

如何在Tkinter应用程序上运行unittest?

  •  18
  • Wayne Werner  · 技术社区  · 15 年前

    我刚开始了解 TDD ,我正在使用Tkinter图形用户界面开发一个程序。唯一的问题是一旦 .mainloop() 方法时,测试套件将挂起,直到窗口关闭。

    # 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()
    

    测试Tkinter应用程序的合适方法是什么?或者只是“不”而已?

    3 回复  |  直到 8 年前
        1
  •  3
  •   Scott    15 年前

    您可以做的一件事是在一个单独的线程中生成主循环,并使用主线程运行实际的测试;观察主循环线程的原样。在进行断言之前,确保检查了Tk窗口的状态。

    多线程处理任何代码都很困难。您可能希望将Tk程序分解成可测试的部分,而不是一次对整个程序进行单元测试(这实际上不是单元测试)。

        2
  •  10
  •   ivan_pozdeev RenanSS    8 年前

    底线:在导致UI事件的操作之后,在需要该事件影响的后续操作之前,使用下面的代码来泵送事件。


    IPython提供了一个没有线程的优雅解决方案 gui tk terminal/pt_inputhooks/tk.py .

    而不是 root.mainloop() root.dooneevent() 在循环中,检查退出条件(交互输入到达)每次迭代。这样,当IPython忙于处理命令时,even循环就不会运行。

    对于测试,没有外部事件等待,并且测试总是“忙”,因此必须在“适当的时刻”手动(或半自动)运行循环。它们是什么?

    测试表明,如果没有事件循环,可以直接更改小部件(使用 <widget>.tk.call() 但事件处理程序从不开火。因此,循环需要在事件发生时运行,并且我们需要它的效果——也就是说,在任何更改了某些内容的操作之后,在需要更改结果的操作之前。

    源于上述IPython过程的代码是:

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

    ( tkinter.Tk.dooneevent() 代表 Tcl_DoOneEvent()


    另请注意,使用此选项:

    root.update()
    root.update_idletasks()
    

    因为两个函数都不处理 全部的


    下面是一个测试用于编辑字符串值的简单弹出对话框的示例:

    class TKinterTestCase(unittest.TestCase):
        """These methods are going to be the same for every GUI test,
        so refactored them into a separate class
        """
        def setUp(self):
            self.root=tkinter.Tk()
            self.pump_events()
    
        def tearDown(self):
            if self.root:
                self.root.destroy()
                self.pump_events()
    
        def pump_events(self):
            while self.root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT):
                pass
    
    class TestViewAskText(TKinterTestCase):
        def test_enter(self):
            v = View_AskText(self.root,value=u"йцу")  # the class implementing the dialog;
                                                      # not included in the example
            self.pump_events()
            v.e.focus_set()
            v.e.insert(tkinter.END,u'кен')
            v.e.event_generate('<Return>')
            self.pump_events()
    
            self.assertRaises(tkinter.TclError, lambda: v.top.winfo_viewable())
            self.assertEqual(v.value,u'йцукен')
    
    
    # ###########################################################
    # The class being tested (normally, it's in a separate module
    # and imported at the start of the test's file)
    # ###########################################################
    
    class View_AskText(object):
        def __init__(self, master, value=u""):
            self.value=None
    
            top = self.top = tkinter.Toplevel(master)
            top.grab_set()
            self.l = ttk.Label(top, text=u"Value:")
            self.l.pack()
            self.e = ttk.Entry(top)
            self.e.pack()
            self.b = ttk.Button(top, text='Ok', command=self.save)
            self.b.pack()
    
            if value: self.e.insert(0,value)
            self.e.focus_set()
            top.bind('<Return>', self.save)
    
        def save(self, *_):
            self.value = self.e.get()
            self.top.destroy()
    
    
    if __name__ == '__main__':
        import unittest
        unittest.main()
    
        3
  •  2
  •   Ryan Ginstrom    15 年前

    有一种技术叫做monkey patching,通过它可以在运行时更改代码。

    您可以对TK类进行monkey补丁,这样mainloop实际上就不会启动程序。

    在test.py中有类似的内容(未测试!):

    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...
    

    类似于 mock 这样就不那么痛苦了。