我想设置
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 %
答案 0 :(得分:44)
在运行时更改sys.argv是一种非常脆弱的测试方法。您应该使用mock的patch功能,该功能可以用作上下文管理器,在给定的块内将一个对象(或属性,方法,函数等)替换为另一个对象代码。
以下示例使用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_file
,None
将回退到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()
不同。这是一个涉及id
等test
字符串的微妙点。
答案 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)行
None
的参数。见第(7)行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():
...