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


当前回答

我不想修改原来的服务脚本,所以我只是模拟出了系统。argparse中的Argv部分。

from unittest.mock import patch

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

如果argparse实现发生变化,这就会中断,但对于快速测试脚本来说已经足够了。在测试脚本中,敏感性要比特异性重要得多。

其他回答

当从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()

我不想修改原来的服务脚本,所以我只是模拟出了系统。argparse中的Argv部分。

from unittest.mock import patch

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

如果argparse实现发生变化,这就会中断,但对于快速测试脚本来说已经足够了。在测试脚本中,敏感性要比特异性重要得多。

使你的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:]))

然后你就可以正常测试了。

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')

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

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())