Python 如何设置 sys.argv 以便我可以对其进行单元测试?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18668947/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 11:23:21  来源:igfitidea点击:

How do I set sys.argv so I can unit test it?

python

提问by ftravers

I would like to set

我想设置

sys.argv

so I can unit test passing in different combinations. The following doesn't work:

所以我可以单元测试通过不同的组合。以下不起作用:

#!/usr/bin/env python
import argparse, sys
def test_parse_args():
    global sys.argv
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
    setup = get_setup_file()
    assert setup == "/home/fenton/project/setup.py"
def get_setup_file():
    parser = argparse.ArgumentParser()
    parser.add_argument('-f')
    args = parser.parse_args()
    return args.file
if __name__ == '__main__':
    test_parse_args()

Then running the file:

然后运行文件:

pscripts % ./test.py                                                                                           
  File "./test.py", line 4
    global sys.argv
              ^
SyntaxError: invalid syntax
pscripts %  

回答by Ian Stapleton Cordasco

It doesn't work because you're not actually calling get_setup_file. Your code should read:

它不起作用,因为您实际上并没有调用get_setup_file. 你的代码应该是:

import argparse

def test_parse_args():
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
    setup = get_setup_file()  # << You need the parentheses
    assert setup == "/home/fenton/project/setup.py"

回答by icktoofay

globalonly exposes global variables within your module, and sys.argvis in sys, not your module. Rather than using global sys.argv, use import sys.

global仅在您的模块中公开全局变量,并且sys.argv在 中sys,而不是您的模块中。而不是使用global sys.argv,使用import sys

You can avoid having to change sys.argvat all, though, quite simply: just let get_setup_fileoptionally take a list of arguments (defaulting to None) and pass that to parse_args. When get_setup_fileis called with no arguments, that argument will be None, and parse_argswill fall back to sys.argv. When it is called with a list, it will be used as the program arguments.

但是,您可以完全避免更改sys.argv,非常简单:只需让get_setup_file可选地获取参数列表(默认为None)并将其传递给parse_args。当get_setup_file不带参数调用时,该参数将是None,并将parse_args回退到sys.argv。当使用列表调用它时,它将用作程序参数。

回答by ftravers

You'll normally have command arguments. You need to test them. Here is how to unit test them.

您通常会有命令参数。你需要测试它们。这是对它们进行单元测试的方法。

  • Assume program may be run like: % myprogram -f setup.py

  • We create a list to mimic this behaviour. See line (4)

  • Then our method that parses args, takes an array as an argument that is defaulted to None. See line (7)
  • Then on line (11) we pass this into parse_args, which uses the array if it isn't None. If it is Nonethen it defaults to using sys.argv.
  • 假设程序可以像这样运行: % myprogram -f setup.py

  • 我们创建了一个列表来模仿这种行为。见第 (4) 行

  • 然后我们解析 args 的方法将一个数组作为默认为 的参数None。见第 (7) 行
  • 然后在第 (11) 行,我们将parse_args它传递给,如果它不是,则使用数组None。如果是,None则默认使用sys.argv.
    1: #!/usr/bin/env python
    2: import argparse
    3: def test_parse_args():
    4:     my_argv = ["-f", "setup.py"]
    5:     setup = get_setup_file(my_argv)
    6:     assert setup == "setup.py"
    7: def get_setup_file(argv=None):
    8:     parser = argparse.ArgumentParser()
    9:     parser.add_argument('-f')
    10:     # if argv is 'None' then it will default to looking at 'sys.argv'        
    11:     args = parser.parse_args(argv) 
    12:     return args.f
    13: if __name__ == '__main__':
    14:     test_parse_args()

回答by Fred Mitchell

Very good question.

很好的问题。

The trick to setting up unit tests is all about making them repeatable. This means that you have to eliminate the variables, so that the tests are repeatable. For example, if you are testing a function that must perform correctly given the current date, then force it to work for specific dates, where the date chosen does not matter, but the chosen dates match in type and range to the real ones.

设置单元测试的技巧就是让它们可重复。这意味着您必须消除变量,以便测试可重复。例如,如果您正在测试一个必须在给定当前日期的情况下正确执行的函数,然后强制它在特定日期工作,其中所选日期无关紧要,但所选日期的类型和范围与真实日期相匹配。

Here sys.argv will be an list of length at least one. So create a "fakemain" that gets called with a list. Then test for the various likely list lengths, and contents. You can then call your fake main from the real one passing sys.argv, knowing that fakemain works, or alter the "if name..." part to do perform the normal function under non-unit testing conditions.

这里 sys.argv 将是一个长度至少为 1 的列表。因此,创建一个使用列表调用的“fakemain”。然后测试各种可能的列表长度和内容。然后,您可以通过 sys.argv 调用您的假 main,知道 fakemain 可以工作,或者更改“if name...”部分以在非单元测试条件下执行正常功能。

回答by hpaulj

test_argparse.py, the official argparseunittest file, uses several means of setting/using argv:

test_argparse.py,官方单元测试argparse文件,使用了几种设置/使用方式argv

parser.parse_args(args)

where argsis a list of 'words', e.g. ['--foo','test']or --foo test'.split().

哪里args是“单词”列表,例如['--foo','test']or --foo test'.split()

old_sys_argv = sys.argv
sys.argv = [old_sys_argv[0]] + args
try:
    return parser.parse_args()
finally:
    sys.argv = old_sys_argv

This pushes the args onto sys.argv.

这会将 args 推到sys.argv.

I just came across a case (using mutually_exclusive_groups) where ['--foo','test']produces different behavior than '--foo test'.split(). It's a subtle point involving the idof strings like test.

我刚刚遇到一个案例(使用mutually_exclusive_groups),其中['--foo','test']产生的行为与'--foo test'.split(). 这是一个微妙的点,涉及idtest.

回答by Jason Antman

Changing sys.argv at runtime is a pretty fragile way of testing. You should use mock's patchfunctionality, which can be used as a context manager to substitute one object (or attribute, method, function, etc.) with another, within a given block of code.

在运行时更改 sys.argv 是一种非常脆弱的测试方式。您应该使用mock补丁功能,它可以用作上下文管理器,在给定的代码块中用另一个对象(或属性、方法、函数等)替换另一个对象。

The following example uses patch()to effectively "replace" sys.argvwith the specified return value (testargs).

下面的示例使用指定的返回值 ( )patch()来有效地“替换” 。sys.argvtestargs

try:
    # python 3.4+ should use builtin unittest.mock not mock package
    from unittest.mock import patch
except ImportError:
    from mock import patch

def test_parse_args():
    testargs = ["prog", "-f", "/home/fenton/project/setup.py"]
    with patch.object(sys, 'argv', testargs):
        setup = get_setup_file()
        assert setup == "/home/fenton/project/setup.py"

回答by flonk

You can attach a wrapper around your function, which prepares sys.argv before calling and restores it when leaving:

你可以在你的函数周围附加一个包装器,它在调用之前准备 sys.argv 并在离开时恢复它:

def run_with_sysargv(func, sys_argv):
""" prepare the call with given sys_argv and cleanup afterwards. """
    def patched_func(*args, **kwargs):
        old_sys_argv = list(sys.argv)
        sys.argv = list(sys_argv)
        try:
            return func(*args, **kwargs)
        except Exception, err:
            sys.argv = old_sys_argv
            raise err
    return patched_func

Then you can simply do

然后你可以简单地做

def test_parse_args():
    _get_setup_file = run_with_sysargv(get_setup_file, 
                                       ["prog", "-f", "/home/fenton/project/setup.py"])
    setup = _get_setup_file()
    assert setup == "/home/fenton/project/setup.py"

Because the errors are passed correctly, it should not interfere with external instances using the testing code, like pytest.

由于错误已正确传递,因此它不应干扰使用测试代码的外部实例,例如pytest.

回答by progfan

I achieved this by creating an execution manager that would set the args of my choice and remove them upon exit:

我通过创建一个执行管理器来实现这一点,该管理器将设置我选择的参数并在退出时删除它们:

import sys    


class add_resume_flag(object):
    def __enter__(self):
        sys.argv.append('--resume')

    def __exit__(self, typ, value, traceback):
        sys.argv = [arg for arg in sys.argv if arg != '--resume']

class MyTestClass(unittest.TestCase):

    def test_something(self):
        with add_resume_flag():
            ...