简体   繁体   English

如何访问 unittest.TestCase 中的 unittest.main(verbosity) 设置

[英]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时设置 python unittest 的详细级别,例如

unittest.main(verbosity=2)

How can I access this information within a unittest.TestCase ?如何在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.任何基于修补或子类化unittest.TestProgram的方法的问题在于,您必须在启动unittest.TestProgram之前将补丁安装到位。 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:在发现案例中有效的一种方法是使用inspect模块搜索堆栈,直到找到unittest.TestProgram上的方法:

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.实现此目的的一种方法是在文件中子类unittest.TestCaseunittest.main Here, you define a variable (eg globalverb ) the can be used globally or as class or Singleton, and then you override unittest.main :在这里,您定义一个变量(例如globalverb ),可以全局使用或作为类或单例使用,然后覆盖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 :稍后,您将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.通过这种方法,可以在(派生的)TestCase 中使用 verbose、verbosity 或任何其他数字和信息,从传递给单元测试的参数中获取。

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.我无法让 Martjin Pieters 的解决方案工作,我认为是因为 unittest.main 在初始化时运行测试,然后将其结果分配给全局。

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.我没有使用 monkeypatching,而是利用我所有的测试都是通过特制的启动脚本触发的。 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.对于更一般的情况,这可能是明智的解决方案,而不是直接运行测试,而是创建 test-runner.sh(或其他),它将进行完全相同的 shell 调用,但带有额外的 export 前缀。

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:爬上堆栈,找到由 unittest.main() 创建的“TestProgram”实例,并访问详细字段:

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 ).如果您只想访问-v选项,您可以在测试中使用self._resultForDoCleanups.showAll检查它(继承自unittest.TestCase )。 The field is true if -v is called for that test.如果为该测试调用-v ,则该字段为真。

If you're always running from the command line, you can just parse the arguments from sys.argv .如果你总是从命令行运行,你可以只解析来自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).您可以手动执行此操作(在列表中查找出现的“-v”、“-vv”等),但我通常更喜欢在源代码中查找相关的解析器并使用相同的版本(带有虚假的选项被剥离)。 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.我习惯了有多个冗长级别的 Django。 In this case you could probably just check if {'-v', '--verbose'} & set(sys.argv) or something like that.在这种情况下,您可能只检查{'-v', '--verbose'} & set(sys.argv)或类似的东西。

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:如果您还想处理--quiet标志,只需再次使用argparse方法添加对add_argument的调用,再次基于源代码:

    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:我将以下内容放在test.py文件的顶部:

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.在其他地方,在代码中我可以检查verbose的值并相应地进行跟踪。 Example:例子:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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