[3] | 1 | #!/usr/bin/env python |
---|
| 2 | """ This is a hacked version of PyUnit that extends its reporting capabilities |
---|
| 3 | with optional meta data on the test cases. It also makes it possible to |
---|
| 4 | separate the standard and error output streams in TextTestRunner. |
---|
| 5 | |
---|
| 6 | It's a hack rather than a set of subclasses because a) Steve had used double |
---|
| 7 | underscore private attributes for some things I needed access to, and b) the |
---|
| 8 | changes affected so many classes that it was easier just to hack it. |
---|
| 9 | |
---|
| 10 | The changes are in the following places: |
---|
| 11 | TestCase: |
---|
| 12 | - minor refactoring of __init__ and __call__ internals |
---|
| 13 | - added some attributes and methods for storing and retrieving meta data |
---|
| 14 | |
---|
| 15 | _TextTestResult |
---|
| 16 | - refactored the stream handling |
---|
| 17 | - incorporated all the output code from TextTestRunner |
---|
| 18 | - made the output of FAIL and ERROR information more flexible and |
---|
| 19 | incorporated the new meta data from TestCase |
---|
| 20 | - added a flag called 'explain' to __init__ that controls whether the new ' |
---|
| 21 | explanation' meta data from TestCase is printed along with tracebacks |
---|
| 22 | |
---|
| 23 | TextTestRunner |
---|
| 24 | - delegated all output to _TextTestResult |
---|
| 25 | - added 'err' and 'explain' to the __init__ signature to match the changes |
---|
| 26 | in _TextTestResult |
---|
| 27 | |
---|
| 28 | TestProgram |
---|
| 29 | - added -e and --explain as flags on the command line |
---|
| 30 | |
---|
| 31 | -- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001) |
---|
| 32 | |
---|
| 33 | - _TestTextResult.printErrorList(): print blank line after each traceback |
---|
| 34 | |
---|
| 35 | -- Mike Orr <mso@oz.net> (Nov 11, 2002) |
---|
| 36 | |
---|
| 37 | TestCase methods copied from unittest in Python 2.3: |
---|
| 38 | - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places. |
---|
| 39 | - .failIfAlmostEqual(first, second, places=7, msg=None) |
---|
| 40 | |
---|
| 41 | -- Mike Orr (Jan 5, 2004) |
---|
| 42 | |
---|
| 43 | |
---|
| 44 | Below is the original docstring for unittest. |
---|
| 45 | --------------------------------------------------------------------------- |
---|
| 46 | Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's |
---|
| 47 | Smalltalk testing framework. |
---|
| 48 | |
---|
| 49 | This module contains the core framework classes that form the basis of |
---|
| 50 | specific test cases and suites (TestCase, TestSuite etc.), and also a |
---|
| 51 | text-based utility class for running the tests and reporting the results |
---|
| 52 | (TextTestRunner). |
---|
| 53 | |
---|
| 54 | Simple usage: |
---|
| 55 | |
---|
| 56 | import unittest |
---|
| 57 | |
---|
| 58 | class IntegerArithmenticTestCase(unittest.TestCase): |
---|
| 59 | def testAdd(self): ## test method names begin 'test*' |
---|
| 60 | self.assertEquals((1 + 2), 3) |
---|
| 61 | self.assertEquals(0 + 1, 1) |
---|
| 62 | def testMultiply(self); |
---|
| 63 | self.assertEquals((0 * 10), 0) |
---|
| 64 | self.assertEquals((5 * 8), 40) |
---|
| 65 | |
---|
| 66 | if __name__ == '__main__': |
---|
| 67 | unittest.main() |
---|
| 68 | |
---|
| 69 | Further information is available in the bundled documentation, and from |
---|
| 70 | |
---|
| 71 | http://pyunit.sourceforge.net/ |
---|
| 72 | |
---|
| 73 | Copyright (c) 1999, 2000, 2001 Steve Purcell |
---|
| 74 | This module is free software, and you may redistribute it and/or modify |
---|
| 75 | it under the same terms as Python itself, so long as this copyright message |
---|
| 76 | and disclaimer are retained in their original form. |
---|
| 77 | |
---|
| 78 | IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, |
---|
| 79 | SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF |
---|
| 80 | THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
---|
| 81 | DAMAGE. |
---|
| 82 | |
---|
| 83 | THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT |
---|
| 84 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
---|
| 85 | PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, |
---|
| 86 | AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, |
---|
| 87 | SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
---|
| 88 | """ |
---|
| 89 | |
---|
| 90 | __author__ = "Steve Purcell" |
---|
| 91 | __email__ = "stephen_purcell at yahoo dot com" |
---|
| 92 | __revision__ = "$Revision: 1.11 $"[11:-2] |
---|
| 93 | |
---|
| 94 | |
---|
| 95 | ################################################## |
---|
| 96 | ## DEPENDENCIES ## |
---|
| 97 | |
---|
| 98 | import os |
---|
| 99 | import re |
---|
| 100 | import string |
---|
| 101 | import sys |
---|
| 102 | import time |
---|
| 103 | import traceback |
---|
| 104 | import types |
---|
| 105 | import pprint |
---|
| 106 | |
---|
| 107 | ################################################## |
---|
| 108 | ## CONSTANTS & GLOBALS |
---|
| 109 | |
---|
| 110 | try: |
---|
| 111 | True,False |
---|
| 112 | except NameError: |
---|
| 113 | True, False = (1==1),(1==0) |
---|
| 114 | |
---|
| 115 | ############################################################################## |
---|
| 116 | # Test framework core |
---|
| 117 | ############################################################################## |
---|
| 118 | |
---|
| 119 | |
---|
| 120 | class TestResult: |
---|
| 121 | """Holder for test result information. |
---|
| 122 | |
---|
| 123 | Test results are automatically managed by the TestCase and TestSuite |
---|
| 124 | classes, and do not need to be explicitly manipulated by writers of tests. |
---|
| 125 | |
---|
| 126 | Each instance holds the total number of tests run, and collections of |
---|
| 127 | failures and errors that occurred among those test runs. The collections |
---|
| 128 | contain tuples of (testcase, exceptioninfo), where exceptioninfo is a |
---|
| 129 | tuple of values as returned by sys.exc_info(). |
---|
| 130 | """ |
---|
| 131 | def __init__(self): |
---|
| 132 | self.failures = [] |
---|
| 133 | self.errors = [] |
---|
| 134 | self.testsRun = 0 |
---|
| 135 | self.shouldStop = 0 |
---|
| 136 | |
---|
| 137 | def startTest(self, test): |
---|
| 138 | "Called when the given test is about to be run" |
---|
| 139 | self.testsRun = self.testsRun + 1 |
---|
| 140 | |
---|
| 141 | def stopTest(self, test): |
---|
| 142 | "Called when the given test has been run" |
---|
| 143 | pass |
---|
| 144 | |
---|
| 145 | def addError(self, test, err): |
---|
| 146 | "Called when an error has occurred" |
---|
| 147 | self.errors.append((test, err)) |
---|
| 148 | |
---|
| 149 | def addFailure(self, test, err): |
---|
| 150 | "Called when a failure has occurred" |
---|
| 151 | self.failures.append((test, err)) |
---|
| 152 | |
---|
| 153 | def addSuccess(self, test): |
---|
| 154 | "Called when a test has completed successfully" |
---|
| 155 | pass |
---|
| 156 | |
---|
| 157 | def wasSuccessful(self): |
---|
| 158 | "Tells whether or not this result was a success" |
---|
| 159 | return len(self.failures) == len(self.errors) == 0 |
---|
| 160 | |
---|
| 161 | def stop(self): |
---|
| 162 | "Indicates that the tests should be aborted" |
---|
| 163 | self.shouldStop = 1 |
---|
| 164 | |
---|
| 165 | def __repr__(self): |
---|
| 166 | return "<%s run=%i errors=%i failures=%i>" % \ |
---|
| 167 | (self.__class__, self.testsRun, len(self.errors), |
---|
| 168 | len(self.failures)) |
---|
| 169 | |
---|
| 170 | class TestCase: |
---|
| 171 | """A class whose instances are single test cases. |
---|
| 172 | |
---|
| 173 | By default, the test code itself should be placed in a method named |
---|
| 174 | 'runTest'. |
---|
| 175 | |
---|
| 176 | If the fixture may be used for many test cases, create as |
---|
| 177 | many test methods as are needed. When instantiating such a TestCase |
---|
| 178 | subclass, specify in the constructor arguments the name of the test method |
---|
| 179 | that the instance is to execute. |
---|
| 180 | |
---|
| 181 | Test authors should subclass TestCase for their own tests. Construction |
---|
| 182 | and deconstruction of the test's environment ('fixture') can be |
---|
| 183 | implemented by overriding the 'setUp' and 'tearDown' methods respectively. |
---|
| 184 | |
---|
| 185 | If it is necessary to override the __init__ method, the base class |
---|
| 186 | __init__ method must always be called. It is important that subclasses |
---|
| 187 | should not change the signature of their __init__ method, since instances |
---|
| 188 | of the classes are instantiated automatically by parts of the framework |
---|
| 189 | in order to be run. |
---|
| 190 | """ |
---|
| 191 | |
---|
| 192 | # This attribute determines which exception will be raised when |
---|
| 193 | # the instance's assertion methods fail; test methods raising this |
---|
| 194 | # exception will be deemed to have 'failed' rather than 'errored' |
---|
| 195 | |
---|
| 196 | failureException = AssertionError |
---|
| 197 | |
---|
| 198 | # the name of the fixture. Used for displaying meta data about the test |
---|
| 199 | name = None |
---|
| 200 | |
---|
| 201 | def __init__(self, methodName='runTest'): |
---|
| 202 | """Create an instance of the class that will use the named test |
---|
| 203 | method when executed. Raises a ValueError if the instance does |
---|
| 204 | not have a method with the specified name. |
---|
| 205 | """ |
---|
| 206 | self._testMethodName = methodName |
---|
| 207 | self._setupTestMethod() |
---|
| 208 | self._setupMetaData() |
---|
| 209 | |
---|
| 210 | def _setupTestMethod(self): |
---|
| 211 | try: |
---|
| 212 | self._testMethod = getattr(self, self._testMethodName) |
---|
| 213 | except AttributeError: |
---|
| 214 | raise ValueError, "no such test method in %s: %s" % \ |
---|
| 215 | (self.__class__, self._testMethodName) |
---|
| 216 | |
---|
| 217 | ## meta data methods |
---|
| 218 | |
---|
| 219 | def _setupMetaData(self): |
---|
| 220 | """Setup the default meta data for the test case: |
---|
| 221 | |
---|
| 222 | - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName |
---|
| 223 | - description: 1st line of Class docstring + 1st line of method docstring |
---|
| 224 | - explanation: rest of Class docstring + rest of method docstring |
---|
| 225 | |
---|
| 226 | """ |
---|
| 227 | |
---|
| 228 | |
---|
| 229 | testDoc = self._testMethod.__doc__ or '\n' |
---|
| 230 | testDocLines = testDoc.splitlines() |
---|
| 231 | |
---|
| 232 | testDescription = testDocLines[0].strip() |
---|
| 233 | if len(testDocLines) > 1: |
---|
| 234 | testExplanation = '\n'.join( |
---|
| 235 | [ln.strip() for ln in testDocLines[1:]] |
---|
| 236 | ).strip() |
---|
| 237 | else: |
---|
| 238 | testExplanation = '' |
---|
| 239 | |
---|
| 240 | fixtureDoc = self.__doc__ or '\n' |
---|
| 241 | fixtureDocLines = fixtureDoc.splitlines() |
---|
| 242 | fixtureDescription = fixtureDocLines[0].strip() |
---|
| 243 | if len(fixtureDocLines) > 1: |
---|
| 244 | fixtureExplanation = '\n'.join( |
---|
| 245 | [ln.strip() for ln in fixtureDocLines[1:]] |
---|
| 246 | ).strip() |
---|
| 247 | else: |
---|
| 248 | fixtureExplanation = '' |
---|
| 249 | |
---|
| 250 | if not self.name: |
---|
| 251 | self.name = self.__class__ |
---|
| 252 | self._id = "%s.%s" % (self.name, self._testMethodName) |
---|
| 253 | |
---|
| 254 | if not fixtureDescription: |
---|
| 255 | self._description = testDescription |
---|
| 256 | else: |
---|
| 257 | self._description = fixtureDescription + ', ' + testDescription |
---|
| 258 | |
---|
| 259 | if not fixtureExplanation: |
---|
| 260 | self._explanation = testExplanation |
---|
| 261 | else: |
---|
| 262 | self._explanation = ['Fixture Explanation:', |
---|
| 263 | '--------------------', |
---|
| 264 | fixtureExplanation, |
---|
| 265 | '', |
---|
| 266 | 'Test Explanation:', |
---|
| 267 | '-----------------', |
---|
| 268 | testExplanation |
---|
| 269 | ] |
---|
| 270 | self._explanation = '\n'.join(self._explanation) |
---|
| 271 | |
---|
| 272 | def id(self): |
---|
| 273 | return self._id |
---|
| 274 | |
---|
| 275 | def setId(self, id): |
---|
| 276 | self._id = id |
---|
| 277 | |
---|
| 278 | def describe(self): |
---|
| 279 | """Returns a one-line description of the test, or None if no |
---|
| 280 | description has been provided. |
---|
| 281 | |
---|
| 282 | The default implementation of this method returns the first line of |
---|
| 283 | the specified test method's docstring. |
---|
| 284 | """ |
---|
| 285 | return self._description |
---|
| 286 | |
---|
| 287 | shortDescription = describe |
---|
| 288 | |
---|
| 289 | def setDescription(self, descr): |
---|
| 290 | self._description = descr |
---|
| 291 | |
---|
| 292 | def explain(self): |
---|
| 293 | return self._explanation |
---|
| 294 | |
---|
| 295 | def setExplanation(self, expln): |
---|
| 296 | self._explanation = expln |
---|
| 297 | |
---|
| 298 | ## core methods |
---|
| 299 | |
---|
| 300 | def setUp(self): |
---|
| 301 | "Hook method for setting up the test fixture before exercising it." |
---|
| 302 | pass |
---|
| 303 | |
---|
| 304 | def run(self, result=None): |
---|
| 305 | return self(result) |
---|
| 306 | |
---|
| 307 | def tearDown(self): |
---|
| 308 | "Hook method for deconstructing the test fixture after testing it." |
---|
| 309 | pass |
---|
| 310 | |
---|
| 311 | def debug(self): |
---|
| 312 | """Run the test without collecting errors in a TestResult""" |
---|
| 313 | self.setUp() |
---|
| 314 | self._testMethod() |
---|
| 315 | self.tearDown() |
---|
| 316 | |
---|
| 317 | ## internal methods |
---|
| 318 | |
---|
| 319 | def defaultTestResult(self): |
---|
| 320 | return TestResult() |
---|
| 321 | |
---|
| 322 | def __call__(self, result=None): |
---|
| 323 | if result is None: |
---|
| 324 | result = self.defaultTestResult() |
---|
| 325 | |
---|
| 326 | result.startTest(self) |
---|
| 327 | try: |
---|
| 328 | try: |
---|
| 329 | self.setUp() |
---|
| 330 | except: |
---|
| 331 | result.addError(self, self.__exc_info()) |
---|
| 332 | return |
---|
| 333 | |
---|
| 334 | ok = 0 |
---|
| 335 | try: |
---|
| 336 | self._testMethod() |
---|
| 337 | ok = 1 |
---|
| 338 | except self.failureException, e: |
---|
| 339 | result.addFailure(self, self.__exc_info()) |
---|
| 340 | except: |
---|
| 341 | result.addError(self, self.__exc_info()) |
---|
| 342 | try: |
---|
| 343 | self.tearDown() |
---|
| 344 | except: |
---|
| 345 | result.addError(self, self.__exc_info()) |
---|
| 346 | ok = 0 |
---|
| 347 | if ok: |
---|
| 348 | result.addSuccess(self) |
---|
| 349 | finally: |
---|
| 350 | result.stopTest(self) |
---|
| 351 | |
---|
| 352 | return result |
---|
| 353 | |
---|
| 354 | def countTestCases(self): |
---|
| 355 | return 1 |
---|
| 356 | |
---|
| 357 | def __str__(self): |
---|
| 358 | return "%s (%s)" % (self._testMethodName, self.__class__) |
---|
| 359 | |
---|
| 360 | def __repr__(self): |
---|
| 361 | return "<%s testMethod=%s>" % \ |
---|
| 362 | (self.__class__, self._testMethodName) |
---|
| 363 | |
---|
| 364 | def __exc_info(self): |
---|
| 365 | """Return a version of sys.exc_info() with the traceback frame |
---|
| 366 | minimised; usually the top level of the traceback frame is not |
---|
| 367 | needed. |
---|
| 368 | """ |
---|
| 369 | exctype, excvalue, tb = sys.exc_info() |
---|
| 370 | if sys.platform[:4] == 'java': ## tracebacks look different in Jython |
---|
| 371 | return (exctype, excvalue, tb) |
---|
| 372 | newtb = tb.tb_next |
---|
| 373 | if newtb is None: |
---|
| 374 | return (exctype, excvalue, tb) |
---|
| 375 | return (exctype, excvalue, newtb) |
---|
| 376 | |
---|
| 377 | ## methods for use by the test cases |
---|
| 378 | |
---|
| 379 | def fail(self, msg=None): |
---|
| 380 | """Fail immediately, with the given message.""" |
---|
| 381 | raise self.failureException, msg |
---|
| 382 | |
---|
| 383 | def failIf(self, expr, msg=None): |
---|
| 384 | "Fail the test if the expression is true." |
---|
| 385 | if expr: raise self.failureException, msg |
---|
| 386 | |
---|
| 387 | def failUnless(self, expr, msg=None): |
---|
| 388 | """Fail the test unless the expression is true.""" |
---|
| 389 | if not expr: raise self.failureException, msg |
---|
| 390 | |
---|
| 391 | def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): |
---|
| 392 | """Fail unless an exception of class excClass is thrown |
---|
| 393 | by callableObj when invoked with arguments args and keyword |
---|
| 394 | arguments kwargs. If a different type of exception is |
---|
| 395 | thrown, it will not be caught, and the test case will be |
---|
| 396 | deemed to have suffered an error, exactly as for an |
---|
| 397 | unexpected exception. |
---|
| 398 | """ |
---|
| 399 | try: |
---|
| 400 | apply(callableObj, args, kwargs) |
---|
| 401 | except excClass: |
---|
| 402 | return |
---|
| 403 | else: |
---|
| 404 | if hasattr(excClass,'__name__'): excName = excClass.__name__ |
---|
| 405 | else: excName = str(excClass) |
---|
| 406 | raise self.failureException, excName |
---|
| 407 | |
---|
| 408 | def failUnlessEqual(self, first, second, msg=None): |
---|
| 409 | """Fail if the two objects are unequal as determined by the '!=' |
---|
| 410 | operator. |
---|
| 411 | """ |
---|
| 412 | if first != second: |
---|
| 413 | raise self.failureException, (msg or '%s != %s' % (first, second)) |
---|
| 414 | |
---|
| 415 | def failIfEqual(self, first, second, msg=None): |
---|
| 416 | """Fail if the two objects are equal as determined by the '==' |
---|
| 417 | operator. |
---|
| 418 | """ |
---|
| 419 | if first == second: |
---|
| 420 | raise self.failureException, (msg or '%s == %s' % (first, second)) |
---|
| 421 | |
---|
| 422 | def failUnlessAlmostEqual(self, first, second, places=7, msg=None): |
---|
| 423 | """Fail if the two objects are unequal as determined by their |
---|
| 424 | difference rounded to the given number of decimal places |
---|
| 425 | (default 7) and comparing to zero. |
---|
| 426 | |
---|
| 427 | Note that decimal places (from zero) is usually not the same |
---|
| 428 | as significant digits (measured from the most signficant digit). |
---|
| 429 | """ |
---|
| 430 | if round(second-first, places) != 0: |
---|
| 431 | raise self.failureException, \ |
---|
| 432 | (msg or '%s != %s within %s places' % (`first`, `second`, `places` )) |
---|
| 433 | |
---|
| 434 | def failIfAlmostEqual(self, first, second, places=7, msg=None): |
---|
| 435 | """Fail if the two objects are equal as determined by their |
---|
| 436 | difference rounded to the given number of decimal places |
---|
| 437 | (default 7) and comparing to zero. |
---|
| 438 | |
---|
| 439 | Note that decimal places (from zero) is usually not the same |
---|
| 440 | as significant digits (measured from the most signficant digit). |
---|
| 441 | """ |
---|
| 442 | if round(second-first, places) == 0: |
---|
| 443 | raise self.failureException, \ |
---|
| 444 | (msg or '%s == %s within %s places' % (`first`, `second`, `places`)) |
---|
| 445 | |
---|
| 446 | ## aliases |
---|
| 447 | |
---|
| 448 | assertEqual = assertEquals = failUnlessEqual |
---|
| 449 | |
---|
| 450 | assertNotEqual = assertNotEquals = failIfEqual |
---|
| 451 | |
---|
| 452 | assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual |
---|
| 453 | |
---|
| 454 | assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual |
---|
| 455 | |
---|
| 456 | assertRaises = failUnlessRaises |
---|
| 457 | |
---|
| 458 | assert_ = failUnless |
---|
| 459 | |
---|
| 460 | |
---|
| 461 | class FunctionTestCase(TestCase): |
---|
| 462 | """A test case that wraps a test function. |
---|
| 463 | |
---|
| 464 | This is useful for slipping pre-existing test functions into the |
---|
| 465 | PyUnit framework. Optionally, set-up and tidy-up functions can be |
---|
| 466 | supplied. As with TestCase, the tidy-up ('tearDown') function will |
---|
| 467 | always be called if the set-up ('setUp') function ran successfully. |
---|
| 468 | """ |
---|
| 469 | |
---|
| 470 | def __init__(self, testFunc, setUp=None, tearDown=None, |
---|
| 471 | description=None): |
---|
| 472 | TestCase.__init__(self) |
---|
| 473 | self.__setUpFunc = setUp |
---|
| 474 | self.__tearDownFunc = tearDown |
---|
| 475 | self.__testFunc = testFunc |
---|
| 476 | self.__description = description |
---|
| 477 | |
---|
| 478 | def setUp(self): |
---|
| 479 | if self.__setUpFunc is not None: |
---|
| 480 | self.__setUpFunc() |
---|
| 481 | |
---|
| 482 | def tearDown(self): |
---|
| 483 | if self.__tearDownFunc is not None: |
---|
| 484 | self.__tearDownFunc() |
---|
| 485 | |
---|
| 486 | def runTest(self): |
---|
| 487 | self.__testFunc() |
---|
| 488 | |
---|
| 489 | def id(self): |
---|
| 490 | return self.__testFunc.__name__ |
---|
| 491 | |
---|
| 492 | def __str__(self): |
---|
| 493 | return "%s (%s)" % (self.__class__, self.__testFunc.__name__) |
---|
| 494 | |
---|
| 495 | def __repr__(self): |
---|
| 496 | return "<%s testFunc=%s>" % (self.__class__, self.__testFunc) |
---|
| 497 | |
---|
| 498 | |
---|
| 499 | def describe(self): |
---|
| 500 | if self.__description is not None: return self.__description |
---|
| 501 | doc = self.__testFunc.__doc__ |
---|
| 502 | return doc and string.strip(string.split(doc, "\n")[0]) or None |
---|
| 503 | |
---|
| 504 | ## aliases |
---|
| 505 | shortDescription = describe |
---|
| 506 | |
---|
| 507 | class TestSuite: |
---|
| 508 | """A test suite is a composite test consisting of a number of TestCases. |
---|
| 509 | |
---|
| 510 | For use, create an instance of TestSuite, then add test case instances. |
---|
| 511 | When all tests have been added, the suite can be passed to a test |
---|
| 512 | runner, such as TextTestRunner. It will run the individual test cases |
---|
| 513 | in the order in which they were added, aggregating the results. When |
---|
| 514 | subclassing, do not forget to call the base class constructor. |
---|
| 515 | """ |
---|
| 516 | def __init__(self, tests=(), suiteName=None): |
---|
| 517 | self._tests = [] |
---|
| 518 | self._testMap = {} |
---|
| 519 | self.suiteName = suiteName |
---|
| 520 | self.addTests(tests) |
---|
| 521 | |
---|
| 522 | def __repr__(self): |
---|
| 523 | return "<%s tests=%s>" % (self.__class__, pprint.pformat(self._tests)) |
---|
| 524 | |
---|
| 525 | __str__ = __repr__ |
---|
| 526 | |
---|
| 527 | def countTestCases(self): |
---|
| 528 | cases = 0 |
---|
| 529 | for test in self._tests: |
---|
| 530 | cases = cases + test.countTestCases() |
---|
| 531 | return cases |
---|
| 532 | |
---|
| 533 | def addTest(self, test): |
---|
| 534 | self._tests.append(test) |
---|
| 535 | if isinstance(test, TestSuite) and test.suiteName: |
---|
| 536 | name = test.suiteName |
---|
| 537 | elif isinstance(test, TestCase): |
---|
| 538 | #print test, test._testMethodName |
---|
| 539 | name = test._testMethodName |
---|
| 540 | else: |
---|
| 541 | name = test.__class__.__name__ |
---|
| 542 | self._testMap[name] = test |
---|
| 543 | |
---|
| 544 | def addTests(self, tests): |
---|
| 545 | for test in tests: |
---|
| 546 | self.addTest(test) |
---|
| 547 | |
---|
| 548 | def getTestForName(self, name): |
---|
| 549 | return self._testMap[name] |
---|
| 550 | |
---|
| 551 | def run(self, result): |
---|
| 552 | return self(result) |
---|
| 553 | |
---|
| 554 | def __call__(self, result): |
---|
| 555 | for test in self._tests: |
---|
| 556 | if result.shouldStop: |
---|
| 557 | break |
---|
| 558 | test(result) |
---|
| 559 | return result |
---|
| 560 | |
---|
| 561 | def debug(self): |
---|
| 562 | """Run the tests without collecting errors in a TestResult""" |
---|
| 563 | for test in self._tests: test.debug() |
---|
| 564 | |
---|
| 565 | |
---|
| 566 | ############################################################################## |
---|
| 567 | # Text UI |
---|
| 568 | ############################################################################## |
---|
| 569 | |
---|
| 570 | class StreamWrapper: |
---|
| 571 | def __init__(self, out=sys.stdout, err=sys.stderr): |
---|
| 572 | self._streamOut = out |
---|
| 573 | self._streamErr = err |
---|
| 574 | |
---|
| 575 | def write(self, txt): |
---|
| 576 | self._streamOut.write(txt) |
---|
| 577 | self._streamOut.flush() |
---|
| 578 | |
---|
| 579 | def writeln(self, *lines): |
---|
| 580 | for line in lines: |
---|
| 581 | self.write(line + '\n') |
---|
| 582 | if not lines: |
---|
| 583 | self.write('\n') |
---|
| 584 | |
---|
| 585 | def writeErr(self, txt): |
---|
| 586 | self._streamErr.write(txt) |
---|
| 587 | |
---|
| 588 | def writelnErr(self, *lines): |
---|
| 589 | for line in lines: |
---|
| 590 | self.writeErr(line + '\n') |
---|
| 591 | if not lines: |
---|
| 592 | self.writeErr('\n') |
---|
| 593 | |
---|
| 594 | |
---|
| 595 | class _TextTestResult(TestResult, StreamWrapper): |
---|
| 596 | _separatorWidth = 70 |
---|
| 597 | _sep1 = '=' |
---|
| 598 | _sep2 = '-' |
---|
| 599 | _errorSep1 = '*' |
---|
| 600 | _errorSep2 = '-' |
---|
| 601 | _errorSep3 = '' |
---|
| 602 | |
---|
| 603 | def __init__(self, |
---|
| 604 | stream=sys.stdout, |
---|
| 605 | errStream=sys.stderr, |
---|
| 606 | verbosity=1, |
---|
| 607 | explain=False): |
---|
| 608 | |
---|
| 609 | TestResult.__init__(self) |
---|
| 610 | StreamWrapper.__init__(self, out=stream, err=errStream) |
---|
| 611 | |
---|
| 612 | self._verbosity = verbosity |
---|
| 613 | self._showAll = verbosity > 1 |
---|
| 614 | self._dots = (verbosity == 1) |
---|
| 615 | self._explain = explain |
---|
| 616 | |
---|
| 617 | ## startup and shutdown methods |
---|
| 618 | |
---|
| 619 | def beginTests(self): |
---|
| 620 | self._startTime = time.time() |
---|
| 621 | |
---|
| 622 | def endTests(self): |
---|
| 623 | self._stopTime = time.time() |
---|
| 624 | self._timeTaken = float(self._stopTime - self._startTime) |
---|
| 625 | |
---|
| 626 | def stop(self): |
---|
| 627 | self.shouldStop = 1 |
---|
| 628 | |
---|
| 629 | ## methods called for each test |
---|
| 630 | |
---|
| 631 | def startTest(self, test): |
---|
| 632 | TestResult.startTest(self, test) |
---|
| 633 | if self._showAll: |
---|
| 634 | self.write("%s (%s)" %( test.id(), test.describe() ) ) |
---|
| 635 | self.write(" ... ") |
---|
| 636 | |
---|
| 637 | def addSuccess(self, test): |
---|
| 638 | TestResult.addSuccess(self, test) |
---|
| 639 | if self._showAll: |
---|
| 640 | self.writeln("ok") |
---|
| 641 | elif self._dots: |
---|
| 642 | self.write('.') |
---|
| 643 | |
---|
| 644 | def addError(self, test, err): |
---|
| 645 | TestResult.addError(self, test, err) |
---|
| 646 | if self._showAll: |
---|
| 647 | self.writeln("ERROR") |
---|
| 648 | elif self._dots: |
---|
| 649 | self.write('E') |
---|
| 650 | if err[0] is KeyboardInterrupt: |
---|
| 651 | self.stop() |
---|
| 652 | |
---|
| 653 | def addFailure(self, test, err): |
---|
| 654 | TestResult.addFailure(self, test, err) |
---|
| 655 | if self._showAll: |
---|
| 656 | self.writeln("FAIL") |
---|
| 657 | elif self._dots: |
---|
| 658 | self.write('F') |
---|
| 659 | |
---|
| 660 | ## display methods |
---|
| 661 | |
---|
| 662 | def summarize(self): |
---|
| 663 | self.printErrors() |
---|
| 664 | self.writeSep2() |
---|
| 665 | run = self.testsRun |
---|
| 666 | self.writeln("Ran %d test%s in %.3fs" % |
---|
| 667 | (run, run == 1 and "" or "s", self._timeTaken)) |
---|
| 668 | self.writeln() |
---|
| 669 | if not self.wasSuccessful(): |
---|
| 670 | self.writeErr("FAILED (") |
---|
| 671 | failed, errored = map(len, (self.failures, self.errors)) |
---|
| 672 | if failed: |
---|
| 673 | self.writeErr("failures=%d" % failed) |
---|
| 674 | if errored: |
---|
| 675 | if failed: self.writeErr(", ") |
---|
| 676 | self.writeErr("errors=%d" % errored) |
---|
| 677 | self.writelnErr(")") |
---|
| 678 | else: |
---|
| 679 | self.writelnErr("OK") |
---|
| 680 | |
---|
| 681 | def writeSep1(self): |
---|
| 682 | self.writeln(self._sep1 * self._separatorWidth) |
---|
| 683 | |
---|
| 684 | def writeSep2(self): |
---|
| 685 | self.writeln(self._sep2 * self._separatorWidth) |
---|
| 686 | |
---|
| 687 | def writeErrSep1(self): |
---|
| 688 | self.writeln(self._errorSep1 * self._separatorWidth) |
---|
| 689 | |
---|
| 690 | def writeErrSep2(self): |
---|
| 691 | self.writeln(self._errorSep2 * self._separatorWidth) |
---|
| 692 | |
---|
| 693 | def printErrors(self): |
---|
| 694 | if self._dots or self._showAll: |
---|
| 695 | self.writeln() |
---|
| 696 | self.printErrorList('ERROR', self.errors) |
---|
| 697 | self.printErrorList('FAIL', self.failures) |
---|
| 698 | |
---|
| 699 | def printErrorList(self, flavour, errors): |
---|
| 700 | for test, err in errors: |
---|
| 701 | self.writeErrSep1() |
---|
| 702 | self.writelnErr("%s %s (%s)" % (flavour, test.id(), test.describe() )) |
---|
| 703 | if self._explain: |
---|
| 704 | expln = test.explain() |
---|
| 705 | if expln: |
---|
| 706 | self.writeErrSep2() |
---|
| 707 | self.writeErr( expln ) |
---|
| 708 | self.writelnErr() |
---|
| 709 | |
---|
| 710 | self.writeErrSep2() |
---|
| 711 | for line in apply(traceback.format_exception, err): |
---|
| 712 | for l in line.split("\n")[:-1]: |
---|
| 713 | self.writelnErr(l) |
---|
| 714 | self.writelnErr("") |
---|
| 715 | |
---|
| 716 | class TextTestRunner: |
---|
| 717 | def __init__(self, |
---|
| 718 | stream=sys.stdout, |
---|
| 719 | errStream=sys.stderr, |
---|
| 720 | verbosity=1, |
---|
| 721 | explain=False): |
---|
| 722 | |
---|
| 723 | self._out = stream |
---|
| 724 | self._err = errStream |
---|
| 725 | self._verbosity = verbosity |
---|
| 726 | self._explain = explain |
---|
| 727 | |
---|
| 728 | ## main methods |
---|
| 729 | |
---|
| 730 | def run(self, test): |
---|
| 731 | result = self._makeResult() |
---|
| 732 | result.beginTests() |
---|
| 733 | test( result ) |
---|
| 734 | result.endTests() |
---|
| 735 | result.summarize() |
---|
| 736 | |
---|
| 737 | return result |
---|
| 738 | |
---|
| 739 | ## internal methods |
---|
| 740 | |
---|
| 741 | def _makeResult(self): |
---|
| 742 | return _TextTestResult(stream=self._out, |
---|
| 743 | errStream=self._err, |
---|
| 744 | verbosity=self._verbosity, |
---|
| 745 | explain=self._explain, |
---|
| 746 | ) |
---|
| 747 | |
---|
| 748 | ############################################################################## |
---|
| 749 | # Locating and loading tests |
---|
| 750 | ############################################################################## |
---|
| 751 | |
---|
| 752 | class TestLoader: |
---|
| 753 | """This class is responsible for loading tests according to various |
---|
| 754 | criteria and returning them wrapped in a Test |
---|
| 755 | """ |
---|
| 756 | testMethodPrefix = 'test' |
---|
| 757 | sortTestMethodsUsing = cmp |
---|
| 758 | suiteClass = TestSuite |
---|
| 759 | |
---|
| 760 | def loadTestsFromTestCase(self, testCaseClass): |
---|
| 761 | """Return a suite of all tests cases contained in testCaseClass""" |
---|
| 762 | return self.suiteClass(tests=map(testCaseClass, |
---|
| 763 | self.getTestCaseNames(testCaseClass)), |
---|
| 764 | suiteName=testCaseClass.__name__) |
---|
| 765 | |
---|
| 766 | def loadTestsFromModule(self, module): |
---|
| 767 | """Return a suite of all tests cases contained in the given module""" |
---|
| 768 | tests = [] |
---|
| 769 | for name in dir(module): |
---|
| 770 | obj = getattr(module, name) |
---|
| 771 | if type(obj) == types.ClassType and issubclass(obj, TestCase): |
---|
| 772 | tests.append(self.loadTestsFromTestCase(obj)) |
---|
| 773 | return self.suiteClass(tests) |
---|
| 774 | |
---|
| 775 | def loadTestsFromName(self, name, module=None): |
---|
| 776 | """Return a suite of all tests cases given a string specifier. |
---|
| 777 | |
---|
| 778 | The name may resolve either to a module, a test case class, a |
---|
| 779 | test method within a test case class, or a callable object which |
---|
| 780 | returns a TestCase or TestSuite instance. |
---|
| 781 | |
---|
| 782 | The method optionally resolves the names relative to a given module. |
---|
| 783 | """ |
---|
| 784 | parts = string.split(name, '.') |
---|
| 785 | if module is None: |
---|
| 786 | if not parts: |
---|
| 787 | raise ValueError, "incomplete test name: %s" % name |
---|
| 788 | else: |
---|
| 789 | parts_copy = parts[:] |
---|
| 790 | while parts_copy: |
---|
| 791 | try: |
---|
| 792 | module = __import__(string.join(parts_copy,'.')) |
---|
| 793 | break |
---|
| 794 | except ImportError: |
---|
| 795 | del parts_copy[-1] |
---|
| 796 | if not parts_copy: raise |
---|
| 797 | parts = parts[1:] |
---|
| 798 | obj = module |
---|
| 799 | for part in parts: |
---|
| 800 | if isinstance(obj, TestSuite): |
---|
| 801 | obj = obj.getTestForName(part) |
---|
| 802 | else: |
---|
| 803 | obj = getattr(obj, part) |
---|
| 804 | |
---|
| 805 | if type(obj) == types.ModuleType: |
---|
| 806 | return self.loadTestsFromModule(obj) |
---|
| 807 | elif type(obj) == types.ClassType and issubclass(obj, TestCase): |
---|
| 808 | return self.loadTestsFromTestCase(obj) |
---|
| 809 | elif type(obj) == types.UnboundMethodType: |
---|
| 810 | return obj.im_class(obj.__name__) |
---|
| 811 | elif isinstance(obj, TestSuite): |
---|
| 812 | return obj |
---|
| 813 | elif isinstance(obj, TestCase): |
---|
| 814 | return obj |
---|
| 815 | elif callable(obj): |
---|
| 816 | test = obj() |
---|
| 817 | if not isinstance(test, TestCase) and \ |
---|
| 818 | not isinstance(test, TestSuite): |
---|
| 819 | raise ValueError, \ |
---|
| 820 | "calling %s returned %s, not a test" %(obj,test) |
---|
| 821 | return test |
---|
| 822 | else: |
---|
| 823 | raise ValueError, "don't know how to make test from: %s" % obj |
---|
| 824 | |
---|
| 825 | def loadTestsFromNames(self, names, module=None): |
---|
| 826 | """Return a suite of all tests cases found using the given sequence |
---|
| 827 | of string specifiers. See 'loadTestsFromName()'. |
---|
| 828 | """ |
---|
| 829 | suites = [] |
---|
| 830 | for name in names: |
---|
| 831 | suites.append(self.loadTestsFromName(name, module)) |
---|
| 832 | return self.suiteClass(suites) |
---|
| 833 | |
---|
| 834 | def getTestCaseNames(self, testCaseClass): |
---|
| 835 | """Return a sorted sequence of method names found within testCaseClass. |
---|
| 836 | """ |
---|
| 837 | testFnNames = [fn for fn in dir(testCaseClass) if fn.startswith(self.testMethodPrefix)] |
---|
| 838 | if hasattr(testCaseClass, 'runTest'): |
---|
| 839 | testFnNames.append('runTest') |
---|
| 840 | for baseclass in testCaseClass.__bases__: |
---|
| 841 | for testFnName in self.getTestCaseNames(baseclass): |
---|
| 842 | if testFnName not in testFnNames: # handle overridden methods |
---|
| 843 | testFnNames.append(testFnName) |
---|
| 844 | if self.sortTestMethodsUsing: |
---|
| 845 | testFnNames.sort(self.sortTestMethodsUsing) |
---|
| 846 | return testFnNames |
---|
| 847 | |
---|
| 848 | |
---|
| 849 | |
---|
| 850 | defaultTestLoader = TestLoader() |
---|
| 851 | |
---|
| 852 | |
---|
| 853 | ############################################################################## |
---|
| 854 | # Patches for old functions: these functions should be considered obsolete |
---|
| 855 | ############################################################################## |
---|
| 856 | |
---|
| 857 | def _makeLoader(prefix, sortUsing, suiteClass=None): |
---|
| 858 | loader = TestLoader() |
---|
| 859 | loader.sortTestMethodsUsing = sortUsing |
---|
| 860 | loader.testMethodPrefix = prefix |
---|
| 861 | if suiteClass: loader.suiteClass = suiteClass |
---|
| 862 | return loader |
---|
| 863 | |
---|
| 864 | def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): |
---|
| 865 | return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) |
---|
| 866 | |
---|
| 867 | def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): |
---|
| 868 | return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) |
---|
| 869 | |
---|
| 870 | def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): |
---|
| 871 | return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) |
---|
| 872 | |
---|
| 873 | ############################################################################## |
---|
| 874 | # Facilities for running tests from the command line |
---|
| 875 | ############################################################################## |
---|
| 876 | |
---|
| 877 | class TestProgram: |
---|
| 878 | """A command-line program that runs a set of tests; this is primarily |
---|
| 879 | for making test modules conveniently executable. |
---|
| 880 | """ |
---|
| 881 | USAGE = """\ |
---|
| 882 | Usage: %(progName)s [options] [test] [...] |
---|
| 883 | |
---|
| 884 | Options: |
---|
| 885 | -h, --help Show this message |
---|
| 886 | -v, --verbose Verbose output |
---|
| 887 | -q, --quiet Minimal output |
---|
| 888 | -e, --expain Output extra test details if there is a failure or error |
---|
| 889 | |
---|
| 890 | Examples: |
---|
| 891 | %(progName)s - run default set of tests |
---|
| 892 | %(progName)s MyTestSuite - run suite 'MyTestSuite' |
---|
| 893 | %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite' |
---|
| 894 | %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething |
---|
| 895 | %(progName)s MyTestCase - run all 'test*' test methods |
---|
| 896 | in MyTestCase |
---|
| 897 | """ |
---|
| 898 | def __init__(self, module='__main__', defaultTest=None, |
---|
| 899 | argv=None, testRunner=None, testLoader=defaultTestLoader, |
---|
| 900 | testSuite=None): |
---|
| 901 | if type(module) == type(''): |
---|
| 902 | self.module = __import__(module) |
---|
| 903 | for part in string.split(module,'.')[1:]: |
---|
| 904 | self.module = getattr(self.module, part) |
---|
| 905 | else: |
---|
| 906 | self.module = module |
---|
| 907 | if argv is None: |
---|
| 908 | argv = sys.argv |
---|
| 909 | self.test = testSuite |
---|
| 910 | self.verbosity = 1 |
---|
| 911 | self.explain = 0 |
---|
| 912 | self.defaultTest = defaultTest |
---|
| 913 | self.testRunner = testRunner |
---|
| 914 | self.testLoader = testLoader |
---|
| 915 | self.progName = os.path.basename(argv[0]) |
---|
| 916 | self.parseArgs(argv) |
---|
| 917 | self.runTests() |
---|
| 918 | |
---|
| 919 | def usageExit(self, msg=None): |
---|
| 920 | if msg: print msg |
---|
| 921 | print self.USAGE % self.__dict__ |
---|
| 922 | sys.exit(2) |
---|
| 923 | |
---|
| 924 | def parseArgs(self, argv): |
---|
| 925 | import getopt |
---|
| 926 | try: |
---|
| 927 | options, args = getopt.getopt(argv[1:], 'hHvqer', |
---|
| 928 | ['help','verbose','quiet','explain', 'raise']) |
---|
| 929 | for opt, value in options: |
---|
| 930 | if opt in ('-h','-H','--help'): |
---|
| 931 | self.usageExit() |
---|
| 932 | if opt in ('-q','--quiet'): |
---|
| 933 | self.verbosity = 0 |
---|
| 934 | if opt in ('-v','--verbose'): |
---|
| 935 | self.verbosity = 2 |
---|
| 936 | if opt in ('-e','--explain'): |
---|
| 937 | self.explain = True |
---|
| 938 | if len(args) == 0 and self.defaultTest is None and self.test is None: |
---|
| 939 | self.test = self.testLoader.loadTestsFromModule(self.module) |
---|
| 940 | return |
---|
| 941 | if len(args) > 0: |
---|
| 942 | self.testNames = args |
---|
| 943 | else: |
---|
| 944 | self.testNames = (self.defaultTest,) |
---|
| 945 | self.createTests() |
---|
| 946 | except getopt.error, msg: |
---|
| 947 | self.usageExit(msg) |
---|
| 948 | |
---|
| 949 | def createTests(self): |
---|
| 950 | if self.test == None: |
---|
| 951 | self.test = self.testLoader.loadTestsFromNames(self.testNames, |
---|
| 952 | self.module) |
---|
| 953 | |
---|
| 954 | def runTests(self): |
---|
| 955 | if self.testRunner is None: |
---|
| 956 | self.testRunner = TextTestRunner(verbosity=self.verbosity, |
---|
| 957 | explain=self.explain) |
---|
| 958 | result = self.testRunner.run(self.test) |
---|
| 959 | self._cleanupAfterRunningTests() |
---|
| 960 | sys.exit(not result.wasSuccessful()) |
---|
| 961 | |
---|
| 962 | def _cleanupAfterRunningTests(self): |
---|
| 963 | """A hook method that is called immediately prior to calling |
---|
| 964 | sys.exit(not result.wasSuccessful()) in self.runTests(). |
---|
| 965 | """ |
---|
| 966 | pass |
---|
| 967 | |
---|
| 968 | main = TestProgram |
---|
| 969 | |
---|
| 970 | |
---|
| 971 | ############################################################################## |
---|
| 972 | # Executing this module from the command line |
---|
| 973 | ############################################################################## |
---|
| 974 | |
---|
| 975 | if __name__ == "__main__": |
---|
| 976 | main(module=None) |
---|
| 977 | |
---|
| 978 | # vim: shiftwidth=4 tabstop=4 expandtab |
---|