argparse的{python unittest

时间:2016-06-08 08:44:13

标签: python python-2.7 unit-testing argparse

我在创建argparse

的模块中有一个函数
def get_options(prog_version='1.0', prog_usage='', misc_opts=None):
     options = [] if misc_opts is None else misc_opts
     parser = ArgumentParser(usage=prog_usage) if prog_usage else ArgumentParser()
     parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(prog_version))
     parser.add_argument('-c', '--config', dest='config', required=True, help='the path to the configuration file')

    for option in options:
        if 'option' in option and 'destination' in option:
            parser.add_argument(option['option'],
                                dest=option.get('destination', ''),
                                default=option.get('default', ''),
                                help=option.get('description', ''),
                                action=option.get('action', 'store'))

    return parser.parse_args()

示例myapp.py将是:

my_options = [
    {
        "option": "-s",
        "destination": "remote_host",
        "default": "127.0.0.1",
        "description": "The remote server name or IP address",
        "action": "store"
    },
]

# Get Command Line Options
options = get_options(misc_opts=my_options)
print options.config
print options.remote_host

,这将被称为:

$> python myapp.py -c config.yaml
$> config.yaml
   127.0.0.1

现在,我正在尝试为此函数创建单元测试,但我的问题是我无法通过测试代码传递命令行参数。

# mytest.py
import unittest
from mymodule import get_options

class argParseTestCase(unittest.TestCase):
     def test_parser(self):
         options = get_options()
         # ...pass the command line arguments...
         self.assertEquals('config.yaml', options.config) # ofcourse this fails because I don't know how I will pass the command line arguments

我的问题是我需要将命令行参数传递给get_options(),但我不知道如何正确地执行此操作。

预期的正确呼叫:python mytest.py-c config.yaml应以某种方式在测试代码中传递。)

什么是"工作" /现在不工作:

  1. python mytest.py -c config.yaml也无效。返回AttributeError: 'module' object has no attribute 'config',因为它希望我改为呼叫argParseTestCase。换句话说,python mytest.py -c argParseTestCase"工作"但是当然是一个回归AssertionError: 'config.yaml' != 'argParseTestCase'
  2. python mytest.py -v以详细模式运行单元测试也会失败。它返回:

      

    test_parser( main .argParseTestCase)... mytest.py 1.0错误   错误:test_parser( main .argParseTestCase)
      Traceback(最近一次调用最后一次):   文件" tests / unit_tests / mytest.py",第376行,在test_parser选项中= get_options()   文件" /root/test/lib/python2.7/site-packages/mymodule.py",第61行,在get_options中返回parser.parse_args()
      文件" /usr/local/lib/python2.7/argparse.py",第1701行,在parse_args args中,argv = self.parse_known_args(args,namespace)
      文件" /usr/local/lib/python2.7/argparse.py",第1733行,在parse_known_args命名空间中,args = self._parse_known_args(args,namespace)
      文件" /usr/local/lib/python2.7/argparse.py" ;,第1939行,在_parse_known_args start_index = consume_optional(start_index)
      文件" /usr/local/lib/python2.7/argparse.py",第1879行,在consume_optional take_action(action,args,option_string)中
      文件" /usr/local/lib/python2.7/argparse.py",第1807行,在take_action操作中(self,namespace,argument_values,option_string)
      文件" /usr/local/lib/python2.7/argparse.py",第1022行,调用 parser.exit(message = formatter.format_help())
      文件" /usr/local/lib/python2.7/argparse.py",第2362行,在退出_sys.exit(status)   SystemExit:0

2 个答案:

答案 0 :(得分:4)

您的错误消息堆栈很难阅读,因为它是引用的形式而不是代码。但我认为-v参数产生了sys.exitversion就像help - 它应该显示一条消息,然后退出。 -vunittest使用,但您的解析器也会读取argparse

有一个test/test_argparse.py unittest模块options。您可能需要开发Python安装才能看到它。有些测试很简单,有些则使用专门的测试结构。一些特殊代码以与parse_args相同的方式创建参数。

这是两个特殊问题:

  • 生成输入。 sys.argv[1:]使用argv,除非其None参数不是sys.argv。因此,您可以通过修改unittest列表(argv=None已使用命令行值)或将parse_args关键字参数传递到函数中并传递到{{1}来测试解析器}}。尝试创建一个命令行,意味着unittest代码与get_options一起使用太复杂了。

  • 捕获输出,尤其是错误生成的sys.exit。一个选项是子类ArgumentParser,并为其提供不同的error和/或exit方法。另一种方法是将函数调用包装在try块中。

unittest接受-c参数,但语法和含义不同

 -c, --catch      Catch control-C and display results

-vverbose,而不是version

=============

这会测试config参数(以自包含的一个文件格式)

import unittest
import sys
#from mymodule import get_options

def get_options(argv=None, prog_version='1.0', prog_usage='', misc_opts=None):
    # argv is optional test list; uses sys.argv[1:] is not provided
    from argparse import ArgumentParser
    options = [] if misc_opts is None else misc_opts
    parser = ArgumentParser(usage=prog_usage) if prog_usage else ArgumentParser()
    parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(prog_version))
    parser.add_argument('-c', '--config', dest='config', help='the path to the configuration file')

    for option in options:
        if 'option' in option and 'destination' in option:
            parser.add_argument(option['option'],
                                dest=option.get('destination', ''),
                                default=option.get('default', ''),
                                help=option.get('description', ''),
                                action=option.get('action', 'store'))

    args = parser.parse_args(argv)
    print('args',args)
    return args

class argParseTestCase(unittest.TestCase):
     def test_config(self):
         sys.argv[1:]=['-c','config.yaml']       
         options = get_options()
         self.assertEquals('config.yaml', options.config) 
     def test_version(self):
         sys.argv[1:]=['-v']   
         with self.assertRaises(SystemExit):
                    get_options() 
         # testing version message requires redirecting stdout
     # similarly for a misc_opts test

if __name__=='__main__':
    unittest.main()

答案 1 :(得分:4)

我更喜欢显式传递参数,而不是依赖全局可用的属性,例如sys.argvparser.parse_args()在内部执行)。因此,我通常会通过自己传递参数列表来使用argparse(到main()然后get_options()以及您需要的任何地方):

def get_options(args, prog_version='1.0', prog_usage='', misc_opts=None):
    # ...
    return parser.parse_args(args)

然后传入参数

def main(args):
    get_options(args)

if __name__ == "__main__":
    main(sys.argv[1:])

这样我可以替换和测试我喜欢的任何参数列表

options = get_options(['-c','config.yaml'])
self.assertEquals('config.yaml', options.config)