unittesting a tkinter application - python

I am attempting to implement unittests (while learning about them) for a Tkinter application that I wrote. For that purpose I have managed to create a very minimalistic unittest around the basic structure of my application as shown below (passing it a Tkinter instance as master):
classTest.py
#! /usr/bin/env python
from Tkinter import *
class App():
def __init__(self,master):
self.text = "foo"
self.printy()
def printy(self):
print self.text
return "test"
# Call the main app
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
test.py
#! /usr/bin/env python
from Tkinter import *
import unittest
import classTest
class testTest(unittest.TestCase):
def test(self):
a = classTest.App(Tk())
self.assertEqual(a.printy(),"test")
if __name__ == "__main__":
unittest.main()
This test returns that it ran succesful, although it does print foo twice. However, when I then attempt to run the same concept on my whole application it crashes on the __init__ of my class.
unitTest.py
#! /usr/bin/env python
import unittest
from Tkinter import *
import MassyTools
class readTest(unittest.TestCase):
def test(self):
a = MassyTools.App(Tk())
self.assertEqual(a.readData("./tests/calibrated_IBD cohort sample H6-run 1_0_E24_1.xy")[0][0],1299.11)
if __name__ == "__main__":
unittest.main()
Running this test crashes it on the fact that root is not defined (see below error).
I have been fiddling around with mocking as per this blogpost but I don't really grasp the concept yet. Below is a subset of the MassyTools.py file including the offending self.toolbar line:
class App():
def __init__(self, master):
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
Therefore, the question is if I should instantiate it differently in the unittest, modify the application to have a fake root if called in this way or something else entirely.

I found the issue in the application that I wanted to test, I declared root = Tk() in the main. This root was passed to the App class as master but in the program I made a call to root where it should have been to master. The below changes removes the initial crash, I also included a call to atexit to not have to physically close the application (as per this answer).
Revised application:
class App():
def __init__(self, master):
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, master)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
# Call the main app
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
I adjusted my unittest.py code as follows, which now finishes with an OK after i physically close the application:
#! /usr/bin/env python
import unittest
from Tkinter import *
import MassyTools
import atexit
class readTest(unittest.TestCase):
def test(self):
self.assertEqual(a.readData("./tests/calibrated_IBD cohort sample H6-run 1_0_E24_1.xy")[0][0],1299.11)
if __name__ == "__main__":
# Set up Tk(), instantiate the application and close it right away.
root = Tk()
a = MassyTools.App(root)
atexit.register(root.mainloop)
root.destroy()
# Start the actual tests
unittest.main(verbosity=2)

Related

Python - How to call a function created into a different file

I'm pretty new in Python. I'm trying to develop a GUI using a drag and drop designer called PAGE (http://page.sourceforge.net/html/index.html) based on Tkinter.
After having created a new project, I added a button to a window. The following code is autogenerated by PAGE and saved into a file called 'myGui.py':
import sys
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
import myGui_support
class Toplevel1:
def __init__(self, top=None):
...
self.StartButton = tk.Button(self.top)
self.StartButton.configure(command=myGui_support.StartButton_Callback)
...
def start_up():
myGui_support.main()
if __name__ == '__main__':
myGui_support.main()
PAGE creates also a second file called 'myGui_support.py', in which after some import statements, the following code is included:
import myGui
def main(*args):
'''Main entry point for the application.'''
global root
root = tk.Tk()
root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
# Creates a toplevel widget.
global _top1, _w1
_top1 = root
_w1 = myGui.Toplevel1(_top1)
root.mainloop()
if __name__ == '__main__':
myGui.start_up()
def StartButton_CallBack():
_w1.DebugLabel["text"] = 'START BUTTON PRESSED'
def StopButton_CallBack():
_w1.DebugLabel["text"] = 'STOP BUTTON PRESSED'
Once I run the myGui in Spyder, an Attribute error occurres:
AttributeError: module 'myGui_support' has no attribute 'StartButton_Callback'
How can it be possible? I already defined the callback function into the 'myGui_support.py'. Any suggestions?

python program using tkinter wont run due to a tclError

I'm trying to learn how to make a GUI with tkinter the conde is rather basic but I get the following error:
Exception has occurred: TclError
no display name and no $DISPLAY environment variable
File "/home/josh/Documents/VSC/python/SECOM/mainWindow.py", line 7, in __init__
self.wind = Tk()
File "/home/josh/Documents/VSC/python/SECOM/mainWindow.py", line 12, in <module>
MW = mainWindow()
When I google such error there is only answer for raspberry pi or remote servers and stuff. Im just using ubuntu(20.04) and have a conda (4.8.3) venv with python (3.8). I'm also using VSC and have the venv as interpreter in VSC. HELP :c
MainWindow.py
from tkinter import ttk
from tkinter import *
class mainWindow:
def __init__(self):
self.title = "SECOM"
self.wind = Tk()
self.wind.title(self.title)
if __name__ == '__main__':
MW = mainWindow()
window.mainloop()
You are talking a lot about windows in your code, but not much of anything is actually a window. Some of it is nothing, at all. Try This.
import tkinter as tk
class Root(tk.Tk):
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs)
#PUT YOUR APP HERE
if __name__ == '__main__':
root = Root()
root.title("SECOM")
root.mainloop()
Here are the problems with your script
from tkinter import ttk
#importing like this pollutes your namespace
from tkinter import *
class mainWindow:
def __init__(self):
#there is no reason to store a reference to the title
self.title = "SECOM"
#why are you burying your root in a class property
self.wind = Tk()
#this is why you don't need a reference to title
self.wind.title(self.title)
if __name__ == '__main__':
#sure
MW = mainWindow()
#window? window where? You buried this in MW.wind
window.mainloop()

Building a tkinter GUI module, so the main program doesn't need to import tkinter

I want to create a GUI module that I can import in my main program without having to import tkinter there, letting just the module handle everything. Here's how I imagine that it might work:
main.py
import gui as g
def update():
#Update the GUI with new Data from this main program
GUI = g.gui()
gui.after(1000, update)
gui.mainloop()
gui.py
import tkinter as tk
class viewer(tk.Frame):
#Some variables
def __init__(self, parent):
tk.Frame.__init(self, parent)
self.parent = parent
self.initialize(400, 100)
def initialize(self, width, height):
#Initialize some widgets, place them on grid, etc
def start(self):
#Do some other stuff, make a main window, configurations, etc
print('Started!')
Edit: "Don't ask for opinion"
How do I make this work?
import tkinter as tk
import gui as g
root = tk.Tk()
GUI = g.gui(root)
GUI.after(1000, update)
GUI.mainloop()
The above is what I don't want.
I used a workaround that seemed plausible to me:
main.py
import gui
GUI = gui.start()
GUI.after(1000, update)
GUI.mainloop()
gui.py
import tkinter as tk
def start():
root = tk.Tk()
run = viewer(root) # <- The class provided above
return run

why can i not call this variable from a class in a different module?

I've got this Tkinter thing going with Python 3.5. I'm trying to pass the contents of an entry-box over to a different module so I can do something else with it. I'm making the Tkinter window like this:
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Label(self,text="Testers Name").grid()
self.testers_name = tk.Entry(self,text="Testers Name").grid()
tk.Label(self,text="Report Name").grid()
self.report_name = tk.Entry(self,text="Report Name").grid()
submitButton = tk.Button(self, text="Test Selected",command=self.query_checkbuttons)
submitButton.grid()
def query_checkbuttons(self):
some_stuff_blah_blah_blah
if __name__ == "__main__":
gui = GUI()
print(gui.report_name)
gui.mainloop()
So, that works OK. At least the print call in __main__ reports None and not an error.
I have another module called pdf.py and that has a call in it that fails when I try to pull in report_name like this:
def myFirstPage(canvas, doc):
import gui
print(gui.report_name)
canvas.saveState()
canvas.setFont('Times-Bold',16)
canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title)
canvas.setFont('Times-Roman',9)
canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
canvas.restoreState()
I've tried everything I can think of. Let's see, how about:
print(gui.gui.report_name)
print(gui.report_name)
print(gui.report_name.get())
print(gui.GUI.report_name)
print(gui.GUI().report_name)
print(gui.GUI().report_name.get())
I've made gui global in and out of the __main__ call.
I've done this in various places in pdf.py:
import gui
from gui import *
from gui import gui
from gui import GUI
in conjunction with permutations of the last slew of calls...Nothing works.
I can print it find in its own module, but I can't get it to show up outside of there. How do I do this?
Not entirely sure but I guess that your issue is related to the following line
if __name__ == "__main__":
meaning that your class GUI is not instantiated as can be seen in [0]. Moving the creation of the GUI class, that is using the following code snippet
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Label(self,text="Testers Name").grid()
self.testers_name = tk.Entry(self,text="Testers Name").grid()
tk.Label(self,text="Report Name").grid()
self.report_name = tk.Entry(self,text="Report Name").grid()
submitButton = tk.Button(self, text="Test Selected",command=self.query_checkbuttons)
submitButton.grid()
def query_checkbuttons(self):
some_stuff_blah_blah_blah
gui = GUI()
print(gui.report_name)
gui.mainloop()
will at least allow you to import the variable gui from that module.
However, I strongly recommand to not instantiate the class at the end of the module because it is reinstantiated every time your import that module!
EDIT1:
I would do it as follows:
thiscode.py
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Label(self,text="Testers Name").grid()
self.testers_name = tk.Entry(self,text="Testers Name").grid()
tk.Label(self,text="Report Name").grid()
self.report_name = tk.Entry(self,text="Report Name").grid()
submitButton = tk.Button(self, text="Test Selected",command=self.query_checkbuttons)
submitButton.grid()
def query_checkbuttons(self):
some_stuff_blah_blah_blah
def start_gui():
gui = GUI()
# print(gui.report_name)
gui.mainloop()
if __name__ == "__main__":
start_gui()
and then in pdf.py
def myFirstPage(canvas, doc):
import gui
gui.start_gui() # --> manually open up the gui after import <--
# print(gui.report_name)
canvas.saveState()
canvas.setFont('Times-Bold',16)
canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title)
canvas.setFont('Times-Roman',9)
canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
canvas.restoreState()
Now let me clarify that when you call thiscode.py via python thiscode.py you will end up with exactly one GUI being opened up. This is however not the case if you import the module as can be seen in pdf.py. Hence, we directly instantiate one GUI object via function invocation which must be done manually (see line 3 with the unmistakable inline comment in pdf.py).
[0] https://docs.python.org/3/tutorial/modules.html#executing-modules-as-scripts
The whole point of the if __name__ block is to only execute its contents when that file is run directly, and not when it is imported. So if you do want to import the gui instance, you should not create it within that block.

Tkinter: Update widget from different module

I've got a Tkinter application that I'm writing in Python 3.
I've got one module which contains a class in which I create all my widgets. I'm trying to update a progress bar from a separate module that does the heavy lifting.
#GUI.py
import tkinter as tk
from tkinter.filedialog import askopenfilename, askdirectory
import tkinter.ttk as ttk
import Work_Module
progress_bar = None
class Application(tk.Frame):
def __init__(self, master=None):
master.title('CCD Creator Version 1.0')
tk.Frame.__init__(self, master, width=500)
self.pack()
self.create_widgets()
def create_widgets(self):
global progress_bar
progress_bar = tk.ttk.Progressbar(orient=tk.HORIZONTAL, length=200, mode='determinate')
progress_bar.pack(side="bottom")
self.run = tk.Button(self, text=" Run ", fg="green", command=self.do_work)
self.run.pack(side="top")
#Continue creating various widgets that don't need to be updated by other modules
def do_work(self):
Work_Module.main()
def main():
root = tk.Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
main()
Here is the module with a class that does the "heavy lifting" that I want to track the progress on.
#Work_Module.py
class Work:
def __init__(self):
#Do some intensive work
main():
for idx, job in enumerate(work_list):
Work()
import GUI
percentage_done = idx / len(job_list) * 100
GUI.progress_bar.step(percentage_done)
The problem I'm running into is that progress_bar has a value of None in the Work_Module. It's assigned an object in the GUI module, but that object isn't retained when trying to reference it from the Work_Module. I know that importing GUI in the main method is kind of strange, and I suspect that this is some sort of namespace issue or circular import problem, but I can't seem to think of a way to work around this.
One way is to inject the progress_bar into Work_Module just before you call the mainloop():
def main():
root = tk.Tk()
app = Application(master=root)
Work_Module.progress_bar = progress_bar
app.mainloop()
If you like, you can also dispense with the global aspect of the progress_bar:
def create_widgets(self):
# global progress_bar (no longer needed)
# add 'self'
self.progress_bar = tk.ttk.Progressbar(orient=tk.HORIZONTAL, length=200, mode='determinate')
self.progress_bar.pack(side="bottom")
...
def main():
root = tk.Tk()
app = Application(master=root)
Work_Module.progress_bar = self.progress_bar # add 'self'
app.mainloop()

Categories

Resources