pyunit-1.4.1/0040775000076400007640000000000007335002407012121 5ustar stevestevepyunit-1.4.1/unittestgui.py0100775000076400007640000003614107264261750015075 0ustar stevesteve#!/usr/bin/env python """ GUI framework and application for use with Python unit testing framework. Execute tests written using the framework provided by the 'unittest' module. Further information is available in the bundled documentation, and from http://pyunit.sourceforge.net/ Copyright (c) 1999, 2000, 2001 Steve Purcell This module is free software, and you may redistribute it and/or modify it under the same terms as Python itself, so long as this copyright message and disclaimer are retained in their original form. IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. """ __author__ = "Steve Purcell (stephen_purcell@yahoo.com)" __version__ = "$Revision: 1.7 $"[11:-2] import unittest import sys import Tkinter import tkMessageBox import traceback import string tk = Tkinter # Alternative to the messy 'from Tkinter import *' often seen ############################################################################## # GUI framework classes ############################################################################## class BaseGUITestRunner: """Subclass this class to create a GUI TestRunner that uses a specific windowing toolkit. The class takes care of running tests in the correct manner, and making callbacks to the derived class to obtain information or signal that events have occurred. """ def __init__(self, *args, **kwargs): self.currentResult = None self.running = 0 self.__rollbackImporter = None apply(self.initGUI, args, kwargs) def getSelectedTestName(self): "Override to return the name of the test selected to be run" pass def errorDialog(self, title, message): "Override to display an error arising from GUI usage" pass def runClicked(self): "To be called in response to user choosing to run a test" if self.running: return testName = self.getSelectedTestName() if not testName: self.errorDialog("Test name entry", "You must enter a test name") return if self.__rollbackImporter: self.__rollbackImporter.rollbackImports() self.__rollbackImporter = RollbackImporter() try: test = unittest.defaultTestLoader.loadTestsFromName(testName) except: exc_type, exc_value, exc_tb = sys.exc_info() apply(traceback.print_exception,sys.exc_info()) self.errorDialog("Unable to run test '%s'" % testName, "Error loading specified test: %s, %s" % \ (exc_type, exc_value)) return self.currentResult = GUITestResult(self) self.totalTests = test.countTestCases() self.running = 1 self.notifyRunning() test.run(self.currentResult) self.running = 0 self.notifyStopped() def stopClicked(self): "To be called in response to user stopping the running of a test" if self.currentResult: self.currentResult.stop() # Required callbacks def notifyRunning(self): "Override to set GUI in 'running' mode, enabling 'stop' button etc." pass def notifyStopped(self): "Override to set GUI in 'stopped' mode, enabling 'run' button etc." pass def notifyTestFailed(self, test, err): "Override to indicate that a test has just failed" pass def notifyTestErrored(self, test, err): "Override to indicate that a test has just errored" pass def notifyTestStarted(self, test): "Override to indicate that a test is about to run" pass def notifyTestFinished(self, test): """Override to indicate that a test has finished (it may already have failed or errored)""" pass class GUITestResult(unittest.TestResult): """A TestResult that makes callbacks to its associated GUI TestRunner. Used by BaseGUITestRunner. Need not be created directly. """ def __init__(self, callback): unittest.TestResult.__init__(self) self.callback = callback def addError(self, test, err): unittest.TestResult.addError(self, test, err) self.callback.notifyTestErrored(test, err) def addFailure(self, test, err): unittest.TestResult.addFailure(self, test, err) self.callback.notifyTestFailed(test, err) def stopTest(self, test): unittest.TestResult.stopTest(self, test) self.callback.notifyTestFinished(test) def startTest(self, test): unittest.TestResult.startTest(self, test) self.callback.notifyTestStarted(test) class RollbackImporter: """This tricky little class is used to make sure that modules under test will be reloaded the next time they are imported. """ def __init__(self): self.previousModules = sys.modules.copy() def rollbackImports(self): for modname in sys.modules.keys(): if not self.previousModules.has_key(modname): # Force reload when modname next imported del(sys.modules[modname]) ############################################################################## # Tkinter GUI ############################################################################## _ABOUT_TEXT="""\ PyUnit unit testing framework. For more information, visit http://pyunit.sourceforge.net/ Copyright (c) 2000 Steve Purcell """ _HELP_TEXT="""\ Enter the name of a callable object which, when called, will return a \ TestCase or TestSuite. Click 'start', and the test thus produced will be run. Double click on an error in the listbox to see more information about it,\ including the stack trace. For more information, visit http://pyunit.sourceforge.net/ or see the bundled documentation """ class TkTestRunner(BaseGUITestRunner): """An implementation of BaseGUITestRunner using Tkinter. """ def initGUI(self, root, initialTestName): """Set up the GUI inside the given root window. The test name entry field will be pre-filled with the given initialTestName. """ self.root = root # Set up values that will be tied to widgets self.suiteNameVar = tk.StringVar() self.suiteNameVar.set(initialTestName) self.statusVar = tk.StringVar() self.statusVar.set("Idle") self.runCountVar = tk.IntVar() self.failCountVar = tk.IntVar() self.errorCountVar = tk.IntVar() self.remainingCountVar = tk.IntVar() self.top = tk.Frame() self.top.pack(fill=tk.BOTH, expand=1) self.createWidgets() def createWidgets(self): """Creates and packs the various widgets. Why is it that GUI code always ends up looking a mess, despite all the best intentions to keep it tidy? Answers on a postcard, please. """ # Status bar statusFrame = tk.Frame(self.top, relief=tk.SUNKEN, borderwidth=2) statusFrame.pack(anchor=tk.SW, fill=tk.X, side=tk.BOTTOM) tk.Label(statusFrame, textvariable=self.statusVar).pack(side=tk.LEFT) # Area to enter name of test to run leftFrame = tk.Frame(self.top, borderwidth=3) leftFrame.pack(fill=tk.BOTH, side=tk.LEFT, anchor=tk.NW, expand=1) suiteNameFrame = tk.Frame(leftFrame, borderwidth=3) suiteNameFrame.pack(fill=tk.X) tk.Label(suiteNameFrame, text="Enter test name:").pack(side=tk.LEFT) e = tk.Entry(suiteNameFrame, textvariable=self.suiteNameVar, width=25) e.pack(side=tk.LEFT, fill=tk.X, expand=1) e.focus_set() e.bind('', lambda e, self=self: self.runClicked()) # Progress bar progressFrame = tk.Frame(leftFrame, relief=tk.GROOVE, borderwidth=2) progressFrame.pack(fill=tk.X, expand=0, anchor=tk.NW) tk.Label(progressFrame, text="Progress:").pack(anchor=tk.W) self.progressBar = ProgressBar(progressFrame, relief=tk.SUNKEN, borderwidth=2) self.progressBar.pack(fill=tk.X, expand=1) # Area with buttons to start/stop tests and quit buttonFrame = tk.Frame(self.top, borderwidth=3) buttonFrame.pack(side=tk.LEFT, anchor=tk.NW, fill=tk.Y) self.stopGoButton = tk.Button(buttonFrame, text="Start", command=self.runClicked) self.stopGoButton.pack(fill=tk.X) tk.Button(buttonFrame, text="Close", command=self.top.quit).pack(side=tk.BOTTOM, fill=tk.X) tk.Button(buttonFrame, text="About", command=self.showAboutDialog).pack(side=tk.BOTTOM, fill=tk.X) tk.Button(buttonFrame, text="Help", command=self.showHelpDialog).pack(side=tk.BOTTOM, fill=tk.X) # Area with labels reporting results for label, var in (('Run:', self.runCountVar), ('Failures:', self.failCountVar), ('Errors:', self.errorCountVar), ('Remaining:', self.remainingCountVar)): tk.Label(progressFrame, text=label).pack(side=tk.LEFT) tk.Label(progressFrame, textvariable=var, foreground="blue").pack(side=tk.LEFT, fill=tk.X, expand=1, anchor=tk.W) # List box showing errors and failures tk.Label(leftFrame, text="Failures and errors:").pack(anchor=tk.W) listFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2) listFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1) self.errorListbox = tk.Listbox(listFrame, foreground='red', selectmode=tk.SINGLE, selectborderwidth=0) self.errorListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, anchor=tk.NW) listScroll = tk.Scrollbar(listFrame, command=self.errorListbox.yview) listScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N) self.errorListbox.bind("", lambda e, self=self: self.showSelectedError()) self.errorListbox.configure(yscrollcommand=listScroll.set) def getSelectedTestName(self): return self.suiteNameVar.get() def errorDialog(self, title, message): tkMessageBox.showerror(parent=self.root, title=title, message=message) def notifyRunning(self): self.runCountVar.set(0) self.failCountVar.set(0) self.errorCountVar.set(0) self.remainingCountVar.set(self.totalTests) self.errorInfo = [] while self.errorListbox.size(): self.errorListbox.delete(0) #Stopping seems not to work, so simply disable the start button #self.stopGoButton.config(command=self.stopClicked, text="Stop") self.stopGoButton.config(state=tk.DISABLED) self.progressBar.setProgressFraction(0.0) self.top.update_idletasks() def notifyStopped(self): self.stopGoButton.config(state=tk.ACTIVE) #self.stopGoButton.config(command=self.runClicked, text="Start") self.statusVar.set("Idle") def notifyTestStarted(self, test): self.statusVar.set(str(test)) self.top.update_idletasks() def notifyTestFailed(self, test, err): self.failCountVar.set(1 + self.failCountVar.get()) self.errorListbox.insert(tk.END, "Failure: %s" % test) self.errorInfo.append((test,err)) def notifyTestErrored(self, test, err): self.errorCountVar.set(1 + self.errorCountVar.get()) self.errorListbox.insert(tk.END, "Error: %s" % test) self.errorInfo.append((test,err)) def notifyTestFinished(self, test): self.remainingCountVar.set(self.remainingCountVar.get() - 1) self.runCountVar.set(1 + self.runCountVar.get()) fractionDone = float(self.runCountVar.get())/float(self.totalTests) fillColor = len(self.errorInfo) and "red" or "green" self.progressBar.setProgressFraction(fractionDone, fillColor) def showAboutDialog(self): tkMessageBox.showinfo(parent=self.root, title="About PyUnit", message=_ABOUT_TEXT) def showHelpDialog(self): tkMessageBox.showinfo(parent=self.root, title="PyUnit help", message=_HELP_TEXT) def showSelectedError(self): selection = self.errorListbox.curselection() if not selection: return selected = int(selection[0]) txt = self.errorListbox.get(selected) window = tk.Toplevel(self.root) window.title(txt) window.protocol('WM_DELETE_WINDOW', window.quit) test, error = self.errorInfo[selected] tk.Label(window, text=str(test), foreground="red", justify=tk.LEFT).pack(anchor=tk.W) tracebackLines = apply(traceback.format_exception, error + (10,)) tracebackText = string.join(tracebackLines,'') tk.Label(window, text=tracebackText, justify=tk.LEFT).pack() tk.Button(window, text="Close", command=window.quit).pack(side=tk.BOTTOM) window.bind('', lambda e, w=window: w.quit()) window.mainloop() window.destroy() class ProgressBar(tk.Frame): """A simple progress bar that shows a percentage progress in the given colour.""" def __init__(self, *args, **kwargs): apply(tk.Frame.__init__, (self,) + args, kwargs) self.canvas = tk.Canvas(self, height='20', width='60', background='white', borderwidth=3) self.canvas.pack(fill=tk.X, expand=1) self.rect = self.text = None self.canvas.bind('', self.paint) self.setProgressFraction(0.0) def setProgressFraction(self, fraction, color='blue'): self.fraction = fraction self.color = color self.paint() self.canvas.update_idletasks() def paint(self, *args): totalWidth = self.canvas.winfo_width() width = int(self.fraction * float(totalWidth)) height = self.canvas.winfo_height() if self.rect is not None: self.canvas.delete(self.rect) if self.text is not None: self.canvas.delete(self.text) self.rect = self.canvas.create_rectangle(0, 0, width, height, fill=self.color) percentString = "%3.0f%%" % (100.0 * self.fraction) self.text = self.canvas.create_text(totalWidth/2, height/2, anchor=tk.CENTER, text=percentString) def main(initialTestName=""): root = tk.Tk() root.title("PyUnit") runner = TkTestRunner(root, initialTestName) root.protocol('WM_DELETE_WINDOW', root.quit) root.mainloop() if __name__ == '__main__': import sys if len(sys.argv) == 2: main(sys.argv[1]) else: main() pyunit-1.4.1/setup.py0100664000076400007640000000073207335002301013623 0ustar stevesteve # see http://software-carpentry.codesourcery.com/entries/build/Distutils/Distutils.html for more information about this file from distutils.core import setup setup ( name = "PyUnit", version = "1.4.1", description = "PyUnit - a unit testing framework for Python", author = "Steve Purcell", author_email = "stephen_purcell@yahoo.com", url = "http://pyunit.sourceforge.net/", py_modules = ['unittest', 'unittestgui'] ) pyunit-1.4.1/unittest.py0100775000076400007640000006050207334162244014362 0ustar stevesteve#!/usr/bin/env python ''' Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's Smalltalk testing framework. This module contains the core framework classes that form the basis of specific test cases and suites (TestCase, TestSuite etc.), and also a text-based utility class for running the tests and reporting the results (TextTestRunner). Simple usage: import unittest class IntegerArithmenticTestCase(unittest.TestCase): def testAdd(self): ## test method names begin 'test*' self.assertEquals((1 + 2), 3) self.assertEquals(0 + 1, 1) def testMultiply(self); self.assertEquals((0 * 10), 0) self.assertEquals((5 * 8), 40) if __name__ == '__main__': unittest.main() Further information is available in the bundled documentation, and from http://pyunit.sourceforge.net/ Copyright (c) 1999, 2000, 2001 Steve Purcell This module is free software, and you may redistribute it and/or modify it under the same terms as Python itself, so long as this copyright message and disclaimer are retained in their original form. IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. ''' __author__ = "Steve Purcell" __email__ = "stephen_purcell at yahoo dot com" __version__ = "$Revision: 1.40 $"[11:-2] import time import sys import traceback import string import os import types ############################################################################## # Test framework core ############################################################################## class TestResult: """Holder for test result information. Test results are automatically managed by the TestCase and TestSuite classes, and do not need to be explicitly manipulated by writers of tests. Each instance holds the total number of tests run, and collections of failures and errors that occurred among those test runs. The collections contain tuples of (testcase, exceptioninfo), where exceptioninfo is a tuple of values as returned by sys.exc_info(). """ def __init__(self): self.failures = [] self.errors = [] self.testsRun = 0 self.shouldStop = 0 def startTest(self, test): "Called when the given test is about to be run" self.testsRun = self.testsRun + 1 def stopTest(self, test): "Called when the given test has been run" pass def addError(self, test, err): "Called when an error has occurred" self.errors.append((test, err)) def addFailure(self, test, err): "Called when a failure has occurred" self.failures.append((test, err)) def addSuccess(self, test): "Called when a test has completed successfully" pass def wasSuccessful(self): "Tells whether or not this result was a success" return len(self.failures) == len(self.errors) == 0 def stop(self): "Indicates that the tests should be aborted" self.shouldStop = 1 def __repr__(self): return "<%s run=%i errors=%i failures=%i>" % \ (self.__class__, self.testsRun, len(self.errors), len(self.failures)) class TestCase: """A class whose instances are single test cases. By default, the test code itself should be placed in a method named 'runTest'. If the fixture may be used for many test cases, create as many test methods as are needed. When instantiating such a TestCase subclass, specify in the constructor arguments the name of the test method that the instance is to execute. Test authors should subclass TestCase for their own tests. Construction and deconstruction of the test's environment ('fixture') can be implemented by overriding the 'setUp' and 'tearDown' methods respectively. If it is necessary to override the __init__ method, the base class __init__ method must always be called. It is important that subclasses should not change the signature of their __init__ method, since instances of the classes are instantiated automatically by parts of the framework in order to be run. """ # This attribute determines which exception will be raised when # the instance's assertion methods fail; test methods raising this # exception will be deemed to have 'failed' rather than 'errored' failureException = AssertionError def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name. """ try: self.__testMethodName = methodName testMethod = getattr(self, methodName) self.__testMethodDoc = testMethod.__doc__ except AttributeError: raise ValueError, "no such test method in %s: %s" % \ (self.__class__, methodName) def setUp(self): "Hook method for setting up the test fixture before exercising it." pass def tearDown(self): "Hook method for deconstructing the test fixture after testing it." pass def countTestCases(self): return 1 def defaultTestResult(self): return TestResult() def shortDescription(self): """Returns a one-line description of the test, or None if no description has been provided. The default implementation of this method returns the first line of the specified test method's docstring. """ doc = self.__testMethodDoc return doc and string.strip(string.split(doc, "\n")[0]) or None def id(self): return "%s.%s" % (self.__class__, self.__testMethodName) def __str__(self): return "%s (%s)" % (self.__testMethodName, self.__class__) def __repr__(self): return "<%s testMethod=%s>" % \ (self.__class__, self.__testMethodName) def run(self, result=None): return self(result) def __call__(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) testMethod = getattr(self, self.__testMethodName) try: try: self.setUp() except: result.addError(self,self.__exc_info()) return ok = 0 try: testMethod() ok = 1 except self.failureException, e: result.addFailure(self,self.__exc_info()) except: result.addError(self,self.__exc_info()) try: self.tearDown() except: result.addError(self,self.__exc_info()) ok = 0 if ok: result.addSuccess(self) finally: result.stopTest(self) def debug(self): """Run the test without collecting errors in a TestResult""" self.setUp() getattr(self, self.__testMethodName)() self.tearDown() def __exc_info(self): """Return a version of sys.exc_info() with the traceback frame minimised; usually the top level of the traceback frame is not needed. """ exctype, excvalue, tb = sys.exc_info() if sys.platform[:4] == 'java': ## tracebacks look different in Jython return (exctype, excvalue, tb) newtb = tb.tb_next if newtb is None: return (exctype, excvalue, tb) return (exctype, excvalue, newtb) def fail(self, msg=None): """Fail immediately, with the given message.""" raise self.failureException, msg def failIf(self, expr, msg=None): "Fail the test if the expression is true." if expr: raise self.failureException, msg def failUnless(self, expr, msg=None): """Fail the test unless the expression is true.""" if not expr: raise self.failureException, msg def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): """Fail unless an exception of class excClass is thrown by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is thrown, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected exception. """ try: apply(callableObj, args, kwargs) except excClass: return else: if hasattr(excClass,'__name__'): excName = excClass.__name__ else: excName = str(excClass) raise self.failureException, excName def failUnlessEqual(self, first, second, msg=None): """Fail if the two objects are unequal as determined by the '!=' operator. """ if first != second: raise self.failureException, (msg or '%s != %s' % (first, second)) def failIfEqual(self, first, second, msg=None): """Fail if the two objects are equal as determined by the '==' operator. """ if first == second: raise self.failureException, (msg or '%s == %s' % (first, second)) assertEqual = assertEquals = failUnlessEqual assertNotEqual = assertNotEquals = failIfEqual assertRaises = failUnlessRaises assert_ = failUnless class TestSuite: """A test suite is a composite test consisting of a number of TestCases. For use, create an instance of TestSuite, then add test case instances. When all tests have been added, the suite can be passed to a test runner, such as TextTestRunner. It will run the individual test cases in the order in which they were added, aggregating the results. When subclassing, do not forget to call the base class constructor. """ def __init__(self, tests=()): self._tests = [] self.addTests(tests) def __repr__(self): return "<%s tests=%s>" % (self.__class__, self._tests) __str__ = __repr__ def countTestCases(self): cases = 0 for test in self._tests: cases = cases + test.countTestCases() return cases def addTest(self, test): self._tests.append(test) def addTests(self, tests): for test in tests: self.addTest(test) def run(self, result): return self(result) def __call__(self, result): for test in self._tests: if result.shouldStop: break test(result) return result def debug(self): """Run the tests without collecting errors in a TestResult""" for test in self._tests: test.debug() class FunctionTestCase(TestCase): """A test case that wraps a test function. This is useful for slipping pre-existing test functions into the PyUnit framework. Optionally, set-up and tidy-up functions can be supplied. As with TestCase, the tidy-up ('tearDown') function will always be called if the set-up ('setUp') function ran successfully. """ def __init__(self, testFunc, setUp=None, tearDown=None, description=None): TestCase.__init__(self) self.__setUpFunc = setUp self.__tearDownFunc = tearDown self.__testFunc = testFunc self.__description = description def setUp(self): if self.__setUpFunc is not None: self.__setUpFunc() def tearDown(self): if self.__tearDownFunc is not None: self.__tearDownFunc() def runTest(self): self.__testFunc() def id(self): return self.__testFunc.__name__ def __str__(self): return "%s (%s)" % (self.__class__, self.__testFunc.__name__) def __repr__(self): return "<%s testFunc=%s>" % (self.__class__, self.__testFunc) def shortDescription(self): if self.__description is not None: return self.__description doc = self.__testFunc.__doc__ return doc and string.strip(string.split(doc, "\n")[0]) or None ############################################################################## # Locating and loading tests ############################################################################## class TestLoader: """This class is responsible for loading tests according to various criteria and returning them wrapped in a Test """ testMethodPrefix = 'test' sortTestMethodsUsing = cmp suiteClass = TestSuite def loadTestsFromTestCase(self, testCaseClass): """Return a suite of all tests cases contained in testCaseClass""" return self.suiteClass(map(testCaseClass, self.getTestCaseNames(testCaseClass))) def loadTestsFromModule(self, module): """Return a suite of all tests cases contained in the given module""" tests = [] for name in dir(module): obj = getattr(module, name) if type(obj) == types.ClassType and issubclass(obj, TestCase): tests.append(self.loadTestsFromTestCase(obj)) return self.suiteClass(tests) def loadTestsFromName(self, name, module=None): """Return a suite of all tests cases given a string specifier. The name may resolve either to a module, a test case class, a test method within a test case class, or a callable object which returns a TestCase or TestSuite instance. The method optionally resolves the names relative to a given module. """ parts = string.split(name, '.') if module is None: if not parts: raise ValueError, "incomplete test name: %s" % name else: parts_copy = parts[:] while parts_copy: try: module = __import__(string.join(parts_copy,'.')) break except ImportError: del parts_copy[-1] if not parts_copy: raise parts = parts[1:] obj = module for part in parts: obj = getattr(obj, part) import unittest if type(obj) == types.ModuleType: return self.loadTestsFromModule(obj) elif type(obj) == types.ClassType and issubclass(obj, unittest.TestCase): return self.loadTestsFromTestCase(obj) elif type(obj) == types.UnboundMethodType: return obj.im_class(obj.__name__) elif callable(obj): test = obj() if not isinstance(test, unittest.TestCase) and \ not isinstance(test, unittest.TestSuite): raise ValueError, \ "calling %s returned %s, not a test" % (obj,test) return test else: raise ValueError, "don't know how to make test from: %s" % obj def loadTestsFromNames(self, names, module=None): """Return a suite of all tests cases found using the given sequence of string specifiers. See 'loadTestsFromName()'. """ suites = [] for name in names: suites.append(self.loadTestsFromName(name, module)) return self.suiteClass(suites) def getTestCaseNames(self, testCaseClass): """Return a sorted sequence of method names found within testCaseClass """ testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p, dir(testCaseClass)) for baseclass in testCaseClass.__bases__: for testFnName in self.getTestCaseNames(baseclass): if testFnName not in testFnNames: # handle overridden methods testFnNames.append(testFnName) if self.sortTestMethodsUsing: testFnNames.sort(self.sortTestMethodsUsing) return testFnNames defaultTestLoader = TestLoader() ############################################################################## # Patches for old functions: these functions should be considered obsolete ############################################################################## def _makeLoader(prefix, sortUsing, suiteClass=None): loader = TestLoader() loader.sortTestMethodsUsing = sortUsing loader.testMethodPrefix = prefix if suiteClass: loader.suiteClass = suiteClass return loader def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) ############################################################################## # Text UI ############################################################################## class _WritelnDecorator: """Used to decorate file-like objects with a handy 'writeln' method""" def __init__(self,stream): self.stream = stream def __getattr__(self, attr): return getattr(self.stream,attr) def writeln(self, *args): if args: apply(self.write, args) self.write('\n') # text-mode streams translate to \r\n if needed class _TextTestResult(TestResult): """A test result class that can print formatted text results to a stream. Used by TextTestRunner. """ separator1 = '=' * 70 separator2 = '-' * 70 def __init__(self, stream, descriptions, verbosity): TestResult.__init__(self) self.stream = stream self.showAll = verbosity > 1 self.dots = verbosity == 1 self.descriptions = descriptions def getDescription(self, test): if self.descriptions: return test.shortDescription() or str(test) else: return str(test) def startTest(self, test): TestResult.startTest(self, test) if self.showAll: self.stream.write(self.getDescription(test)) self.stream.write(" ... ") def addSuccess(self, test): TestResult.addSuccess(self, test) if self.showAll: self.stream.writeln("ok") elif self.dots: self.stream.write('.') def addError(self, test, err): TestResult.addError(self, test, err) if self.showAll: self.stream.writeln("ERROR") elif self.dots: self.stream.write('E') if err[0] is KeyboardInterrupt: self.shouldStop = 1 def addFailure(self, test, err): TestResult.addFailure(self, test, err) if self.showAll: self.stream.writeln("FAIL") elif self.dots: self.stream.write('F') def printErrors(self): if self.dots or self.showAll: self.stream.writeln() self.printErrorList('ERROR', self.errors) self.printErrorList('FAIL', self.failures) def printErrorList(self, flavour, errors): for test, err in errors: self.stream.writeln(self.separator1) self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) self.stream.writeln(self.separator2) for line in apply(traceback.format_exception, err): for l in string.split(line,"\n")[:-1]: self.stream.writeln("%s" % l) class TextTestRunner: """A test runner class that displays results in textual form. It prints out the names of tests as they are run, errors as they occur, and a summary of the results at the end of the test run. """ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): self.stream = _WritelnDecorator(stream) self.descriptions = descriptions self.verbosity = verbosity def _makeResult(self): return _TextTestResult(self.stream, self.descriptions, self.verbosity) def run(self, test): "Run the given test case or test suite." result = self._makeResult() startTime = time.time() test(result) stopTime = time.time() timeTaken = float(stopTime - startTime) result.printErrors() self.stream.writeln(result.separator2) run = result.testsRun self.stream.writeln("Ran %d test%s in %.3fs" % (run, run == 1 and "" or "s", timeTaken)) self.stream.writeln() if not result.wasSuccessful(): self.stream.write("FAILED (") failed, errored = map(len, (result.failures, result.errors)) if failed: self.stream.write("failures=%d" % failed) if errored: if failed: self.stream.write(", ") self.stream.write("errors=%d" % errored) self.stream.writeln(")") else: self.stream.writeln("OK") return result ############################################################################## # Facilities for running tests from the command line ############################################################################## class TestProgram: """A command-line program that runs a set of tests; this is primarily for making test modules conveniently executable. """ USAGE = """\ Usage: %(progName)s [options] [test] [...] Options: -h, --help Show this message -v, --verbose Verbose output -q, --quiet Minimal output Examples: %(progName)s - run default set of tests %(progName)s MyTestSuite - run suite 'MyTestSuite' %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething %(progName)s MyTestCase - run all 'test*' test methods in MyTestCase """ def __init__(self, module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=defaultTestLoader): if type(module) == type(''): self.module = __import__(module) for part in string.split(module,'.')[1:]: self.module = getattr(self.module, part) else: self.module = module if argv is None: argv = sys.argv self.verbosity = 1 self.defaultTest = defaultTest self.testRunner = testRunner self.testLoader = testLoader self.progName = os.path.basename(argv[0]) self.parseArgs(argv) self.runTests() def usageExit(self, msg=None): if msg: print msg print self.USAGE % self.__dict__ sys.exit(2) def parseArgs(self, argv): import getopt try: options, args = getopt.getopt(argv[1:], 'hHvq', ['help','verbose','quiet']) for opt, value in options: if opt in ('-h','-H','--help'): self.usageExit() if opt in ('-q','--quiet'): self.verbosity = 0 if opt in ('-v','--verbose'): self.verbosity = 2 if len(args) == 0 and self.defaultTest is None: self.test = self.testLoader.loadTestsFromModule(self.module) return if len(args) > 0: self.testNames = args else: self.testNames = (self.defaultTest,) self.createTests() except getopt.error, msg: self.usageExit(msg) def createTests(self): self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module) def runTests(self): if self.testRunner is None: self.testRunner = TextTestRunner(verbosity=self.verbosity) result = self.testRunner.run(self.test) sys.exit(not result.wasSuccessful()) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None) pyunit-1.4.1/examples/0040775000076400007640000000000007335002407013737 5ustar stevestevepyunit-1.4.1/examples/withjpython.py0100775000076400007640000000220107237261620016700 0ustar stevesteve#!/usr/bin/env jpython """Example of using PyUnit with JPython""" import sys, os ## Patch paths to pyunit and standard C-python modules; adapt to your machine! sys.path.insert(0, os.path.dirname(os.getcwd())) # i.e. '..' sys.path.insert(0,'/usr/lib/python1.5') import unittest import java.util.StringTokenizer StringTokenizer = java.util.StringTokenizer class StringTokenizerTestCase(unittest.TestCase): def _getTokens(self, tokenizer): tokens = [] while tokenizer.hasMoreTokens(): tokens.append(tokenizer.nextToken()) return tokens def testDefaultTokenizing(self): "Default tokenizing with whitespace delimiters" tokenizer = StringTokenizer("mary had\t a\n little lamb") assert self._getTokens(tokenizer) == ['mary','had','a','little','lamb'] def testNullDelimiters(self): "Tokenizing a string containing nulls" tokenizer = StringTokenizer("a\000b\000c","\000") assert self._getTokens(tokenizer) == ['a','b','c'] def suite(): return unittest.makeSuite(StringTokenizerTestCase) if __name__ == '__main__': unittest.main(defaultTest='suite') pyunit-1.4.1/examples/alltests.py0100664000076400007640000000122407253134230016137 0ustar stevesteve#!/usr/bin/env python # # Example of assembling all available unit tests into one suite. This usually # varies greatly from one project to the next, so the code shown below will not # be incorporated into the 'unittest' module. Instead, modify it for your own # purposes. # # $Id: alltests.py,v 1.3 2001/03/12 11:52:56 purcell Exp $ import unittest def suite(): modules_to_test = ('listtests', 'widgettests') # and so on alltests = unittest.TestSuite() for module in map(__import__, modules_to_test): alltests.addTest(unittest.findTestCases(module)) return alltests if __name__ == '__main__': unittest.main(defaultTest='suite') pyunit-1.4.1/examples/manytests.py0100664000076400007640000000102207333457350016341 0ustar stevesteve#!/usr/bin/env python # # Large numbers of unit tests # # $Id: manytests.py,v 1.2 2001/08/06 09:10:00 purcell Exp $ import unittest class ProlificTestCase(unittest.TestCase): """A toy test case class with very many test methods""" for i in range(10000): exec("def test%i(self): pass" % i) del(i) def suite(): return unittest.makeSuite(ProlificTestCase) if __name__ == '__main__': # When this module is executed from the command-line, run all its tests unittest.TextTestRunner().run(suite()) pyunit-1.4.1/examples/listtests.py0100664000076400007640000000650307253134230016347 0ustar stevesteve#!/usr/bin/env python # # Unit tests for lists. This example shows how subclassing can be used in # order to re-use test code wth different test objects. Comments in this # module explain some points about typical usage. See the documentation for # more information, including the documentation strings in the unittest module. # # $Id: listtests.py,v 1.3 2001/03/12 11:52:56 purcell Exp $ import unittest from UserList import UserList class ListTestCase(unittest.TestCase): """A simple and incomplete test case for python's built-in lists""" def setUp(self): self.list = [] # All list test cases will start with an empty list def _appendItemsToList(self,items): """Do some further set-up. Used by some of the test functions.""" for item in items: self.list.append(item) def testAppend(self): # See note in documentation concerning use of 'assert' vs. 'assert_' self.assert_(len(self.list) == 0) self.list.append('anItem') self.assertEquals(len(self.list), 1) self.assertEquals(self.list[0], 'anItem') def testCount(self): "Check count() within heterogeneous list" self._appendItemsToList(('a','b',1,2,'a','2')) self.assert_(self.list.count('a') == 2) # failUnless is synonymous with assert_ self.failUnless(self.list.count('c') == 0) self.failUnless(self.list.count(2) == 1) self.failUnless(self.list.count(None) == 0) def testIndexing(self): # Normally when an exception is expected we would use # 'self.assertRaises', but this is not possible when testing behaviour # of operators such as [] try: self.list[0] except IndexError: pass else: self.fail('expected IndexError when list empty') self.list.append('first') self.failUnless(self.list[0] == 'first') self.failUnless(self.list[-1] == 'first') def testSlicing(self): self.failUnless(len(self.list[:0]) == 0) self.failUnless(len(self.list[:1]) == 0) self.failUnless(len(self.list[1:]) == 0) # no IndexErrors expected self.failUnless(len(self.list[1:1]) == 0) self._appendItemsToList(('first','second','third')) self.failUnless(len(self.list[:1]) == 1) self.failUnless(type(self.list[:1]) == type(self.list)) class UserListTestCase(ListTestCase): """A ListTestCase subclass that tests UserLists""" def setUp(self): self.list = UserList() def suite(): """Returns a suite containing all the test cases in this module. It can be a good idea to put an identically named factory function like this in every test module. Such a naming convention allows automation of test discovery. """ # Build a TestSuite containing all the possible test case instances # that can be made from the ListTestCase class using its 'test*' # functions. suite1 = unittest.makeSuite(ListTestCase) # Same with UserListTestCase, which subclasses ListTestCase; the 'test*' # methods in the base class will be found suite2 = unittest.makeSuite(UserListTestCase) # Make a composite test suite containing the two other suites return unittest.TestSuite((suite1, suite2)) if __name__ == '__main__': # When this module is executed from the command-line, run all its tests unittest.main() pyunit-1.4.1/examples/widgettests.py0100775000076400007640000000315607333457350016675 0ustar stevesteve#!/usr/bin/env python # # Unit tests for Widgets. Some of these tests intentionally fail. # # $Id: widgettests.py,v 1.8 2001/08/06 09:10:00 purcell Exp $ from widget import Widget import unittest class WidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget("The widget") def tearDown(self): self.widget.dispose() self.widget = None def testDefaultSize(self): assert self.widget.size() == (50,50), 'incorrect default size' def testResize(self): """Resizing of widgets Docstrings for test methods are used as the short description of the test when it is run. Only the first line is printed. """ # This is how to check that an expected exception really *is* thrown: self.assertRaises(ValueError, self.widget.resize, 0,0) self.widget.resize(100,150) assert self.widget.size() == (100,150), \ 'wrong size after resize' # Fancy way to build a suite class WidgetTestSuite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self,map(WidgetTestCase, ("testDefaultSize", "testResize"))) # Simpler way def makeWidgetTestSuite(): suite = unittest.TestSuite() suite.addTest(WidgetTestCase("testDefaultSize")) suite.addTest(WidgetTestCase("testResize")) return suite def suite(): return unittest.makeSuite(WidgetTestCase) # Make this test module runnable from the command prompt if __name__ == "__main__": #unittest.main(defaultTest="WidgetTestCase:testResize") unittest.main() pyunit-1.4.1/examples/widget.py0100664000076400007640000000110507103647203015570 0ustar stevesteve# A phony test subject class for the 'widgettests' example module # # Very artificial, and not to be used as an example of good python style # # $Id: widget.py,v 1.2 2000/05/02 21:54:11 purcell Exp $ class Widget: def __init__(self, widgetname, size=(50,50)): self.name = widgetname self._size = size def size(self): return self._size def resize(self, width, height): if width < 1 or height < 1: raise ValueError, "illegal size" self.__size = (width, height) # Deliberate typo def dispose(self): pass pyunit-1.4.1/examples/README0100664000076400007640000000031007024231203014577 0ustar stevesteveThis directory contains simple example tests that demonstrate how to use PyUnit. In order for them to work correctly, the 'unittest' module ('unittest.py') must be in your Python module search path. pyunit-1.4.1/doc/0040775000076400007640000000000007335002407012666 5ustar stevestevepyunit-1.4.1/doc/PyUnit.html0100664000076400007640000007411607334164250015016 0ustar stevesteve Python Unit Testing Framework

Python Unit Testing Framework

Author: Steve Purcell, <stephen_purcell at yahoo dot com>
Project website: http://pyunit.sourceforge.net/

Contents

Overview

The Python unit testing framework, dubbed 'PyUnit' by convention, is a Python language version of JUnit, by smart cookies Kent Beck and Erich Gamma. JUnit is, in turn, a Java version of Kent's Smalltalk testing framework. Each is the de facto standard unit testing framework for its respective language.

This document explains the Python-specific aspects of the design and usage of PyUnit; for background information on the basic design of the framework the reader is referred to Kent's original paper, "Simple Smalltalk Testing: With Patterns".

PyUnit forms a part of the Python Standard Library as of Python version 2.1.

The following information assumes knowledge of Python, a language so easy that even I managed to learn it, and so addictive that I can't stop.

System requirements

PyUnit is designed to work with any standard Python, version 1.5.2 and higher.

PyUnit has been tested by the author on Linux (Redhat 6.0 and 6.1, Debian Potato) with Python 1.5.2, 2.0 and 2.1. It is also known to work on other Python platforms, including Windows and Mac. If any platform or Python version issues cause you trouble, please let me know.

For details of using PyUnit with JPython and Jython, please refer to the section 'Using PyUnit with JPython and Jython'.

Using PyUnit to write your own tests

Installation

The classes needed to write tests are to be found in the 'unittest' module. This module is part of the standard Python library for Python 2.1 and later. If you are using an older Python version, you should obtain the module from the separate PyUnit distribution.

To be able to use the module from your own code, simply ensure that the directory containing the file 'unittest.py' is in your Python search path. You can do this by setting the '$PYTHONPATH' environment variable, or by placing the file in a directory in your current Python search path, such as /usr/lib/python1.5/site-packages on Redhat Linux machines.

Note that you will have to do this before you can run the examples that are provided with PyUnit unless you copy 'unittest.py' into the examples directory.

An introduction to TestCases

The basic building blocks of unit testing are 'test cases' -- single scenarios that must be set up and checked for correctness. In PyUnit, test cases are represented by the TestCase class in the unittest module. To make your own test cases you must write subclasses of TestCase.

An instance of a TestCase class is an object that can completely run a single test method, together with optional set-up and tidy-up code.

The testing code of a TestCase instance should be entirely self contained, such that it can be run either in isolation or in arbitrary combination with any number of other test cases.

Creating a simple test case

The simplest test case subclass will simply override the runTest method in order to perform specific testing code:

        import unittest

        class DefaultWidgetSizeTestCase(unittest.TestCase):
            def runTest(self):
                widget = Widget("The widget")
                assert widget.size() == (50,50), 'incorrect default size'
    

Note that in order to test something, we just use the built-in 'assert' statement of Python. If the assertion fails when the test case runs, an AssertionError will be raised, and the testing framework will identify the test case as a 'failure'. Other exceptions that do not arise from explicit 'assert' checks are identified by the testing framework as 'errors'. (See also the section 'More about test conditions'.)

The way to run a test case will be described later. For now, note that to construct an instance of such a test case, we call its constructor without arguments:

        testCase = DefaultWidgetSizeTestCase()
    

Re-using set-up code: creating 'fixtures'

Now, such test cases can be numerous, and their set-up can be repetitive. In the above case, constructing a 'Widget' in each of 100 Widget test case subclasses would mean unsightly duplication.

Luckily, we can factor out such set-up code by implementing a hook method called setUp, which the testing framework will automatically call for us when we run the test:

        import unittest

        class SimpleWidgetTestCase(unittest.TestCase):
            def setUp(self):
                self.widget = Widget("The widget")

        class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):
            def runTest(self):
                assert self.widget.size() == (50,50), 'incorrect default size'

        class WidgetResizeTestCase(SimpleWidgetTestCase):
            def runTest(self):
                self.widget.resize(100,150)
                assert self.widget.size() == (100,150), \
                       'wrong size after resize'
    

If the setUp method raises an exception while the test is running, the framework will consider the test to have suffered an error, and the runTest method will not be executed.

Similarly, we can provide a tearDown method that tidies up after the runTest method has been run:

        import unittest

        class SimpleWidgetTestCase(unittest.TestCase):
            def setUp(self):
                self.widget = Widget("The widget")
            def tearDown(self):
                self.widget.dispose()
                self.widget = None
    

If setUp succeeded, the tearDown method will be run regardless of whether or not runTest succeeded.

Such a working environment for the testing code is termed a fixture.

TestCase classes with several test methods

Often, many small test cases will use the same fixture. In this case, we would end up subclassing SimpleWidgetTestCase into many small one-method classes such as DefaultWidgetSizeTestCase. This is time-consuming and discouraging, so in the same vein as JUnit, PyUnit provides a simpler mechanism:

        import unittest

        class WidgetTestCase(unittest.TestCase):
            def setUp(self):
                self.widget = Widget("The widget")
            def tearDown(self):
                self.widget.dispose()
                self.widget = None
            def testDefaultSize(self):
                assert self.widget.size() == (50,50), 'incorrect default size'
            def testResize(self):
                self.widget.resize(100,150)
                assert self.widget.size() == (100,150), \
                       'wrong size after resize'
    

Here we have not provided a runTest method, but have instead provided two different test methods. Class instances will now each run one of the test methods, with self.widget created and destroyed separately for each instance. When creating an instance we must specify the test method it is to run. We do this by passing the method name in the constructor:

        defaultSizeTestCase = WidgetTestCase("testDefaultSize")
        resizeTestCase = WidgetTestCase("testResize")
    

Aggregating test cases into test suites

Test case instances are grouped together according to the features they test. PyUnit provides a mechanism for this: the 'test suite', represented by the class TestSuite in the unittest module:

        widgetTestSuite = unittest.TestSuite()
        widgetTestSuite.addTest(WidgetTestCase("testDefaultSize"))
        widgetTestSuite.addTest(WidgetTestCase("testResize"))
    

For the ease of running tests, as we will see later, it is a good idea to provide in each test module a 'callable' object that returns a pre-built test suite:

       def suite():
           suite = unittest.TestSuite()
           suite.addTest(WidgetTestCase("testDefaultSize"))
           suite.addTest(WidgetTestCase("testResize"))
           return suite
    

or even:

       class WidgetTestSuite(unittest.TestSuite):
           def __init__(self):
               unittest.TestSuite.__init__(self,map(WidgetTestCase,
                                                     ("testDefaultSize",
                                                      "testResize")))
    

(the latter is admittedly not for the faint-hearted)

Since it is a common pattern to create a TestCase subclass with many similarly named test functions, there is a convenience function called makeSuite provided in the unittest module that constructs a test suite that comprises all of the test cases in a test case class:-

       suite = unittest.makeSuite(WidgetTestCase,'test')
    

Note that when using the makeSuite function, the order in which the various test cases will be run by the test suite is the order determined by sorting the test function names using the cmp built-in function.

Nesting test suites

Often it is desirable to group suites of test cases together, so as to run tests for the whole system at once. This is easy, since TestSuites can be added to a TestSuite just as TestCases can be added to a TestSuite:-

       suite1 = module1.TheTestSuite()
       suite2 = module2.TheTestSuite()
       alltests = unittest.TestSuite((suite1, suite2))
    

An example of nesting test suites can be found in the file 'alltests.py', in the 'examples' subdirectory of the distribution package.

Where to place testing code

You can place the definitions of test cases and test suites in the same modules as the code they are to test (e.g. 'widget.py'), but there are several advantages to placing the test code in a separate module, such as 'widgettests.py':

  • The test module can be run standalone from the command line
  • The test code can more easily be separated from shipped code
  • There is less temptation to change test code to fit the code it tests without a good reason
  • Test code should be modified much less frequently than the code it tests
  • Tested code can be refactored more easily
  • Tests for modules written in C must be in separate modules anyway, so why not be consistent?
  • If the testing strategy changes, there is no need to change the source code

Running tests interactively

Of course, the whole point of writing these tests is so that we can run them and find out if our software is working. The test framework uses 'TestRunner' classes to provide an environment in which your tests can execute. The most common TestRunner is TextTestRunner, which can run tests and report the results in textual form:

        runner = unittest.TextTestRunner()
        runner.run(widgetTestSuite)
    

By default, TextTestRunner prints its output to sys.stderr, but this can be changed by passing a different file-like object to its constructor.

Using TextTestRunner like this is an ideal way to run your tests interactively from within a Python interpreter session.

Running tests from the command line

The unittest module contains a function called main, which can be used to easily turn a test module into a script that will run the tests it contains. The main function uses the unittest.TestLoader class to automatically find and load test cases within the current module.

Therefore, if you name your test methods using the test* convention described earlier, you can place the following code at the bottom of your test module:

        if __name__ == "__main__":
            unittest.main()
    

Then, when you execute your test module from the command line, all of the tests contained therein will be run. Run the module with the '-h' option in order to see the options available.

To run arbitrary tests from the command-line, you can run the unittest module as a script, giving it the name of an test case or test suite:

        % python unittest.py widgettests.WidgetTestSuite
    

or

        % python unittest.py widgettests.makeWidgetTestSuite
    

You may also specify particular tests on the command-line. To run the TestCase subclass 'ListTestCase' in the module 'listtests' (see the 'examples' subdirectory of the distribution package) you can execute the command:

        % python unittest.py listtests.ListTestCase.testAppend
    

where 'testAppend' is the name of the test method that is to be run by the test case instance. To create and run ListTestCase instances for all the 'test*' methods in that class, you can run:

        % python unittest.py listtests.ListTestCase
    

The GUI test runner

There is a graphical front end that you can use in order to run your tests. It is written using Tkinter, the windowing toolkit shipped with Python on most platforms. It looks similar to the JUnit GUI.

To use the GUI test runner, simply run:

        % python unittestgui.py
    
or
        % python unittestgui.py widgettests.WidgetTestSuite
    

Note that here, again, the name entered for the test to be run should be the fully-qualified name of an object which returns a TestCase or TestSuite instance. It should not be the name of a pre-created test, since every test must be recreated each time it is run.

The use of the GUI test runner rather than the text test runner imposes a time overhead due to all those window updates; on my system, it takes an extra seven seconds per thousand tests. Your mileage may vary.

Documenting your tests

Usually, when a test is run its name is displayed by the TestRunner. This name is derived from the name of the test case class, and the name of the test method that the instance has been initialised to run.

However, if you supply a doc-string for a test method, the first line of that doc-string will be displayed when the test is run. This provides an easy mechanism for documenting your tests:

        class WidgetTestCase(unittest.TestCase):
            def testDefaultSize(self):
                """Check that widgets are created with correct default size"""
                assert self.widget.size() == (50,50), 'incorrect default size'
    

More about test conditions

I have suggested the use of Python's built-in assertion mechanism for checking conditions in test cases rather than a 'home-brewed' equivalent; assert is simple, concise and familiar.

Note, however, that if tests are run with Python's optimisation option turned on (generating '.pyo' bytecode files), the assert statements will be skipped, rendering the test cases quite useless.

For those who tend to work with Python's optimisation option enabled, I have included a method assert_ in the TestCase class. It is functionally equivalent to the assert built-in and will not be optimised away, but it is less convenient and results in less helpful error messages:

        def runTest(self):
            self.assert_(self.widget.size() == (100,100), "size is wrong")
    

For good measure I have also provided TestCase with failIf and failUnless methods:

        def runTest(self):
            self.failIf(self.widget.size() <> (100,100))
    

A test case method can also call fail in order to fail immediately:

        def runTest(self):
            ...
            if not hasattr(something, "blah"):
                self.fail("blah missing")
                # or just 'self.fail()'
    

Testing for equality

The most common type of assertion is an assertion of equality between two values or objects. If the assertion fails, the developer usually wants to see what the incorrect value actually was.

TestCase has a pair of methods called assertEqual and assertNotEqual for this purpose (with aliases failUnlessEqual and failIfEqual for those who prefer):

        def testSomething(self):
            self.widget.resize(100,100)
            self.assertEqual(self.widget.size, (100,100))
    

Testing for exceptions

Often a test will wish to check that an exception is raised in a certain set of circumstances. If the expected exception is not thrown, the test should fail. This is easy to do:

        def runTest(self):
            try:
                self.widget.resize(-1,-1)
            except ValueError:
                pass
            else:
                fail("expected a ValueError")
    

Usually, the source of the expected exception is a callable object; for that reason, TestCase has an assertRaises method. The first two arguments of the method are the expected exception as it would appear in an 'except' clause, and the callable object. The remaining arguments are those that should be passed to the callable object:

        def runTest(self):
            self.assertRaises(ValueError, self.widget.resize, -1, -1)
    

Re-using old test code with PyUnit

Some users will find that they have existing test code that they would like to run from PyUnit, without converting every old test function to a TestCase subclass.

For this reason, PyUnit provides a FunctionTestCase class. This subclass of TestCase can be used to wrap an existing test function. Set-up and tear-down functions can also optionally be wrapped.

Given the following test function:

        def testSomething():
            something = makeSomething()
            assert something.name is not None
            ...
    

one can create an equivalent test case instance as follows:

        testcase = unittest.FunctionTestCase(testSomething)
    

If there are additional set-up and tear-down methods that should be called as part of the test case's operation, they can also be provided:

        testcase = unittest.FunctionTestCase(testSomething,
                                             setUp=makeSomethingDB,
                                             tearDown=deleteSomethingDB)
    

Using PyUnit with JPython and Jython

Although PyUnit was written primarily for 'C' Python, it is possible to write PyUnit tests using Jython for your Java or Jython software. This can be preferable to trying to write JUnit tests using Jython. PyUnit also works correctly with Jython's predecessors, JPython 1.0 and 1.1.

Of course, Java does not have a TK GUI interface, so PyUnit's Tkinter-based GUI will not work with Jython. The text-only interface works just fine, however.

To do so, simply copy the standard C Python library module files 'traceback.py', 'linecache.py', 'stat.py' and 'getopt.py' to a location from which they can be imported by JPython. You can get these files from any distribution of C Python. (These guidelines are based on the standard library of C Python 1.5.x, and may not be correct for other Python versions.)

Now you can write your PyUnit tests exactly as you would with C Python.

Caveats

Assertions

See the caveats in the section "More about test conditions" above.

Memory use

When exceptions are raised during the running of a test suite, the resulting traceback objects are saved so that failure details can be formatted and printed at the end of the test run. Apart from simplicity, the benefit of this is that a future version of the GUI TestRunner will be able to allow post-mortem inspection of local and global variable values, which are stored with the traceback.

A possible side-effect is that when running test suites with very high failure rates, the memory usage of all these saved traceback objects could become a problem. Of course, if so many tests are failing, this memory overhead is the least of your problems.

Terms of use

You may freely use, alter and redistribute this software under the same liberal terms that apply to Python itself. All I ask is that my name, e-mail address and the project URL be retained in the source code and accompanying documentation, with a credit for me as the original author.

My motive for writing this software was to make a small contribution to the improvement of software quality in the world; I didn't bargain on getting any cash. (That's not to say that sponsorship would be unwelcome.)

Future plans

One key plan for the future is to integrate the TK GUI with the IDLE IDE. Volunteers are welcome!

Other than that, I have no great plans to extend the functionality of the module. I have kept PyUnit as simple as possible (but no simpler, hopefully!) because I believe that helper modules for such common testing tasks as log file comparison are better written by test writers than by myself.

Updates and community

News, updates and more are available at the project website.

Comments, suggestions and bug reports are welcome; simply e-mail me or, better still, join the very low-volume mailing list and post your comments there. There are a surprisingly large number of people already using PyUnit, and they all have wisdom to share.

Acknowledgements

Many thanks to Guido and his disciples for the Python language. In tribute, I have written the following haiku (or 'pyku', if you will):

Guido van Rossum
'Gawky Dutchman' gave birth to
Beautiful Python

I gratefully acknowledge the work of Kent Beck and Erich Gamma for their work on JUnit, which made the design of PyUnit a no-brainer.

Thanks also to Tim Voght; I discovered after I had implemented PyUnit that he had also implemented a 'pyunit' module as part of his 'PyWiki' WikiWikiWeb clone. He graciously gave me the go-ahead to submit my version to the community at large.

Many thanks to those who have written to me with suggestions and questions. I've tried to add appropriate credits in the CHANGES file in the download package.

Particular thanks to Jérôme Marant, who packaged PyUnit for Debian.

Related information


About the author

Steve Purcell is just a programmer at heart, working independently writing, applying and teaching Open Source software.

He recently acted as Technical Director for a Web/WAP start-up, but spends most of his time architecting and coding large Java systems whilst counterproductively urging his Java-skilled colleagues to take up Python instead.


Steve Purcell, <stephen_purcell at yahoo dot com>
$Id: PyUnit.html,v 1.20 2001/08/08 07:22:16 purcell Exp $ pyunit-1.4.1/CHANGES0100664000076400007640000000576407335002301013116 0ustar stevesteveCHANGE LOG FOR PYUNIT ===================== Changes from 1.4.0 to 1.4.1 --------------------------- - corrected version number in distutils 'setup.py' - fixed an incorrect code example in cookbook document - fixed bug when running 'unittest.py' from the command line in order to execute tests in other modules (thanks to Gary Todd) Changes from 1.3.1 to 1.4.0 --------------------------- - main() runs all tests in the module __main__ by default - New code allows all test case in a given module to be located and instantiated - Simplified command line syntax - Made TextTestRunner configurable with verbose and quiet options, and consequently removed obsoleted JUnitTextTestRunner - Test instantiation code now localised in a replaceable TestLoader class - 'setup.py' contributed by Bill Bumgarner - TestResult.addSuccess() method added (thanks to Frederic Corne) - Stop overridden test methods being run twice (thanks to Brian Zimmer and Maas-Marten Zeeman) - Don't expand GUI's progress bar vertically when resizing (Fred Drake) - Updated documentation Changes from 1.3.0 to 1.3.1 --------------------------- * 'debug' methods added to allow running tests without silently collecting exceptions (thanks to Jim Fulton) * Bugfix for JUnitTextTestRunner (thanks to Fred Drake) Changes from 1.2.0 to 1.3.0 --------------------------- * Clearer and more verbose text output format * Tests run in text mode can now be interrupted using ctrl-c * New FunctionTestCase class provides support for wrapping legacy test functions into PyUnit test case instances * Code is now compatible with JPython (new example: examples/withjpython.py) * Support for short descriptions of tests, taken from __doc__ strings by default * Updated and expanded documentation * Tested with Python 2 * Changed module reloading mechanism in GUI test runner to fix a problem with Python 2 on Win32 reported by Henrik Weber (bug 125463) * Convenient new unittest.main() function for use by all test modules Changes from 1.1.0 to 1.2.0 --------------------------- * Added TK-based GUI: unittestgui.py * Updated documentation and doc strings * Fixed minor bug 106912: test cases not sorted correctly. Thanks to Frederic Giacometti for spotting. * Check that imported test names are callable() objects * New example 'manytests.py'; large number of small dummy tests Changes from 1.0.1 to 1.1.0 --------------------------- * Changed 'makeSuite' so that test cases functions in base classes of the given test case class are also found. This allows re-use of test code with different set-up code. See 'listtests' example. (Thanks to Lars Marius Garshol.) * Added sorting functionality to 'makeSuite' function. (Suggested by Kevin Butler) * Changes to documentation (clarifications, notes about new functionality) * Added 'assertRaises' method to TestCase * Added 'fail' method to TestCase * Added this CHANGES file to distribution * Added 'listtests.py' and 'alltests.py' examples Changes from 1.0 to 1.0.1 --------------------------- * Move to SourceForge