This recipe will help us explore building a project-level script that allows us to run different test suites. We will also focus on how to run it in our doctests
.
With the following steps, we will craft a command-line script to allow us to manage a project including running doctests
.
recipe25.py
to contain all the code for this recipe.getopt
library.import getopt import glob import logging import nose import os import os.path import re import sys def usage(): print print "Usage: python recipe25.py [command]" print print " --help" print " --doctest" print " --suite [suite]" print " --debug-level [info|debug]" print " --package" print " --publish" print " --register" print try: optlist, args = getopt.getopt(sys.argv[1:], "h", ["help", "doctest", "suite=", "debug-level=", "package", "publish", "register"]) except getopt.GetoptError: # print help information and exit: print "Invalid command found in %s" % sys.argv usage() sys.exit(2)
–test
.def test(test_suite, debug_level): logger = logging.getLogger("recipe25") loggingLevel = debug_level logger.setLevel(loggingLevel) ch = logging.StreamHandler() ch.setLevel(loggingLevel) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) nose.run(argv=["", test_suite, "--verbosity=2"])
–doctest
.def doctest(test_suite=None): args = ["", "--with-doctest"] if test_suite is not None: print "Running doctest suite %s" % test_suite args.extend(test_suite.split(',')) nose.run(argv=args) else: nose.run(argv=args)
package
, publish
, and register
.def package(): print "This is where we can plug in code to run " + "setup.py to generate a bundle." def publish(): print "This is where we can plug in code to upload " + "our tarball to S3 or some other download site." def register(): print "setup.py has a built in function to " + "'register' a release to PyPI. It's " + "convenient to put a hook in here." # os.system("%s setup.py register" % sys.executable)
if len(optlist) == 0: usage() sys.exit(1)
debug_levels = {"info":logging.INFO, "debug":logging.DEBUG} # Default debug level is INFO debug_level = debug_levels["info"] for option in optlist: if option[0] in ("--debug-level"): # Override with a user-supplied debug level debug_level = debug_levels[option[1]]
–help
, and, if found, exits the script.# Check for help requests, which cause all other # options to be ignored. for option in optlist: if option[0] in ("--help", "-h"): usage() sys.exit(1)
–doctest
has been picked. If so, have it specially scan –suite
and run it through method doctest()
. Otherwise, run –suite
through method test()
.ran_doctests = False for option in optlist: # If --doctest is picked, then --suite is a # suboption. if option[0] in ("--doctest"): suite = None for suboption in optlist: if suboption[0] in ("--suite"): suite = suboption[1] print "Running doctests..." doctest(suite) ran_doctests = True if not ran_doctests: for option in optlist: if option[0] in ("--suite"): print "Running test suite %s..." % option[1] test(option[1], debug_level)
# Parse the arguments, in order for option in optlist: if option[0] in ("--package"): package() if option[0] in ("--publish"): publish() if option[0] in ("--register"): register()
–help
.–doctest
. Notice the first few lines of output in the following screenshot. It shows how the tests have passed and failed along with detailed output.–doctest –suite=recipe16,recipe17.py
.Just like the recipe in Writing a project-level script mentioned in Chapter 2, which lets you run different test suites, this script uses Python's getopt
library, which is modeled after the C getopt()
function (refer to http://docs.python.org/library/getopt.html for more details).
We have wired the following functions:
Usage
: This is a function to provide help to the useroptlist, args = getopt.getopt(sys.argv[1:], "h", ["help", "doctest", "suite=", "debug-level=", "package", "publish", "register"])
"h"
defined the short option: -h
."="
accept an argument. Those without are flags.usage()
, and then exit.doctest
: This runs modules through nose using –with-doctest
package,
publish,
and register
: These are just like the functions described in the previous chapter's project-level script recipeWith each of these functions defined, we can now iterate over the options that were parsed. For this script, there is a sequence:
logging.INFO
, but provide the ability to switch to logging.DEBUG
.-h
or –help
was called. If so, print out the usage()
information and then exit with no more parsing.–suite
can be used either by itself to run unittest tests, or as a suboption for -doctest, we have to parse through things and figure out whether or not –doctest
was used.To exercise things, we first called this script with the –help
option that printed out the command choices we had.
Then we called it with –doctest
to see how it handled finding all the doctests in this folder. In our case, we found all the recipes for this chapter including three test failures.
Finally, we called the script with –doctest –suite=recipe16,recipe17.py
. This shows how we can pick a subset of tests delineated by the comma. With this example, we see that nose can process either by module name (recipe16
) or by filename (recipe17
).
The features this script provides could easily be handled by already built commands. We looked at nosetests
with doctest
earlier in this chapter and saw how it can flexibly take arguments to pick tests.
Using setup.py
to generate tarballs and register releases is also a commonly used feature in the Python community.
So why write this script? Because, we can exploit all these features with a single command.
There are more details that can be found in the previous chapter about project-level script recipe, such as reasons for using getopt
.
3.146.35.72