Python Unit Testing Example

Russell Bateman
July 2016
last update:

Here's a complete, if small, example of unit testing in Python without any mocking. (Python unit testing with mocking is a different and more complicated demonstration.) The haters will say my Python code doesn't look like Python. Like I care. The world of naming and formatting has moved on since the C Programming Language that was in vogue back when Python was created. So sue me.

One day, I was writing a utility to validate file formats that will be greatly expanded, but preliminarily, will need to validate that a file (or a string) is valid JSON. As I haven't written much in Python over the last few months (I'm a Java guy), I thought I'd add this to my notes as a basic example before it gets seriously bigger.

json_validate.py

The test subject.

import sys
import json

def main( argv ):
    pass           // (TODO: needs to sort out argument and call validator)

def validateFileAsJson( filepath=None ):
    try:
        with open( filepath, 'r' ) as f:
            jsonDict = json.load( f )
    except Exception as e:
        return False
    return True

def validateStringAsJson( string=None ):
    try:
        jsonDict = json.loads( string )
    except Exception as e:
        return False
    return True

if __name__ == "__main__":
    if len( sys.argv ) <= 1:
        sys.exit( main( '--help' ) )
    elif len( sys.argv ) >= 1:
        sys.exit( main( sys.argv ) )
json_validate_test.py

The unit-test code—why you're even looking at these notes).

import sys
import unittest
import json_validate
import testutilities

PRINT = True            # change to False when finished debugging...

GOOD_JSON = '{ "json" : "This is a JSON" }'
BAD_JSON  = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?><json> This isn\'t JSON </json>'

class JsonValidateTest( unittest.TestCase ):
    """
    Test json_validate.py. Use https://jsonformatter.curiousconcept.com/#jsonformatter to
    create suitable (valid) and invalid JSON fodder.
    """
    @classmethod
    def setUpClass( JsonValidateTest ):
        testutilities.turnOnPrinting( PRINT )

    def setUp( self ):
        pass

    def tearDown( self ):
        pass

    def testGoodFileAsJson( self ):
        testutilities.printTestCaseName( sys._getframe().f_code.co_name )
        temp = testutilities.createTemporaryFile( '.json', GOOD_JSON )
        result = json_validate.validateFileAsJson( temp )
        testutilities.eraseTemporaryFile( temp )
        self.assertTrue( result )

    def testBadFileAsJson( self ):
        testutilities.printTestCaseName( sys._getframe().f_code.co_name )
        temp = testutilities.createTemporaryFile( '.json', BAD_JSON )
        result = json_validate.validateFileAsJson( temp )
        testutilities.eraseTemporaryFile( temp )
        self.assertFalse( result )

    def testGoodStringAsJson( self ):
        testutilities.printTestCaseName( sys._getframe().f_code.co_name )
        result = json_validate.validateStringAsJson( GOOD_JSON )
        self.assertTrue( result )

    def testBadStringAsJson( self ):
        testutilities.printTestCaseName( sys._getframe( ).f_code.co_name )
        result = json_validate.validateStringAsJson( BAD_JSON )
        self.assertFalse( result )

if __name__ == '__main__':
    unittest.main()
testutilities.py

Some test utilities I like to carry around. (This is a simplified subset.)

import os
import sys
import tempfile

PRINT = False

def spinCommandLine( scriptName=None, commandLine=None ):
    ''' This will turn test with spaces into a command line as if sys.argv. '''
    sys_argv = []
    if not commandLine:
        return sys_argv
    sys_argv.append( scriptName )
    sys_argv.extend( commandLine.split( ' ' ) )
    return sys_argv

def createTemporaryFile( extension=None, contents=None ):
    if extension:
        (fd, path) = tempfile.mkstemp( suffix=extension )
    else:
        (fd, path) = tempfile.mkstemp( suffix='.tmp' )
    if contents:
        os.write( fd, contents )
    os.close( fd )
    return path

def readTemporaryFileAsString( path=None ):
    if not path:
        return ''
    string = ''
    try:
        with open( path, 'r' ) as f:
            for line in f:
                string = string + line
    except Exception as e:
        print( 'I/O operation failed on temporary file: %s' % e )

def eraseTemporaryFile( path=None ):
    if not path:
        return
    os.remove( path )

def turnOnPrinting( enable=False ):
    PRINT = enable

def printOrNot( thing='' ):
    if not PRINT:
        return
    print thing

CONSOLE_WIDTH = 80

def printTestCaseName( functionName ):
    '''
    Call thus:
    printTestCaseName( sys._getframe().f_code.co_name )
    --helps you sort through unit test output by creating a banner.
    '''
    if not PRINT:
        return
    banner = '\nRunning test case %s ' % functionName
    length = len( banner )
    sys.stdout.write( banner )
    for col in range( length, CONSOLE_WIDTH ):
        sys.stdout.write( '-' )
    sys.stdout.write( '\n' )
    sys.stdout.flush()