Writing a project-level script that lets you run different test suites

Python, with its multi-paradigm nature, makes it easy to build applications as well as provide scripting support to things.

This recipe will help us explore building a project-level script that allows us to run different test suites. We will also show some extra command-line options to create hooks for packaging, publishing, registering, and writing automated documentation.

How to do it...

  1. Create a script called recipe15.py 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 pydoc
    import re
    import sys
    
    def usage():
        print
        print "Usage: python recipe15.py [command]"
        print
        print "	--help"
        print "	--test"
        print "	--suite [suite]"
        print "	--debug-level [info|debug]"
        print "	--package"
        print "	--publish"
        print "	--register"
        print "	--pydoc"
        print
    
    try:
        optlist, args = getopt.getopt(sys.argv[1:],
                "ht",
               ["help", "test", "suite=", 
                "debug-level=", "package", 
                "publish", "register", "pydoc"])
    except getopt.GetoptError:
        # print help information and exit:
        print "Invalid command found in %s" % sys.argv
        usage()
        sys.exit(2)
  2. Create a function that maps to –test.
    def test(test_suite, debug_level):
        logger = logging.getLogger("recipe15")
        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"])
  3. 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)
  4. Create a function to auto-generate docs using Python's pydoc module.
    def create_pydocs():
        print "It's useful to use pydoc to generate docs."
        pydoc_dir = "pydoc"
        module = "recipe15_all"
        __import__(module)
    
        if not os.path.exists(pydoc_dir):
            os.mkdir(pydoc_dir)
    
        cur = os.getcwd()
        os.chdir(pydoc_dir)
        pydoc.writedoc("recipe15_all")
        os.chdir(cur)
  5. Add some code that defines debug levels and then parses options to allow users 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]]
  6. 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)
  7. Finish it by iterating through each of the command-line options, and invoking the other functions based on which options are picked.
    # Parse the arguments, in order
    for option in optlist:
        if option[0] in ("--test"):
            print "Running recipe15_checkin tests..."
            test("recipe15_checkin", debug_level)
    
        if option[0] in ("--suite"):
            print "Running test suite %s..." % option[1]
            test(option[1], debug_level)
    
        if option[0] in ("--package"):
            package()
    	
        if option[0] in ("--publish"):
            publish()
    
        if option[0] in ("--register"):
            register()
    
        if option[0] in ("--pydoc"):
            create_pydocs()
  8. Run the recipe15.py script with –help.
    How to do it...
  9. Create a new file called recipe15_checkin.py to create a new test suite.
  10. Reuse the test cases from the recipe Getting nosy with testing to define a check in test suite.
    import recipe11
    
    class Recipe11Test(recipe11.ShoppingCartTest):
        pass
  11. Run the recipe15.py script, using –test –package –publish –register –pydoc. In the following screenshot, do you notice how it exercises each option in the same sequence as it was supplied on the command line?
    How to do it...
  12. Inspect the report generated in the pydoc directory.
    How to do it...
  13. Create a new file named recipe15_all.py to define another new test suite.
  14. Reuse the test code from the earlier recipes of this chapter to define an all test suite.
    import recipe11
    import recipe12
    import recipe13
    import recipe14
    
    class Recipe11Test(recipe11.ShoppingCartTest):
        pass
    
    class Recipe12Test(recipe12.ShoppingCartTest):
        pass
    
    class Recipe13Test(recipe13.ShoppingCartTest):
        pass
    
    class Recipe14Test(recipe14.ShoppingCartTest):
        pass
  15. Run the recipe15.py script with –suite=recipe15_all.
    How to do it...

How it works...

This script uses Python's getopt library, which is modeled after the C programming language's getopt() function. This means we use the API to define a set of commands, and then we iterate over the options, calling corresponding functions.

Note

Visit http://docs.python.org/library/getopt.html for more details on the getopt library.

  • usage: This is a function to provide help to the user.
  • key: The option definitions are included in the following block:
        optlist, args = getopt.getopt(sys.argv[1:],
                "ht",
               ["help", "test", "suite=", 
                "debug-level=", "package", 
                "publish", "register", "pydoc"])
    • We parse everything in the arguments except the first, being the executable itself.
    • "ht" defined the short options: -h and –t.
    • 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.
  • test: This activates loggers, which can be very useful if our app uses Python's logging library.
  • package: This generates tarballs. We created a stub, but it can be handy to provide a shortcut by running setup.py sdist|bdist.
  • publish: Its function is to push tarballs to the deployment site. We created a stub, but deploying it to an S3 site or somewhere else is useful.
  • register: This registers the module with PyPI. We created a stub, but it would be handy to provide a shortcut to running setup.py register.
  • create_pydocs: They are the auto-generated docs. Generating HTML files based on code is very convenient.

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

  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. 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 all the options to demonstrate the features. The script is coded to exercise a check in suite when we use –test. This is a short test suite, which simulates running a quicker test meant to tell if things look alright.

Finally, we called the script with –suite=recipe15_all. This test suite simulates running a more complete test suite that typically takes longer.

There's more

The features which this script provides could easily be handled by commands that are already built. We looked at nosetests 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 tap into all of these features with a single command script, as setup.py contains a prebuilt set of commands that involve bundling and uploading to the Python Project Index. Doing other tasks like generating pydocs, deploying to another location like an Amazon S3 bucket, or any other system level task is not included. This script demonstrates how easy it is to wire in other command-line options and link them with the project management functions.

We can also conveniently embed the usage of pydoc. Basically, any Python library that serves project management needs can be embedded as well.

Tip

On an existing project, I developed a script to provide a unified way to embed version info into a templated setup.py as well as documentation generated by pydoc, sphinx, and DocBook. The script saved me from having to remember all the commands needed to manage the project.

Why didn't I extend distutils to create my own commands? It was personally a matter of taste. I preferred using getopt and working outside the framework of distutils instead of creating and registering new subcommands.

Why use getopt instead of optparse?

Python has several options to handle command-line option parsing. getopt is possibly the simplest. It is meant to quickly allow defining short and long options, but it has limits. It requires custom coding help output, as we did with the usage function.

It also requires custom handling of the arguments. optparse provides more sophisticated options, such as better handling of arguments and auto-built help. But it also requires more code to get functional. optparse is also targeted to be replaced by argparse in the future.

It is left as an exercise for you to write an alternative version of this script using optparse to assess which one is a better solution.

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

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