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

pyd*_*nny 137 python unit-testing argparse

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

Vik*_*kez 181

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

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)
Run Code Online (Sandbox Code Playgroud)

然后在你的main函数中你应该用它调用它:

parser = parse_args(sys.argv[1:])
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser
Run Code Online (Sandbox Code Playgroud)

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

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')
Run Code Online (Sandbox Code Playgroud)

  • 响应上面的@thomas-fauskanger:“parse_args(args)”允许您从测试中传递参数 - 这就是这里的意图。单独的“parse_args()”无需从“main()”传入“sys.argv[1:]”即可工作。顺便说一句,这非常有帮助。 (5认同)
  • 感谢您的回答.当某个参数未通过时,我们如何测试错误? (4认同)
  • @PratikKhadloya如果参数是必需的并且没有传递,argparse将引发异常. (2认同)
  • @PratikKhadloya是的,遗憾的是这条消息并不真正有用:(它只是``2`` ...```argparse``不是非常友好测试,因为它直接打印到``sys.stderr`` ... (2认同)
  • 我认为没有必要使用参数 args=sys.argv[1:] 调用 ``ArgumentParser.parse_args`` 方法调用。它已经调用了 ``ArgumentParser.parse_known_args`` 方法。使用参数 args==None 它将使用 args = _sys.argv[1:] 获取它们,其中 _sys 是 sys 的别名。(这可能是自发布答案以来的更新。) (2认同)
  • @basswaves我相信托马斯建议在主函数中调用`parser = parse_args(None)`而不是`parser = parse_args(sys.argv[1:])`,因为两者产生相同的结果(这是当parse_args 被赋予“None”)。 (2认同)

mun*_*nsu 20

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

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
Run Code Online (Sandbox Code Playgroud)

Namespace 即使它们没有被传递,你也必须包含所有命令方法的args .给那些args一个值None.(请参阅文档)此样式对于快速对每个方法参数传递不同值的情况进行测试非常有用.如果您选择在测试中模拟Namespace自身的总argparse不依赖,请确保它的行为与实际Namespace类相似.

  • 这里使用“Namespace”类正是我所寻找的。尽管测试仍然依赖于“argparse”,但它并不依赖于被测试代码对“argparse”的特定实现,这对于我的单元测试很重要。此外,可以轻松使用 pytest 的 parametrize() 方法通过包含 return_value=argparse.Namespace(accumulate=accumulate, integers=integers) 的模板化模拟快速测试各种参数组合。 (2认同)

Cea*_*sta 14

使您的main()函数argv作为参数而不是默认情况下将读取sys.argv:

# 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:]))
Run Code Online (Sandbox Code Playgroud)

然后你可以正常测试.

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')
Run Code Online (Sandbox Code Playgroud)


hpa*_*ulj 7

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

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)
...
Run Code Online (Sandbox Code Playgroud)

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

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


And*_*den 7

parse_argsthrows aSystemExit并打印到 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
Run Code Online (Sandbox Code Playgroud)

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

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

assertTrue(validate_args(["-l", "-m"]))
Run Code Online (Sandbox Code Playgroud)

或者,您可能希望捕获并重新抛出不同的错误(而不是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())
Run Code Online (Sandbox Code Playgroud)


Ste*_*nes 6

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


김민준*_*김민준 6

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

from unittest.mock import patch

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

如果argparse实现更改但足以进行快速测试脚本,则此操作会中断。无论如何,在测试脚本中,敏感性比特异性要重要得多。