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

When building unit tests for modules many times using PyUnit feels like overkill. This is a simple implementation for testing single file modules.

Python, 118 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
#! /usr/bin/env python
######################################################################
#  Written by Kevin L. Sitze on 2010-12-03
#  This code may be used pursuant to the MIT License.
######################################################################

import sys
import traceback
from types import FloatType, ComplexType

__all__ = ( 
    'assertEquals',
    'assertNotEquals',
    'assertException',
    'assertFalse',
    'assertNone',
    'assertNotNone',
    'assertSame',
    'assertNotSame',
    'assertTrue'
)

def colon( msg ):
    if msg:
        return ": " + str( msg )
    else:
        return ""

def assertEquals( exp, got, msg = None ):
    """assertEquals( exp, got[, message] )

    Two objects test as "equal" if:
    
    * they are the same object as tested by the 'is' operator.
    * either object is a float or complex number and the absolute
      value of the difference between the two is less than 1e-8.
    * applying the equals operator ('==') returns True.
    """
    if exp is got:
        r = True
    elif ( type( exp ) in ( FloatType, ComplexType ) or
           type( got ) in ( FloatType, ComplexType ) ):
        r = abs( exp - got ) < 1e-8
    else:
        r = ( exp == got )
    if not r:
        print >>sys.stderr, "Error: expected <%s> but got <%s>%s" % ( repr( exp ), repr( got ), colon( msg ) )
        traceback.print_stack()

def assertNotEquals( exp, got, msg = None ):
    """assertNotEquals( exp, got[, message] )

    Two objects test as "equal" if:
    
    * they are the same object as tested by the 'is' operator.
    * either object is a float or complex number and the absolute
      value of the difference between the two is less than 1e-8.
    * applying the equals operator ('==') returns True.
    """
    if exp is got:
        r = False
    elif ( type( exp ) in ( FloatType, ComplexType ) or
           type( got ) in ( FloatType, ComplexType ) ):
        r = abs( exp - got ) >= 1e-8
    else:
        r = ( exp != got )
    if not r:
        print >>sys.stderr, "Error: expected different values but both are equal to <%s>%s" % ( repr( exp ), colon( msg ) )
        traceback.print_stack()

def assertException( exceptionType, f, msg = None ):
    """Assert that an exception of type \var{exceptionType}
    is thrown when the function \var{f} is evaluated.
    """
    try:
        f()
    except exceptionType:
        assert True
    else:
        print >>sys.stderr, "Error: expected <%s> to be thrown by function%s" % ( exceptionType.__name__, colon( msg ) )
        traceback.print_stack()

def assertFalse( b, msg = None ):
    """assertFalse( b[, message] )
    """
    if b:
        print >>sys.stderr, "Error: expected value to be False%s" % colon( msg )
        traceback.print_stack()

def assertNone( x, msg = None ):
    assertSame( None, x, msg )

def assertNotNone( x, msg = None ):
    assertNotSame( None, x, msg )

def assertSame( exp, got, msg = None ):
    if got is not exp:
        print >>sys.stderr, "Error: expected <%s> to be the same object as <%s>%s" % ( repr( exp ), repr( got ), colon( msg ) )
        traceback.print_stack()

def assertNotSame( exp, got, msg = None ):
    if got is exp:
        print >>sys.stderr, "Error: expected two distinct objects but both are the same object <%s>%s" % ( repr( exp ), colon( msg ) )
        traceback.print_stack()

def assertTrue( b, msg = None ):
    if not b:
        print >>sys.stderr, "Error: expected value to be True%s" % colon( msg )
        traceback.print_stack()

if __name__ == "__main__":
    assertNone( None )
    assertEquals( 5, 5 )
    assertException( KeyError, lambda: {}['test'] )

    assertNone( 5, 'this assertion is expected' )
    assertEquals( 5, 6, 'this assertion is expected' )
    assertException( KeyError, lambda: {}, 'this assertion is expected' )

You may have noticed in some of my other recipes that I commonly place unit tests for the module at the bottom of the file. Those tests originally used this code, however I simply cut and pasted the appropriate "assert" functions out of this file into those recipes to help them be more self-contained.

This is how the original code looked:

if __name__ == '__main__':
    from assertions import *
    assertEquals( ... )
    ...

This is how your modules also can look.

I despise how heavy-handed PyUnit is when working with a single file. Having these functions outside of a class and stand-alone helps immensely when building single purpose library modules.

My hope is that making writing tests simpler will encourage more recipe authors to add tests to their code.