Extends pyunit with method decorators, allowing the programmer to 'anotate' test methods and expected exceptions, rather than having to adhere to a naming convention.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | #!/usr/bin/env python
"""
pyunit2.py - extends the facilities in the unittest module (PyUnit ),
which is supplied with the Python Standard Library (since version 2.1,
see the unittest.py module or go to the original project home page at
http://pyunit.sourceforge.net/pyunit.html for more info).
Example:
#!/usr/bin/env python
from pyunit2 import * #includes class Fixture
class ExampleTestFixture(Fixture):
@test
def anExampleTest(self):
self.assertEqual(1, 2) #fails
@test
@expected_exception(NameError)
def usingExpectedExceptions(self):
class Foo: pass
f = Foo()
print f.non_existent_attribute
if __name__ == "__main__":
#the following line(s) equate to
#import unittest
#unittest.main()
#... etc
import pyunit2
pyunit2.main()
"""
__author__ = "Tim Watson"
__version__ = "$Revision: 0.2 $"
__license__ = "Python"
##############################################################################
# Exported classes and functions
##############################################################################
__all__ = [
'expected_exception',
'test',
'Fixture'
]
import unittest
##############################################################################
# utility classes
##############################################################################
class TestDeclaration( object ):
declarations = {}
def __init__( self, func, doc_string=None ):
if func.func_name.startswith( "test" ): return
func.__doc__ = doc_string or func.__doc__
fname = "test_%s" % func.func_name
if not fname in TestDeclaration.declarations:
TestDeclaration.declarations[ fname ] = func
def __call__( self, func ):
if func.func_name.startswith( "test" ):
def execute( *args, **kwargs ):
return func( *args, **kwargs )
return execute
raise Exception( 'should not have arrived here!?' )
class ExpectedException( object ):
def __init__( self, exception_class ):
self.exception_class = exception_class
def __call__( self, func ):
def execute( *args, **kwargs ):
self_pointer = args [ 0 ]
assert not self_pointer is None
self_pointer.assertRaises( self.exception_class,
func, *args, **kwargs )
return execute
##############################################################################
# replacement base class 'Fixture' and supporting meta-class(es)
##############################################################################
class TestFixtureManager( type ):
"""
A meta-class for mapping the decorator based test syntax
from this module, to standard PyUnit style test method names, etc.
"""
def __new__( cls, name, bases, attrs ):
new_class = type.__new__( cls, name, bases, attrs )
if not bases: return new_class
[ setattr( new_class, func_name, func )
for func_name, func in TestDeclaration.declarations.iteritems() ]
TestDeclaration.declarations.clear()
return new_class
def __init__( cls, name, bases, dict ):
#todo: deal with fixture setup/teardown here!
pass
class Fixture( unittest.TestCase ): __metaclass__ = TestFixtureManager
##############################################################################
# decorators to make declaring tests/expected exceptions easier!
##############################################################################
def test( func, doc_string=None ):
"""
Marks a method as a test method. Removes the need
to call all your test methods testXXX and so on.
"""
return TestDeclaration( func, doc_string )
def expected_exception( ex_cls ):
"""
Marks a method as expecting an exception of class -> ex_cls
"""
return ExpectedException( ex_cls )
##############################################################################
# support for fixture wide setup/teardown (NOT IMPLEMENTED YET)
##############################################################################
def testFixtureSetUp():
"""
Adds support for a setup method that runs
only once per testcase/fixture. The method must be defined
as a staticmethod.
"""
pass
def testFixtureTearDown():
"""
Adds support for a teardown method that runs
only once per testcase/fixture. The method should be defined
as a staticmethod.
"""
pass
def main():
unittest.main()
if __name__ == "__main__":
main()
|
This syntactic sugar is probably a case of personal preference at the moment, but will (hopefully) support the easy creation of a Behaviour Driven Development toolkit (along the lines of RSpec) some time in the future.
Tags: oop