[Python-checkins] cpython (3.3): Issue18130: Test class idlelib.configSectionNameDialog.GetCfgSectionNameDialog.

terry.reedy python-checkins at python.org
Wed Jun 5 20:41:11 CEST 2013


http://hg.python.org/cpython/rev/db4ecaf852e3
changeset:   84040:db4ecaf852e3
branch:      3.3
parent:      84019:ffdee6b36305
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Wed Jun 05 14:22:26 2013 -0400
summary:
  Issue18130: Test class idlelib.configSectionNameDialog.GetCfgSectionNameDialog.
Fix bug in existing human test and add instructions; fix two bugs in tested
code; remove redundancies, add spaces, and change two internal method names.
Add mock_tk with mocks for tkinter.Variable subclasses and tkinter.messagebox.
Use mocks in test_config_name to unittest methods that are otherwise gui-free.

files:
  Lib/idlelib/configSectionNameDialog.py    |  111 +++++----
  Lib/idlelib/idle_test/mock_tk.py          |   63 +++++
  Lib/idlelib/idle_test/test_config_name.py |   75 ++++++
  3 files changed, 198 insertions(+), 51 deletions(-)


diff --git a/Lib/idlelib/configSectionNameDialog.py b/Lib/idlelib/configSectionNameDialog.py
--- a/Lib/idlelib/configSectionNameDialog.py
+++ b/Lib/idlelib/configSectionNameDialog.py
@@ -1,97 +1,106 @@
 """
 Dialog that allows user to specify a new config file section name.
 Used to get new highlight theme and keybinding set names.
+The 'return value' for the dialog, used two placed in configDialog.py,
+is the .result attribute set in the Ok and Cancel methods.
 """
 from tkinter import *
 import tkinter.messagebox as tkMessageBox
 
 class GetCfgSectionNameDialog(Toplevel):
-    def __init__(self,parent,title,message,usedNames):
+    def __init__(self, parent, title, message, used_names):
         """
         message - string, informational message to display
-        usedNames - list, list of names already in use for validity check
+        used_names - string collection, names already in use for validity check
         """
         Toplevel.__init__(self, parent)
         self.configure(borderwidth=5)
-        self.resizable(height=FALSE,width=FALSE)
+        self.resizable(height=FALSE, width=FALSE)
         self.title(title)
         self.transient(parent)
         self.grab_set()
         self.protocol("WM_DELETE_WINDOW", self.Cancel)
         self.parent = parent
-        self.message=message
-        self.usedNames=usedNames
-        self.result=''
-        self.CreateWidgets()
-        self.withdraw() #hide while setting geometry
+        self.message = message
+        self.used_names = used_names
+        self.create_widgets()
+        self.withdraw()  #hide while setting geometry
         self.update_idletasks()
         #needs to be done here so that the winfo_reqwidth is valid
         self.messageInfo.config(width=self.frameMain.winfo_reqwidth())
-        self.geometry("+%d+%d" %
-            ((parent.winfo_rootx()+((parent.winfo_width()/2)
-                -(self.winfo_reqwidth()/2)),
-              parent.winfo_rooty()+((parent.winfo_height()/2)
-                -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
-        self.deiconify() #geometry set, unhide
+        self.geometry(
+                "+%d+%d" % (
+                parent.winfo_rootx() +
+                (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
+                parent.winfo_rooty() +
+                (parent.winfo_height()/2 - self.winfo_reqheight()/2)
+                ) )  #centre dialog over parent
+        self.deiconify()  #geometry set, unhide
         self.wait_window()
 
-    def CreateWidgets(self):
-        self.name=StringVar(self)
-        self.fontSize=StringVar(self)
-        self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
-        self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
-        self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5,
-                text=self.message)#,aspect=200)
-        entryName=Entry(self.frameMain,textvariable=self.name,width=30)
+    def create_widgets(self):
+        self.name = StringVar(self.parent)
+        self.fontSize = StringVar(self.parent)
+        self.frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
+        self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
+        self.messageInfo = Message(self.frameMain, anchor=W, justify=LEFT,
+                    padx=5, pady=5, text=self.message) #,aspect=200)
+        entryName = Entry(self.frameMain, textvariable=self.name, width=30)
         entryName.focus_set()
-        self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH)
-        entryName.pack(padx=5,pady=5)
-        frameButtons=Frame(self)
-        frameButtons.pack(side=BOTTOM,fill=X)
-        self.buttonOk = Button(frameButtons,text='Ok',
-                width=8,command=self.Ok)
-        self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
-        self.buttonCancel = Button(frameButtons,text='Cancel',
-                width=8,command=self.Cancel)
-        self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
+        self.messageInfo.pack(padx=5, pady=5) #, expand=TRUE, fill=BOTH)
+        entryName.pack(padx=5, pady=5)
 
-    def NameOk(self):
-        #simple validity check for a sensible
-        #ConfigParser file section name
-        nameOk=1
-        name=self.name.get()
-        name.strip()
+        frameButtons = Frame(self, pady=2)
+        frameButtons.pack(side=BOTTOM)
+        self.buttonOk = Button(frameButtons, text='Ok',
+                width=8, command=self.Ok)
+        self.buttonOk.pack(side=LEFT, padx=5)
+        self.buttonCancel = Button(frameButtons, text='Cancel',
+                width=8, command=self.Cancel)
+        self.buttonCancel.pack(side=RIGHT, padx=5)
+
+    def name_ok(self):
+        ''' After stripping entered name, check that it is a  sensible
+        ConfigParser file section name. Return it if it is, '' if not.
+        '''
+        name = self.name.get().strip()
         if not name: #no name specified
             tkMessageBox.showerror(title='Name Error',
                     message='No name specified.', parent=self)
-            nameOk=0
         elif len(name)>30: #name too long
             tkMessageBox.showerror(title='Name Error',
                     message='Name too long. It should be no more than '+
                     '30 characters.', parent=self)
-            nameOk=0
-        elif name in self.usedNames:
+            name = ''
+        elif name in self.used_names:
             tkMessageBox.showerror(title='Name Error',
                     message='This name is already in use.', parent=self)
-            nameOk=0
-        return nameOk
+            name = ''
+        return name
 
     def Ok(self, event=None):
-        if self.NameOk():
-            self.result=self.name.get().strip()
+        name = self.name_ok()
+        if name:
+            self.result = name
             self.destroy()
 
     def Cancel(self, event=None):
-        self.result=''
+        self.result = ''
         self.destroy()
 
 if __name__ == '__main__':
-    #test the dialog
-    root=Tk()
+    import unittest
+    unittest.main('idlelib.idle_test.test_config_name', verbosity=2, exit=False)
+    
+    # also human test the dialog
+    root = Tk()
     def run():
-        keySeq=''
         dlg=GetCfgSectionNameDialog(root,'Get Name',
-                'The information here should need to be word wrapped. Test.')
+                "After the text entered with [Ok] is stripped, <nothing>, "
+                "'abc', or more that 30 chars are errors. "
+                "Close with a valid entry (printed), [Cancel], or [X]",
+                {'abc'})
         print(dlg.result)
-    Button(root,text='Dialog',command=run).pack()
+    Message(root, text='').pack()  # will be needed for oher dialog tests
+    Button(root, text='Click to begin dialog test', command=run).pack()
     root.mainloop()
diff --git a/Lib/idlelib/idle_test/mock_tk.py b/Lib/idlelib/idle_test/mock_tk.py
new file mode 100644
--- /dev/null
+++ b/Lib/idlelib/idle_test/mock_tk.py
@@ -0,0 +1,63 @@
+"""Classes that replace tkinter gui objects used by an object being tested.
+A gui object is anything with a master or parent paramenter, which is typically
+required in spite of what the doc strings say.
+"""
+
+class Var:
+    "Use for String/Int/BooleanVar: incomplete"
+    def __init__(self, master=None, value=None, name=None):
+        self.master = master
+        self.value = value
+        self.name = name
+    def set(self, value):
+        self.value = value
+    def get(self):
+        return self.value
+
+class Mbox_func:
+    """Generic mock for messagebox functions. All have same call signature.
+    Mbox instantiates once for each function. Tester uses attributes.
+    """
+    def __init__(self):
+        self.result = None  # The return for all show funcs
+    def __call__(self, title, message, *args, **kwds):
+        # Save all args for possible examination by tester
+        self.title = title
+        self.message = message
+        self.args = args
+        self.kwds = kwds
+        return self.result  # Set by tester for ask functions
+
+class Mbox:
+    """Mock for tkinter.messagebox with an Mbox_func for each function.
+    This module was 'tkMessageBox' in 2.x; hence the 'import as' in  3.x.
+    Example usage in test_module.py for testing functios in module.py:
+    ---
+from idlelib.idle_test.mock_tk import Mbox
+import module
+
+orig_mbox = module.tkMessageBox
+showerror = Mbox.showerror  # example, for attribute access in test methods
+
+class Test(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        module.tkMessageBox = Mbox
+
+    @classmethod
+    def tearDownClass(cls):
+        module.tkMessageBox = orig_mbox
+    ---
+    When tkMessageBox functions are the only gui making calls in a method,
+    this replacement makes the method gui-free and unit-testable.
+    For 'ask' functions, set func.result return before calling method.
+    """
+    askokcancel = Mbox_func()     # True or False
+    askquestion = Mbox_func()     # 'yes' or 'no'
+    askretrycancel = Mbox_func()  # True or False
+    askyesno = Mbox_func()        # True or False
+    askyesnocancel = Mbox_func()  # True, False, or None
+    showerror = Mbox_func()    # None
+    showinfo = Mbox_func()     # None
+    showwarning = Mbox_func()  # None
diff --git a/Lib/idlelib/idle_test/test_config_name.py b/Lib/idlelib/idle_test/test_config_name.py
new file mode 100644
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_config_name.py
@@ -0,0 +1,75 @@
+"""Unit tests for idlelib.configSectionNameDialog"""
+import unittest
+from idlelib.idle_test.mock_tk import Var, Mbox
+from idlelib import configSectionNameDialog as name_dialog_module
+
+name_dialog = name_dialog_module.GetCfgSectionNameDialog
+
+class Dummy_name_dialog:
+    # Mock for testing the following methods of name_dialog
+    name_ok = name_dialog.name_ok
+    Ok = name_dialog.Ok
+    Cancel = name_dialog.Cancel
+    # Attributes, constant or variable, needed for tests
+    used_names = ['used']
+    name = Var()
+    result = None
+    destroyed = False
+    def destroy(self):
+        self.destroyed = True
+
+# name_ok calls Mbox.showerror if name is not ok
+orig_mbox = name_dialog_module.tkMessageBox
+showerror = Mbox.showerror
+
+class TestConfigName(unittest.TestCase):
+    dialog = Dummy_name_dialog()
+   
+    @classmethod
+    def setUpClass(cls):
+        name_dialog_module.tkMessageBox = Mbox
+
+    @classmethod
+    def tearDownClass(cls):
+        name_dialog_module.tkMessageBox = orig_mbox
+
+    def test_blank_name(self):
+        self.dialog.name.set(' ')
+        self.assertEqual(self.dialog.name_ok(), '')
+        self.assertEqual(showerror.title, 'Name Error')
+        self.assertIn('No', showerror.message)
+        
+    def test_used_name(self):
+        self.dialog.name.set('used')
+        self.assertEqual(self.dialog.name_ok(), '')
+        self.assertEqual(showerror.title, 'Name Error')
+        self.assertIn('use', showerror.message)
+        
+    def test_long_name(self):
+        self.dialog.name.set('good'*8)
+        self.assertEqual(self.dialog.name_ok(), '')
+        self.assertEqual(showerror.title, 'Name Error')
+        self.assertIn('too long', showerror.message)
+
+    def test_good_name(self):
+        self.dialog.name.set('  good ')
+        showerror.title = 'No Error'  # should not be called
+        self.assertEqual(self.dialog.name_ok(), 'good')
+        self.assertEqual(showerror.title, 'No Error')
+        
+    def test_ok(self):
+        self.dialog.destroyed = False
+        self.dialog.name.set('good')
+        self.dialog.Ok()
+        self.assertEqual(self.dialog.result, 'good')
+        self.assertTrue(self.dialog.destroyed)
+
+    def test_cancel(self):
+        self.dialog.destroyed = False
+        self.dialog.Cancel()
+        self.assertEqual(self.dialog.result, '')
+        self.assertTrue(self.dialog.destroyed)
+        
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2, exit=False)

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list