简体   繁体   中英

how to access the unittest.main(verbosity) setting in a unittest.TestCase

According to the documentation I can set the verbosity level of a python unittest when calling unittest.main , eg

unittest.main(verbosity=2)

How can I access this information within a unittest.TestCase ?

The problem with any method based on patching or subclassing unittest.TestProgram is that you have to get the patch in place before unittest.TestProgram is started. But that's not going to be possible if your test case is being run via discovery:

python -m unittest discover -v

An approach that works in the discovery case is to use the inspect module to search up the stack until a method on unittest.TestProgram is found:

import inspect
import unittest

def unittest_verbosity():
    """Return the verbosity setting of the currently running unittest
    program, or 0 if none is running.

    """
    frame = inspect.currentframe()
    while frame:
        self = frame.f_locals.get('self')
        if isinstance(self, unittest.TestProgram):
            return self.verbosity
        frame = frame.f_back
    return 0

A way to achieve this is to subclass unittest.TestCase and unittest.main in a file. Here, you define a variable (eg globalverb ) the can be used globally or as class or Singleton, and then you override unittest.main :

def main(*args, **kwargs):

    # parse arguments etc to get the verbosity number or whatever
    # ...
    # set this number to the defined class
    globalverb = verbose_number
    return unittest.main(*args, **kwargs)

Later, you subclass unittest.TestCase :

class MyTestCase(unittest.TestCase):
    def my_special_function(self):
        if globalverb ...

With this approach it is possible to use the verbose,verbosity or any other number and information in a (derived) TestCase, from arguments passed on to a unittest.

Comments welcome.

I wasn't able to get Martjin Pieters' solution to work, I think because unittest.main runs the tests when it is initialized, before its result has been assigned to the global.

Instead, I replaced my initialiation with:

    def new_parseArgs(self, argv):
        global old_parseArgs,verbosity
        old_parseArgs(self, argv)
        verbosity = self.verbosity

    if __name__ == '__main__':
        # monkeypatch unittest.TestProgram.parseArgs() to save verbosity
        # in a global variable
        old_parseArgs = unittest.TestProgram.parseArgs
        unittest.TestProgram.parseArgs = new_parseArgs

        unittest.main()

In the test cases that need to know the verbosity, I use something like:

            global verbosity

    ...

            if verbosity >= 2:
                print("Keys' order: %s" % dd.keys())

My solution was quite different. Instead of monkeypatching I took advantage that all my tests are triggered via specially crafted launch script. It gathers various config variables and setups environment so it was pretty straight forward to just add one extra export.

It might be sensible solution for more generic cases, instead of running tests directly, create test-runner.sh (or whatever) that will make exactly the same shell call but with extra export prefixed to it.

Because one picture is worth thousands of words:

This is my test runner:

#!/usr/bin/env bash

VERBOSE=false

while getopts ":vt:" opt; do
    case $opt in
        t)
            TEST_TO_RUN=$OPTARG
            ;;
        v)
            VERBOSE=true
            ;;
        \?)
          echo "Invalid option: -$OPTARG" >&2
          exit 1
          ;;
        :)
          echo "Option -$OPTARG requires an argument." >&2
          exit 1
      ;;
    esac
done

ENVS=""
ENVS+=" PYTHONPATH=$PYTHONPATH:$PWD"

PARAMS=""
PARAMS+=" -s --nologcapture --with-id"
PARAMS+=" --cov-config=.apirc --cov-report html --with-cov"

SERVER_PRIMER="coverage run --rcfile=.apirc"

if [[ ! -z "$TEST_TO_RUN" ]]; then
    PARAMS+=" $TEST_TO_RUN"
fi

if [[ "$VERBOSE" = true ]]; then
    PARAMS+=" -v"
    ENVS+=" TEST_VERBOSITY=2"
fi

eval "$ENVS nosetests $PARAMS"

RESULT_TEST=$?

And then I have this method on unit test:

@property
def verbosity(self):
    return int(os.environ.get('TEST_VERBOSITY', 0))

Climb the stack, find the "TestProgram" instance created by unittest.main( ), and access the verbosity field:

class MyTestCase(unittest.TestCase):

    def test_verbosity(self):
        """Return current verbosity"""
        for f in inspect.getouterframes(inspect.currentframe() ):
            args, _,_, local_dict = inspect.getargvalues(f[0])
            if args: 
                first_arg = args[0] 
                first_value = local_dict[first_arg]
                if type(first_value).__name__ == "TestProgram":
                    return first_value.verbosity

If you just want to access the -v option, you can check it with self._resultForDoCleanups.showAll in your test (inheriting from unittest.TestCase ). The field is true if -v is called for that test.

If you're always running from the command line, you can just parse the arguments from sys.argv . You can do it manually (look for occurrences of '-v', '-vv', etc. in the list), but I usually prefer to look up the relevant parser in the source code and use an identical version (with the spurious options stripped out). In your case, that would look like this:

The relevant code appears to be here . We can extract that bit of code and get the following:

    import argparse


    verbosity_parser = argparse.ArgumentParser(add_help=False)
    verbosity_parser.add_argument(
        '-v', '--verbose', dest='verbosity', 
        action='store_const', const=2,
        help='Verbose output')  # We could remove this if we want
    args, rest = verbosity_parser.parse_known_args()
    verbosity = args.verbosity

EDIT

I just realized how simple the parser is. I'm used to Django where there are multiple verbosity levels. In this case you could probably just check if {'-v', '--verbose'} & set(sys.argv) or something like that.

If you also want to handle the --quiet flag, it's just a matter of adding another call to add_argument using the argparse approach, again based on the source code:

    parser.add_argument('-q', '--quiet', dest='verbosity',
                        action='store_const', const=0,
                        help='Quiet output')  # this could be removed

We could also handle what appears to be an implicit default if we want:

    if verbosity is None:
        verbosity = 1

This works only when the verbosity is set via command line switch.

I put the following at the top of my test.py file:

verbose = sum(arg.count('v')
for arg in sys.argv if arg.startswith("-") and not arg.startswith("--"))

Elsewhere, in the code I can check the value of verbose and trace accordingly. Example:

if verbose > 2:
  sys.stderr.write("Some very noisy message\n")
  sys.stderr.flush()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM