用于argparse的python unittest

python unittest for argparse

我在创建argparse的模块中有一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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将是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

这将被称为:

1
2
3
$> python myapp.py -c config.yaml
$> config.yaml
   127.0.0.1

现在,我正试图为这个函数创建一个单元测试,但是我的问题是我不能通过测试代码传递命令行参数。

1
2
3
4
5
6
7
8
9
# 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应该以某种方式在测试代码中传递。)

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

  • python mytest.py -c config.yaml也不工作。返回AttributeError: 'module' object has no attribute 'config',因为它希望我改为调用argParseTestCase。换言之,python mytest.py -c argParseTestCase是"作品",但当然是一个回报AssertionError: 'config.yaml' != 'argParseTestCase'
  • 在详细模式下运行单元测试的python mytest.py -v也失败。它返回:


    test_parser (main.argParseTestCase) ... mytest.py 1.0 ERROR
    ERROR: test_parser (main.argParseTestCase)
    Traceback (most recent call last):
    File"tests/unit_tests/mytest.py", line 376, in test_parser options = get_options()
    File"/root/test/lib/python2.7/site-packages/mymodule.py", line 61, in get_options return parser.parse_args()
    File"/usr/local/lib/python2.7/argparse.py", line 1701, in parse_args args, argv = self.parse_known_args(args, namespace)
    File"/usr/local/lib/python2.7/argparse.py", line 1733, in parse_known_args namespace, args = self._parse_known_args(args, namespace)
    File"/usr/local/lib/python2.7/argparse.py", line 1939, in _parse_known_args start_index = consume_optional(start_index)
    File"/usr/local/lib/python2.7/argparse.py", line 1879, in consume_optional take_action(action, args, option_string)
    File"/usr/local/lib/python2.7/argparse.py", line 1807, in take_action action(self, namespace, argument_values, option_string)
    File"/usr/local/lib/python2.7/argparse.py", line 1022, in call parser.exit(message=formatter.format_help())
    File"/usr/local/lib/python2.7/argparse.py", line 2362, in exit _sys.exit(status)
    SystemExit: 0


  • 我更喜欢显式地传递参数,而不是依赖于全局可用的属性,如sys.argv(parser.parse_args())。因此,我通常使用argparse,通过自己传递参数列表(到main()和随后的get_options(),以及您需要的地方):

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

    然后传递参数

    1
    2
    3
    4
    5
    def main(args):
        get_options(args)

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

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

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

    您的错误消息堆栈很难读取,因为它是带引号的,而不是代码。但我认为-v的论点产生了sys.exitversionhelp相似,它应该显示一条消息,然后退出。-vunittest使用,但也由解析器读取。

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

    有两个特殊问题:

    • 生成输入。parse_args使用sys.argv[1:],除非其argv参数不是None参数。因此,您可以通过修改sys.argv列表(unittest已经使用了您的命令行值)或向函数和parse_args传递argv=None关键字参数来测试解析器。试图使一个用于unittest代码的命令行与get_options一起工作太复杂了。

    • 捕获输出,尤其是错误产生的sys.exit。一种选择是将ArgumentParser分为不同的error和/或exit方法。另一种方法是将函数调用包装在try块中。

    unittest采用-c参数,但具有不同的语法和含义。

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

    -vverbose,不是version

    企业的价值

    这测试了config参数(以独立的一个文件形式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    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()