你如何编写python模块的argparse部分的测试?

时间:2013-08-10 08:25:06

标签: python unit-testing argparse

我有一个使用argparse库的Python模块。如何为代码库的该部分编写测试?

10 个答案:

答案 0 :(得分:159)

您应该重构代码并将解析移动到函数:

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)

然后在main函数中,您应该使用:

来调用它
parser = parse_args(sys.argv[1:])

(其中表示脚本名称的sys.argv的第一个元素被删除,以便在CLI操作期间不将其作为附加开关发送。)

在测试中,您可以使用要测试它的任何参数列表调用解析器函数:

def test_parser(self):
    parser = parse_args(['-l', '-m'])
    self.assertTrue(parser.long)
    # ...and so on.

这样,您就不必执行应用程序的代码来测试解析器。

如果您需要稍后在应用程序中更改和/或向解析器添加选项,请创建工厂方法:

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser

如果需要,您可以稍后操作它,测试可能如下所示:

class ParserTest(unittest.TestCase):
    def setUp(self):
        self.parser = create_parser()

    def test_something(self):
        parsed = self.parser.parse_args(['--something', 'test'])
        self.assertEqual(parsed.something, 'test')

答案 1 :(得分:16)

" argparse部分"有点模糊,所以这个答案集中在一个部分:parse_args方法。这是与命令行交互并获取所有传递值的方法。基本上,您可以模拟parse_args返回的内容,这样它就不需要从命令行实际获取值。可以通过pip为python版本2.6-3.2安装mock package。从版本3.3开始,它是unittest.mock标准库的一部分。

import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass

即使他们没有通过,您也必须在Namespace中包含所有命令方法的参数。为这些args赋值None。 (请参阅docs)此样式对于快速对每个方法参数传递不同值的情况进行测试非常有用。如果您选择模拟Namespace本身以确保测试中的argparse不依赖,请确保其行为与实际的Namespace类相似。

以下是使用argparse库中第一个代码段的示例。

# test_mock_argparse.py
import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


def main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args)  # NOTE: this is how you would check what the kwargs are if you're unsure
    return args.accumulate(args.integers)


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
    res = main()
    assert res == 6, "1 + 2 + 3 = 6"


if __name__ == "__main__":
    print(main())

答案 2 :(得分:11)

让您的main()函数将argv作为参数,而不是让它read from sys.argv as it will by default

# mymodule.py
import argparse
import sys


def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    process(**vars(parser.parse_args(args)))
    return 0


def process(a=None):
    pass

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

然后你可以正常测试。

import mock

from mymodule import main


@mock.patch('mymodule.process')
def test_main(process):
    main([])
    process.assert_call_once_with(a=None)


@mock.patch('foo.process')
def test_main_a(process):
    main(['-a', '1'])
    process.assert_call_once_with(a='1')

答案 3 :(得分:6)

  1. 使用sys.argv.append()填充您的arg列表,然后调用 parse(),检查结果并重复。
  2. 使用您的标志和dump args标志从批处理/ bash文件调用。
  3. 将所有参数解析放在一个单独的文件中,并在if __name__ == "__main__":调用解析中转储/评估结果,然后从批处理/ bash文件中对此进行测试。

答案 4 :(得分:4)

测试解析器的一种简单方法是:

parser = ...
parser.add_argument('-a',type=int)
...
argv = '-a 1 foo'.split()  # or ['-a','1','foo']
args = parser.parse_args(argv)
assert(args.a == 1)
...

另一种方法是修改sys.argv,然后调用args = parser.parse_args()

argparse

中有很多测试lib/test/test_argparse.py的例子

答案 5 :(得分:4)

我不想修改原始服务脚本,所以我只是嘲笑了argparse中的sys.argv部分。

from unittest.mock import patch

with patch('argparse._sys.argv', ['python', 'serve.py']):
    ...  # your test code here

如果argparse实现发生了变化,但是对于快速测试脚本来说足够了。无论如何,敏感性比测试脚本中的特异性更重要。

答案 6 :(得分:2)

将结果从argparse.ArgumentParser.parse_args传递到函数时,有时会使用namedtuple模拟用于测试的参数。

import unittest
from collections import namedtuple
from my_module import main

class TestMyModule(TestCase):

    args_tuple = namedtuple('args', 'arg1 arg2 arg3 arg4')

    def test_arg1(self):
        args = TestMyModule.args_tuple("age > 85", None, None, None)
        res = main(args)
        assert res == ["55289-0524", "00591-3496"], 'arg1 failed'

    def test_arg2(self):
        args = TestMyModule.args_tuple(None, [42, 69], None, None)
        res = main(args)
        assert res == [], 'arg2 failed'

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

答案 7 :(得分:0)

parse_args抛出一个SystemExit并打印到stderr,您可以捕获这两个:

import contextlib
import io
import sys

@contextlib.contextmanager
def captured_output():
    new_out, new_err = io.StringIO(), io.StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

def validate_args(args):
    with captured_output() as (out, err):
        try:
            parser.parse_args(args)
            return True
        except SystemExit as e:
            return False

您检查stderr(使用err.seek(0); err.read(),但通常不需要粒度。

现在您可以使用assertTrue或任何您喜欢的测试:

assertTrue(validate_args(["-l", "-m"]))

或者,您可能希望捕获并抛出另一个错误(而不是SystemExit):

def validate_args(args):
    with captured_output() as (out, err):
        try:
            return parser.parse_args(args)
        except SystemExit as e:
            err.seek(0)
            raise argparse.ArgumentError(err.read())

答案 8 :(得分:0)

为了测试CLI(命令行界面),不是命令输出,我做了类似的事情

let n = Numbers()
let c = n.p
    .catch {_ in return Just(-1)}

    .sink(receiveCompletion: {result in
        switch result {
        case .failure:
            print("Error")
        case .finished:
            print("Finished")
        }
    }, receiveValue: {
        print($0)
    })

n.start(max: 5)

答案 9 :(得分:-2)

我发现,至少对我来说,最简单的方法就是检查sys.argv [0],看看python是否以@SpringApplicationConfiguration(classes ={ServerConfig.class,ServerThroughAMQPBrokerRabbitMQIntegrationTestConfig.class}) 运行,如果是这种情况则不解析任何内容。

python -m unittest