Welcome, guest | Sign In | My Account | Store | Cart

Extends pyunit with method decorators, allowing the programmer to 'anotate' test methods and expected exceptions, rather than having to adhere to a naming convention.

Python, 146 lines
  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.