Updating the project-level script to run this chapter's doctests

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.

How to do it...

With the following steps, we will craft a command-line script to allow us to manage a project including running doctests.

  1. Create a new file called recipe25.py to contain all the code for this recipe.
  2. Add code that parses a set of options using Python's 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)
  3. Create a function that maps to –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"])
  4. Create a function that maps to –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)
  5. Create stub functions that support 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)
  6. Add some code that detects if the option list is empty. If so, have it print out the help menu and exit the script.
    if len(optlist) == 0:
        usage()
        sys.exit(1)
  7. Add some code that defines debug levels and then parses options to allow the user to override.
    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]]
  8. Add some code that scans the command-line options for –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)
  9. Add code that checks if –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)
  10. Finish it by iterating through each of the command-line options, and invoking the other functions based on the options that are picked.
    # 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()
  11. Run the script with –help.
    How to do it...
  12. Run the script with –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.
    How to do it...

    Note

    The output is much longer. It has been trimmed for the sake of brevity.

  13. Run the script with –doctest –suite=recipe16,recipe17.py.
    How to do it...

    Note

    We deliberately used recipe16.py and recipe17.py to demonstrate that it works with both module names and filenames.

How it works...

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 user
  • The key option definitions are included in the following block:
        optlist, args = getopt.getopt(sys.argv[1:],
                "h",
               ["help", "doctest", "suite=", 
                "debug-level=", "package", 
                "publish", "register"])
    • We parse everything in the arguments except the first, being the executable itself.
    • "h" defined the short option: -h.
    • The list defines long options. Those with "=" accept an argument. Those without are flags.
    • If an option is received that isn't in the list, an exception is thrown, we print out 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 recipe

With each of these functions defined, we can now iterate over the options that were parsed. For this script, there is a sequence:

  1. Check if there is a debugging override. We default to logging.INFO, but provide the ability to switch to logging.DEBUG.
  2. Check if -h or –help was called. If so, print out the usage() information and then exit with no more parsing.
  3. Because –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.
  4. Finally, iterate over the options, and call their corresponding functions.

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).

There's more

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.

See also

There are more details that can be found in the previous chapter such as reasons for using getopt.

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

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