74

我想设置

sys.argv

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

#!/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()

然后运行文件:

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

9 回答 9

100

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

以下示例使用指定的返回值 ( )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"
于 2015-01-04T13:29:54.580 回答
15

test_argparse.py,官方的argparseunittest 文件,使用了几种设置/使用的方法argv

parser.parse_args(args)

其中args是“单词”列表,例如['--foo','test']--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

这会将 args 推到sys.argv.

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

于 2013-09-07T16:56:02.930 回答
9

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

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

于 2013-09-07T02:22:59.147 回答
1

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

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"
于 2013-09-07T02:09:53.650 回答
1

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

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():
            ...
于 2018-02-25T03:11:57.137 回答
0

非常好的问题。

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

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

于 2013-09-07T03:26:39.160 回答
0

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 None then it defaults to using 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()
于 2013-09-07T03:06:21.537 回答
0

您可以在函数周围附加一个包装器,该包装器在调用之前准备 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

然后你可以简单地做

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"

因为错误是正确传递的,所以它不应该干扰使用测试代码的外部实例,比如pytest.

于 2018-01-19T15:15:21.470 回答
0

我喜欢用unittest.mock.patch(). 不同之patch.object()处在于您不需要直接引用要修补的对象,而是使用字符串。

from unittest.mock import patch

with patch("sys.argv", ["file.py", "-h"]):
    print(sys.argv)
于 2022-01-26T22:55:21.187 回答