#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jan 2013
    Posts
    76
    Rep Power
    2

    Unittesting, tkinter howto?


    Hey, I've recently started to learn myself tkinter, and made a simple calculator program.
    I know the basics of unitttesting, but I can't really get my head around how I could end up with my current program using unittests as the "driving force". And what I should unit test in a simple program such as this. Does anyone have any hints/tips to how I should do that? or improvements to my current program?

    Code:
    #!/usr/bin/env python2
    # coding=utf-8
    #This module is made to work in python2
    
    
    #GENERAL INFORMATION
    __author__ = "Leon Naley"
    __copyright__ = "Copyright (c) 2013 Leon Naley"
    __version_info__ = ["Alpha Version", "0", "1"]
    __version__ = '.'.join(__version_info__[1:])
    
    #IMPORTS AND SETS UP MODULES
    import sys
    import Tkinter as tk
    
    #DEFINES FUNCTIONS
    def fCalculate(sOperator):
        dsOperator.set(sOperator)
        try:
            fVar1 = float(dsTextField1.get())
            fVar2 = float(dsTextField2.get())
        except ValueError:
            dsResult.set("invalid input")
            return
    
        if sOperator == "+":
            fAnswer = fVar1 + fVar2
        if sOperator == "-":
            fAnswer = fVar1 - fVar2
        if sOperator == "*":
            fAnswer = fVar1 * fVar2
        if sOperator == "/":
            if fVar2 == 0:
                dsResult.set("You can't divide by zero")
                return
            fAnswer = fVar1 / fVar2
    
        if fAnswer == int(fAnswer):
            dsResult.set(str(int(fAnswer)))
        else:
            dsResult.set(str(fAnswer))
    
    #RUNS THE MAIN PROGRAM
    if __name__ == "__main__":
        #Creates a window
        owMain = tk.Tk()
        owMain.geometry("450x450+200+200")
        owMain.title("Calculator")
    
        #Creates some tkinter dynamic variables
        dsTextField1 = tk.StringVar()
        dsTextField2 = tk.StringVar()
        dsOperator = tk.StringVar()
        dsResult = tk.StringVar()
    
        #Creates widgets
        weTextField1 = tk.Entry(owMain, textvariable=dsTextField1, justify="right")
    
        wlOperator = tk.Label(textvariable=dsOperator)
        weTextField2 = tk.Entry(owMain, textvariable=dsTextField2, justify="right")
    
        wlResult = tk.Label(textvariable=dsResult)
    
        wbPlus = tk.Button(owMain, text="+", command=lambda: fCalculate("+"))
        wbMinus = tk.Button(owMain, text="-", command=lambda: fCalculate("-"))
        wbMultiplicate = tk.Button(owMain, text="*", command=lambda: fCalculate("*"))
        wbDivide = tk.Button(owMain, text="/", command=lambda: fCalculate("/"))
    
        #Packs the widgets in a grid
        tk.Label(text=" ").grid(row=0, column=0)
        weTextField1.grid(row=0, column=1, columnspan=3)
    
        wlOperator.grid(row=1, column=0)
        weTextField2.grid(row=1, column=1, columnspan=3)
    
        tk.Label(text="=").grid(row=2, column=0)
        wlResult.grid(row=2, column=1, columnspan=3)
    
        wbPlus.grid(row=4, column=0)
        wbMinus.grid(row=4, column=1)
        wbMultiplicate.grid(row=4, column=2)
        wbDivide.grid(row=4, column=3)
        #Runs the main Tkinter loop
        owMain.mainloop()
  2. #2
  3. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,709
    Rep Power
    480
    Novel calculator, the inputs remain. You can easily try various operations with them.

    I wouldn't convert the answer to int unless the inputs are also int, in which case I'd convert with int not float. Try your program to add 1e30 to 0 . OK, I actually liked the feature as implemented and struggled a bit to find why it's wrong. It is wrong.

    You want to use unittest . unit testing demands discipline. You'll want to run all the unit tests in a directory or project. You'll eventually need to write a frame work that searches for test files, causing you to enforce a file naming policy, such as
    module.py and module_test.py
    nosetests (search the internet for them) might have already done that work for you.

    You also want to automate gui testing. I haven't done it but I suppose you'd need to send artificial signals which are also bound within your program.
    widget.bind('<<artificial-event>>', callable)
    Or maybe you could use one of those programs that simulates mouse clicks. Or maybe your window system maintains an event log you can edit and play back in some controlled manner that gives you time to verify program outputs.

    I would write your calculator program without passing so much data via global variables. Give a hoot, don't pollute. Keep your name spaces clean. Parts of your program will become reusable. I use lambda to shorten programs. I don't use them namelessly. I name them.
    Code:
    #!/usr/bin/env python2
    # coding=utf-8
    
    import sys
    if sys.version[0] != '2':
        sys.stderr.write('This module is made to work in python2\n')
        sys.exit(1)
    
    import operator
    import Tkinter as tk
    
    class compute:
    
        operations = {symbol: getattr(operator, attribute) for (symbol, attribute,) in zip('+-*/','add sub mul div'.split())}
    
        def __init__(self, dsOperator,dsTextField1,dsTextField2,dsResult):
            self. dsOperator = dsOperator
            self.dsTextField1 = dsTextField1
            self.dsTextField2 = dsTextField2
            self.dsResult = dsResult
    
        add = lambda self: self('+')
        sub = lambda self: self('-')
        mul = lambda self: self('*')
        div = lambda self: self('/')
    
        def __call__(self, sOperator):
            self.dsOperator.set(sOperator)
            try:
                fVar1 = float(self.dsTextField1.get())
                fVar2 = float(self.dsTextField2.get())
            except ValueError:
                result = "invalid input"
            else:
                try:
                    fAnswer = self.__class__.operations[sOperator](fVar1, fVar2)
                except:
                    result = "Trouble!  Did you divide by zero?"
                else:
                    if fAnswer == int(fAnswer):
                        result = int(fAnswer)
                    else:
                        result = str(fAnswer)
            self.dsResult.set(str(result))
    
    #RUNS THE MAIN PROGRAM
    if __name__ == "__main__":
        #Creates a window
        owMain = tk.Tk()
        owMain.geometry("450x450+200+200")
        owMain.title("Calculator")
    
        #Creates some tkinter dynamic variables
        dsTextField1 = tk.StringVar()
        dsTextField2 = tk.StringVar()
        dsOperator = tk.StringVar()
        dsResult = tk.StringVar()
    
        #Creates widgets
        weTextField1 = tk.Entry(owMain, textvariable=dsTextField1, justify="right")
    
        wlOperator = tk.Label(textvariable=dsOperator)
        weTextField2 = tk.Entry(owMain, textvariable=dsTextField2, justify="right")
    
        wlResult = tk.Label(textvariable=dsResult)
    
        actions = compute(dsOperator,dsTextField1,dsTextField2,dsResult)
    
        wbPlus = tk.Button(owMain, text="+", command = actions.add)
        wbMinus = tk.Button(owMain, text="-", command = actions.sub)
        wbMultiplicate = tk.Button(owMain, text="*", command = actions.mul)
        wbDivide = tk.Button(owMain, text="/", command = actions.div)
    
        #Packs the widgets in a grid
        tk.Label(text=" ").grid(row=0, column=0)
        weTextField1.grid(row=0, column=1, columnspan=3)
    
        wlOperator.grid(row=1, column=0)
        weTextField2.grid(row=1, column=1, columnspan=3)
    
        tk.Label(text="=").grid(row=2, column=0)
        wlResult.grid(row=2, column=1, columnspan=3)
    
        wbPlus.grid(row=4, column=0)
        wbMinus.grid(row=4, column=1)
        wbMultiplicate.grid(row=4, column=2)
        wbDivide.grid(row=4, column=3)
        #Runs the main Tkinter loop
        owMain.mainloop()
    My version is around 10 lines longer than yours, but primarily the purpose of rewrite was to demonstrate how to localize data to make a reuseable class.
    [code]Code tags[/code] are essential for python code and Makefiles!
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jan 2013
    Posts
    76
    Rep Power
    2
    Thanks for great input I've modified my code and made it cleaner in general and abit easier to unittest aswell.
    Is this better?
    Code:
    #!/usr/bin/env python2
    #coding=utf-8
    #this module is made to work in python2
    
    
    #GENERAL INFORMATION
    __author__ = "Leon Naley"
    __copyright__ = "Copyright (c) 2013 Leon Naley"
    __version_info__ = ["Alpha Version", "0", "2"]
    __version__ = '.'.join(__version_info__[1:])
    
    #IMPORTS AND SETS UP MODULES
    import sys
    import Tkinter as tk
    
    #CHECKS PYTHON VERSION
    if sys.version[0] != '2':
        sys.stderr.write('This module is made to work in python2\n')
        sys.exit(1)
    
    
    
    #DEFINES FUNCTIONS
    def fsCalculate(sOperator, sFirstNumber, sSecondNumber):
        #Converts the two numbers into int or float,
        #   depending on what they should be
        lNumbers = []
        for sNumber in [sFirstNumber, sSecondNumber]:
            if "." in sNumber:
                lNumbers.append(float(sNumber))
            else:
                lNumbers.append(int(sNumber))
    
        #Does the calculation and checks for zero division errors
        if sOperator == "+":
            xAnswer = lNumbers[0] + lNumbers[1]
        if sOperator == "-":
            xAnswer = lNumbers[0] - lNumbers[1]
        if sOperator == "*":
            xAnswer = lNumbers[0] * lNumbers[1]
        if sOperator == "/":
            try:
                xAnswer = float(lNumbers[0]) / float(lNumbers[1])
            except ZeroDivisionError:
                return("You can't divide by zero.")
    
        #Transform the answer into an int,
        #   if it doesn't need any decimals to be accurate
        if float(xAnswer) == int(xAnswer):
            xAnswer = int(xAnswer)
    
        #Return the answer back as
        return(str(xAnswer))
    
    
    #DEFINES CLASSES
    class cCalculator():
        def __init__(self):
            #Creates a window
            self.owMain = tk.Tk()
            self.owMain.title("Calculator")
            #Sets the geometry of the window
            self.iWidth = 220
            self.iHeight = 110
            self.iLeft = (self.owMain.winfo_screenwidth() / 2) - (self.iWidth / 2)
            self.iTop = (self.owMain.winfo_screenheight() / 2) - (self.iHeight / 2)
            self.owMain.geometry(str(self.iWidth) + "x" +
                                 str(self.iHeight) + "+" +
                                 str(self.iLeft) + "+" +
                                 str(self.iTop))
    
            #Creates tkinter dynamic variables
            self.dsUpperEntry = tk.StringVar()
            self.dsLowerEntry = tk.StringVar()
            self.dsOperator = tk.StringVar()
            self.dsResultEntry = tk.StringVar()
    
            #Creates lambda functions for use with buttons
            fAdd = lambda: self.mDisplayResult("+")
            fSubtract = lambda: self.mDisplayResult("-")
            fMultiplicate = lambda: self.mDisplayResult("*")
            fDivide = lambda: self.mDisplayResult("/")
    
            #Creates widgets for row0
            self.weUpper = tk.Entry(self.owMain, textvariable=self.dsUpperEntry,
                                    justify="right")
            #Creates widgets for row1
            self.wlOperator = tk.Label(self.owMain, textvariable=self.dsOperator)
            self.weLower = tk.Entry(self.owMain, textvariable=self.dsLowerEntry,
                                    justify="right")
            #Creates widgets for row2
            self.weResult = tk.Entry(self.owMain, textvariable=self.dsResultEntry,
                                     justify="right")
            #Creates widgets for row3
            self.wbAdd = tk.Button(self.owMain, text="+", command=fAdd)
            self.wbSub = tk.Button(self.owMain, text="-", command=fSubtract)
            self.wbMul = tk.Button(self.owMain, text="*", command=fMultiplicate)
            self.wbDiv = tk.Button(self.owMain, text="/", command=fDivide)
    
            #Packs the widgets into row0
            tk.Label(text=" ").grid(row=0, column=0)
            self.weUpper.grid(row=0, column=1, columnspan=3)
            #Packs the widgets into row1
            self.wlOperator.grid(row=1, column=0)
            self.weLower.grid(row=1, column=1, columnspan=3)
            #Packs the widgets into row2
            tk.Label(text="=").grid(row=2, column=0)
            self.weResult.grid(row=2, column=1, columnspan=3)
            #Packs the widgets into row3
            self.wbAdd.grid(row=3, column=0)
            self.wbSub.grid(row=3, column=1)
            self.wbMul.grid(row=3, column=2)
            self.wbDiv.grid(row=3, column=3)
    
        def mDisplayResult(self, sOperator):
            '''This function will complete the math operation,
            and update the result entry and the operator label text.'''
            #Calculate result
            sResult = fsCalculate(sOperator, self.dsUpperEntry.get(),
                                  self.dsLowerEntry.get())
    
            #Change entry field, and operator label text
            self.dsResultEntry.set(sResult)
            self.dsOperator.set(sOperator)
    
    #RUNS THE MAIN PROGRAM
    if __name__ == "__main__":
        #Creates an object for the calculator window
        oCalculator = cCalculator()
    
        #Runs the main Tkinter loop
        oCalculator.owMain.mainloop()
    Last edited by leonnaley2; July 22nd, 2013 at 08:00 AM.
  6. #4
  7. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2013
    Posts
    1
    Rep Power
    0

    Thumbs up


    thanks

IMN logo majestic logo threadwatch logo seochat tools logo