Creating unit test template files

The bare-bones starting point for the collection of tests just shown would work as a starting point for any other collection of tests that are concerned with a single module. The expected code structure for hms_sys, however, includes whole packages of code, and may include packages inside those packages. We don't know yet, because we haven't gotten that far. That's going to have an impact on the final unit testing approach, as well as on the creation of template files to make the creation of those test modules faster and less error-prone.

The main impact is centered around the idea that we want to be able to execute all of the tests for an entire project with a single call, while at the same time not being required to execute every test in the component project's test suite in cases where the interest is in one or more tests running against something deeper in the package structure. It would make sense, then, to break the tests out in the same sort of organizational structure as the package that they are testing, and allow test modules at any level to import child tests when they are called or imported themselves by a parent higher Up the module tree.

To that end, the template module for unit tests needs to accommodate the same sort of import capabilities that the main code base does, while keeping track of all the tests that result from whatever import process originated with the test run. Fortunately, the unittest module also provides classes that can be used to manage that need, such as the TestSuite class, which is a collection of tests that can be executed and that can have new tests added to it as needed. The final test module template looks much like the module template we created earlier, though it starts with some search-and-replace boilerplate comments:

#!/usr/bin/env python

# Python unit-test-module template. Copy the template to a new
# unit-test-module location, and start replacing names as needed:
#
# PackagePath  ==> The path/namespace of the parent of the module/package
#                  being tested in this file.
# ModuleName   ==> The name of the module being tested
#
# Then remove this comment-block

"""
Defines unit-tests for the module at PackagePath.ModuleName.
"""

#######################################
# Any needed from __future__ imports  #
# Create an "__all__" list to support #
#   "from module import member" use   #
#######################################

Unlike the packages and modules that provide application functionality, the unit test module template doesn't expect or need to provide much in the way of all entries—only the test case classes that reside in the module itself, and any child test modules:

__all__ = [
    # Test-case classes
    # Child test-modules
]

There are a few standard imports that will occur in all test modules, and there is the potential for third-party imports as well, though that's probably not going to be common:

#######################################
# Standard library imports needed     #
#######################################

import os
import sys
import unittest

#######################################
# Third-party imports needed          #
#######################################

#######################################
# Local imports needed                #
#######################################

from unit_testing import *

#######################################
# Initialization needed before member #
#   definition can take place         #
#######################################

All the test modules will define a unittest.TestSuite instance named LocalSuite, which contains all of the local test cases and can be imported by name in parent modules when needed:

#######################################
# Module-level Constants              #
#######################################

LocalSuite = unittest.TestSuite()

#######################################
# Import the module being tested      #
#######################################

import PackagePath.ModuleName as ModuleName

We'll also define boilerplate code that defines the code coverage test case class:

#######################################
# Code-coverage test-case and         #
# decorator-methods                   #
#######################################

class testModuleNameCodeCoverage(ModuleCoverageTest):
    _testModule = ModuleName

LocalSuite.addTests(
    unittest.TestLoader().loadTestsFromTestCase(
        testModuleNameCodeCoverage
   )
)

From this point on, everything that isn't part of the __main__ execution of the module should be definitions of the test case classes:

#######################################
# Test-cases in the module            #
#######################################

#######################################
# Child-module test-cases to execute  #
#######################################

If child test modules need to be imported later on, the code structure for doing so is here, commented out and ready to copy, paste, uncomment, and rename as needed:

# import child_module
# LocalSuite.addTests(child_module.LocalSuite._tests)

There more standard module sections, following the organization structure of the standard module and package templates:

#######################################
# Imports to resolve circular         #
# dependencies. Avoid if possible.    #
#######################################

#######################################
# Initialization that needs to        #
# happen after member definition.     #
#######################################

#######################################
# Code to execute if file is called   #
# or run directly.                    #
#######################################

Finally, there's some provision for executing the module directly, running the tests, and displaying and writing out the reports when no failures occur:

if __name__ == '__main__':
    import time
    results = unittest.TestResult()
    testStartTime = time.time()
    LocalSuite.run(results)
    results.runTime = time.time() - testStartTime
    PrintTestResults(results)
    if not results.errors and not results.failures:
        SaveTestReport(results, 'PackagePath.ModuleName',
            'PackagePath.ModuleName.test-results')

The template provides a handful of items that can be found and replaced when it's first copied to a final test module:

  • PackagePath: The full namespace to the module being tested, minus the module itself. For example, if a test module was being created for a module whose full namespace was hms_core.business.processes.artisanthe PackagePath would be hms_core.business.processes

  • ModuleName: The name of the module being tested (artisan, using the preceding example)

That search-and-replace operation will also provide a unique name for the ModuleCoverageTest subclass definition that's embedded in the template. As soon as those replacements are completed, the test module can be run, as shown in the preceding example, and will start reporting on missing test cases and methods.

Each test module that follows this structure keeps track of its local tests in a unittest.TestSuite object that can be imported by parent test modules, and this can add tests from child TestSuite instances as needed a commented-out example of what that would look like is in place of the template file:

# import child_module
# LocalSuite.addTests(child_module.LocalSuite._tests)

Finally, the template file makes use of some display and reporting functions defined in the custom unit_testing module to write summary test result data to the console and (when tests run without failure) to a local file that can be tracked in source control if/as desired.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.76.89