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

时间:2013-09-07 02:03:36

标签: python

我想设置

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 %  

8 个答案:

答案 0 :(得分:44)

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

以下示例使用patch()来有效地"替换" sys.argv具有指定的返回值(testargs)。

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"

答案 1 :(得分:7)

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

您可以完全避免更改sys.argv,只需简单地让get_setup_file选择一个参数列表(默认为None)并将其传递给{{ 1}}。如果在没有参数的情况下调用parse_args,则该参数将为get_setup_fileNone将回退到parse_args。当使用列表调用它时,它将用作程序参数。

答案 2 :(得分:5)

test_argparse.py,官方argparse单元测试文件,使用多种设置/使用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()不同。这是一个涉及idtest字符串的微妙点。

答案 3 :(得分:1)

它不起作用,因为你实际上并没有调用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"

答案 4 :(得分:0)

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

  • 假设程序可以像:% 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()

答案 5 :(得分:0)

非常好的问题。

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

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

答案 6 :(得分: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

答案 7 :(得分:0)

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

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():
            ...
相关问题