Tkinter: properly pass the instance for unit test - python

I am trying to bring the Tkinter script to the testing. The application would be tested without opening the window, just simulate the button click. What I've done seems to be inappropriate, so how could I pass the instance properly? Thanks.
Here is the prototype of my code:
# importing Tkinter and math
from tkinter import *
import math
class calc:
def __init__(self,master):
"""Constructor method"""
master.title('Calculator')
master.geometry()
self.master = master
self.btn_equal = Button(self.master,text="=",width=11,height=3, fg="red", bg="light green",command=lambda:self.equals()).grid(row=4, column=4,columnspan=2)
self.e.grid(row=0,column=0,columnspan=6,pady=3)
def start_application():
root = Tk()
app = calc(root)
# print(app)
root.bind_class("Button", "<Button-1>", app.callback)
return root
if __name__ == "__main__":
start_application().mainloop()
Here is the testing code:
import unittest
from tkinter import *
from calculator2 import start_application
class TestCalculator2(unittest.TestCase):
# start the application, test, and destroy at the end of the test
async def _start_app(self):
self.app.mainloop()
def setUp(self):
self.app = start_application()
self._start_app()
def tearDown(self):
self.app.destroy()
class TestCalculation(TestCalculator2):
def test_startup(self):
title = self.app.winfo_toplevel().title()
expected = "Calculator"
self.assertEqual(title, expected)
def test_addition(self):
self.btn_equal.invoke() # _Tkinter.tkapp has no attribute, since root is not calc object

The short answer is that the Button widget calls the grid, leading to the NoneType. The correct way of instantiating a button inside a class with position should be:
self.btn_equal = Button(self.master,text="=",width=11,height=3, fg="red", bg="light green",command=lambda:self.equals())
self.btn_equal.grid(row=4, column=4,columnspan=2)
On the other hand, the correct way to instantiate the calc class mentioned above and write a successful unit test with invoke(), which clicks the button. The code won't open the GUI, and the testing script works (with warning):
class TestCalculator2(unittest.TestCase):
# start the application, test, and destroy at the end of the test
async def _start_app(self):
self.app.mainloop() # the root in Tkinter activates the mainloop()
def setUp(self):
self.app = start_application() # activate the root
self.calc = calc(self.app) # instantiate the calculator
self._start_app()
def tearDown(self):
self.app.destroy()
class TestCalculation(TestCalculator2):
def test_startup(self):
title = self.app.winfo_toplevel().title()
expected = "Calculator"
self.assertEqual(title, expected)
def test_addition(self):
self.calc.btn_AC.invoke() # click AC to clear the place
self.calc.btn_7.invoke() # click button 7
self.calc.btn_plus.invoke() # click button +
self.calc.btn_5.invoke() # click button 5
result = self.calc.btn_equal.invoke() # click button equal, get the value
self.assertEqual(result, 12)
Note that the code snippet just gives a brief idea of how unit testing works on Tkinter, the calc class provided is incomplete.

Related

Python Tkimport how to make one from several windows when importing from a module?

I am writing an application in tkinter consisting of several modules in which there are classes. Each module to a separate page of the app. As I move the buttons between the pages "next", "previous" it opens a new window for me every time. How do I make it so that each time calling pages opens in the same window?
I give draft code.
thank you for your help :D
task1.py
import tkinter as tk
from Test.modul.task1 import FirstPage1
class FirstPage0:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.geometry("100x200")
def get_second_page(self):
FirstPage1(tk.Toplevel()).get_run_first_page()
def get_button(self):
# Add buttons
tk.Button(self.root, text="Start page", command=self.get_second_page).pack()
tk.Button(self.root, text="Exit", command=self.root.destroy).pack()
def get_run_first_page(self):
# Launching the application
self.get_settings()
self.get_button()
self.root.mainloop()
if __name__ == '__main__':
first = FirstPage0(tk.Tk())
first.get_run_first_page()
task2.py
import tkinter as tk
class FirstPage1:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.geometry("100x200")
def get_second_page1(self):
from Test.task import FirstPage0
FirstPage0(tk.Toplevel()).get_run_first_page()
def get_button(self):
# Add buttons
tk.Button(self.root, text="Back", command=self.get_second_page1).pack()
tk.Button(self.root, text="Exit", command=self.root.destroy).pack()
def get_run_first_page(self):
# Launching the application
self.get_settings()
self.get_button()
self.root.mainloop()
if __name__ == '__main__':
first = FirstPage1(tk.Tk())
first.get_run_first_page()
Solution
#AdrianSz, you wanted to make the buttons not stack under each other. There are three ways to do so. One, is to keep only one button and change its command and text parameters each time when the frames change. Another would be to unpack the button not needed and pack the button needed. The third would be to pack the buttons in the root window instead of frame and change the text and command parameters. I would recommend the second method as it is easier and less prone to errors.
Code
task1.py
import tkinter as tk
from Test.modul.task1 import FirstPage1
class FirstPage0:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.geometry("100x200")
def get_second_page(self):
self.root.pg_0_btn_start.pack_forget()
self.root_pg_0_btn_exit.pack_forget()
FirstPage1(tk.Toplevel()).get_run_first_page()
def get_button(self):
# Add buttons
self.root.pg_0_btn_start = tk.Button(self.root, text="Start page",
command=self.get_second_page).pack()
self.root_pg_0_btn_exit = tk.Button(self.root, text="Exit",
command=self.root.destroy).pack()
def get_run_first_page(self):
# Launching the application
self.get_settings()
self.get_button()
self.root.mainloop()
if __name__ == '__main__':
first = FirstPage0(tk.Tk())
first.get_run_first_page()
task2.py
import tkinter as tk
class FirstPage1:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.geometry("100x200")
def get_second_page1(self):
from Test.task import FirstPage0
self.root.pg_1_btn_back.pack_forget()
self.root_pg_1_btn_exit.pack_forget()
FirstPage0(tk.Toplevel()).get_run_first_page()
def get_button(self):
# Add buttons
self.root.pg_1_btn_back = tk.Button(self.root, text="Back", command=self.get_second_page1).pack()
self.root.pg_1_btn_exit = tk.Button(self.root, text="Exit", command=self.root.destroy).pack()
def get_run_first_page(self):
# Launching the application
self.get_settings()
self.get_button()
self.root.mainloop()
if __name__ == '__main__':
first = FirstPage1(tk.Tk())
first.get_run_first_page()
Note: I couldn't test this code from my side so if there are any errors, please comment on this answer
Suggestions
Since you are using modules for these, just make them inherit a class specified in a different file and operate them both from that file. There you can use self to access the methods of a subclass because the subclasses instantiate the base class and thus the self is a object of the subclass and is passed to the base class. The type of code you used is quite confusing too. I have added code to give you your wanted output using the principles I mentioned here. Hope this helped!

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?

Is there a way to make a label show in every window for tkinter without having to write out every single line of code over and over

I am wondering if there is a way, in tkinter or using any other python module, to make it so you keep a label or any other element in every window made by just using something like a function that makes the label within the window? I've tried this:
#Modules
import tkinter as tkin
#The initializer window
class Test:
def __init__(self):
#Initializes the main window
self.start = tkin.Tk()
self.start.title('Test')
self.label_thing()
#Makes a label
def label_thing(self):
self.label1 = tkin.Label(text='Not Button')
self.label1.pack()
I don't know if I made any errors or if this isn't a thing you can do but I'd also like to refrain from having the label localized to this window and having to remake the code for every window.
Let us assume you have a button that creates windows, you would pass this window as an argument to the function that creates the label, so like:
import tkinter as tk # Fixed weird import naming
class Test:
def __init__(self):
self.start = tk.Tk()
self.start.title('Test')
self.label_thing(self.start) # Label on the main window
tk.Button(self.start,text='Click me',command=self.create_win).pack()
self.start.mainloop()
def label_thing(self, master):
self.label1 = tk.Label(master, text='Not Button')
self.label1.pack()
def create_win(self):
new = tk.Toplevel()
self.label_thing(new) # Label on the new window
if __name__ == '__main__':
Test()
As you can see, as long as you press the button, new windows are created, and all those windows have the label on them, dynamically.

Testing tkinter application

I wrote a small application using python 3 and tkinter. Testing every widget, even though there are not many of them feels daunting so I wanted to write a couple of automated tests to simplify the process. I read some other question that seemed relevant to this problem but none fit my needs. Right now I'm doing the testing in a very simple manner - I invoke the command for every widget and manually click through it to see if it works. It does make things a bit faster, but I constantly run into some problems - i.e. I can't automatically close popup windows (like showinfo) even with using libraries to simulate keyboard clicks (namely pynput). Is there an efficient approach for testing applications using tkinter?
Here is the code I use right now:
import tkinter as tkinter
import unittest
from mygui import MyGUI
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.info_button.invoke()
v.close_button.invoke()
v.btnOut.invoke()
if __name__ == "__main__":
unittest.main()
I don't know much about unittest but I found a workaround to close popup dialogs like showinfo during the tests. The idea is to use keyboard event to invoke the button of the dialog. But since the app is waiting for the user to close the popup dialog, we need to schedule in advance the keyboard event using after:
self.root.after(100, self.root.event_generate('<Return>'))
v.button.invoke()
Full example
import tkinter
from tkinter import messagebox
import unittest
class MyGUI(tkinter.Frame):
def __init__(self, master, **kw):
tkinter.Frame.__init__(self, master, **kw)
self.info_button = tkinter.Button(self, command=self.info_cmd, text='Info')
self.info_button.pack()
self.quit_button = tkinter.Button(self, command=self.quit_cmd, text='Quit')
self.quit_button.pack()
def info_cmd(self):
messagebox.showinfo('Info', master=self)
def quit_cmd(self):
confirm = messagebox.askokcancel('Quit?', master=self)
if confirm:
self.destroy()
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
self.root.bind('<Key>', lambda e: print(self.root, e.keysym))
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.pack()
self.root.update_idletasks()
# info
v.after(100, lambda: self.root.event_generate('<Return>'))
v.info_button.invoke()
# quit
def cancel():
self.root.event_generate('<Tab>')
self.root.event_generate('<Return>')
v.after(100, cancel)
v.quit_button.invoke()
self.assertTrue(v.winfo_ismapped())
v.after(100, lambda: self.root.event_generate('<Return>'))
v.quit_button.invoke()
with self.assertRaises(tkinter.TclError):
v.winfo_ismapped()
if __name__ == "__main__":
unittest.main()

Python Tkinter File Dialog event order, returning string from button click

Good afternoon all,
Rather than using the command line or hard coding a file name in Python, I'd like to have a separate class that I can use in any other Python program that allows a file to be chosen with the Tkinter file dialog box. Here is what I have so far:
import Tkinter as tk
import tkFileDialog
import tkFont
###################################################################################################
class MyFileDialog(tk.Frame):
# class level variables #######################################################################
my_form = tk.Tk() # declare form
my_button = tk.Button(my_form) # declare button
chosen_file = ""
# constructor #################################################################################
def __init__(self):
# create button event
self.my_button.configure(text = "press here", command = self.on_my_button_click)
self.my_button.pack() # add button to form
# make the button font bigger so I don't have to squint
updated_font = tkFont.Font(font = self.my_button['font'])
updated_font['size'] = int(float(updated_font['size']) * 1.6)
self.my_button.configure(font = updated_font)
###############################################################################################
def on_my_button_click(self):
self.chosen_file = tkFileDialog.askopenfilename() # get chosen file name
return
###############################################################################################
def get_chosen_file(self):
return self.chosen_file # return chosen file name
###################################################################################################
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# I need to get the chosen file here, when the file is chosen, NOT when the window is closed !!
# please help !!
print my_file_dialog.get_chosen_file() # this does not print until the window is closed !!!
The current problem with this code is (as the comment towards the end indicates) that the test code does not receive the chosen file name until the window is closed, however I need the test code to receive the file name when the file is chosen.
I should mention that in production use the test code would be in a different file altogether that would import and instantiate this class, therefore the modularity of the class should be maintained. Anybody out there have a solution? Please help !
In a nutshell, mainloop() blocks all other calls until the window is destroyed (more on this: Tkinter understanding mainloop). This is why your print statement waits until the window is closed.
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# this line will not run until the mainloop() is complete.
print my_file_dialog.get_chosen_file()
One simple solution might be to change your chosen_file variable to a tk.StringVar(), which you could then attach a callback method to.
import Tkinter as tk
import tkFileDialog
import tkFont
class MyFileDialog(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
# class variables
self.chosen_file = tk.StringVar()
# ui elements
self.my_button = tk.Button(self)
# configure button
self.my_button.configure(\
text = "press here", command = self.on_my_button_click)
# make button font bigger
# ...
# add button to frame
self.my_button.pack()
# attach a callback to chosen_file with trace
self.chosen_file.trace("w", self.callback)
# execute this method whenever chosen_file gets written
def callback(self, *args):
print("MyFileDialog: chosen_file is %s" % self.chosen_file.get())
def on_my_button_click(self):
f = tkFileDialog.askopenfilename()
# set chosen_file here to trigger callback (wherever it is)
self.chosen_file.set(f)
if __name__ == "__main__":
root = tk.Tk()
app = MyFileDialog(root).pack()
root.mainloop()
Note that you don't have to place the callback method in your MyFileDialog class: you could just as easily create the callback in another class once you have instantiated a MyFileDialog object.
class MyApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.mfd = MyFileDialog(master)
self.mfd.pack()
# attach callback to chosen_file from MyFileDialog
self.mfd.chosen_file.trace("w", self.callback)
def callback(self, *args):
print("MyApp: chosen_file is %s" % self.mfd.chosen_file.get())

Categories

Resources