如何在使用argparse的文件上使用指定的测试目录运行pytest?

时间:2018-08-06 14:36:28

标签: python command-line-interface pytest argparse

  • 我正在使用 pytest
  • 为Python库编写单元测试
  • 我需要指定测试文件的目录,以避免自动发现测试文件,因为存在很大的子目录结构,包括库中包含“ _test”或“ test_”的许多文件名称,但不用于pytest
  • 库中的某些文件使用 argparse 指定命令行选项
  • 问题在于,将pytest的目录指定为命令行参数似乎干扰了使用argparse的命令行选项

举个例子,我在根目录中有一个名为script_with_args.py的文件,如下所示:

import argparse

def parse_args():
    parser = argparse.ArgumentParser(description="description")

    parser.add_argument("--a", type=int, default=3)
    parser.add_argument("--b", type=int, default=5)

    return parser.parse_args()

我在根目录中还有一个名为tests的文件夹,其中包含一个名为test_file.py的测试文件:

import script_with_args

def test_script_func():
    args = script_with_args.parse_args()
    assert args.a == 3

如果我从命令行调用python -m pytest,则测试通过。如果我在命令行中使用python -m pytest tests指定测试目录,则会返回以下错误:

============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: C:\Users\Jake\CBAS\pytest-tests, inifile:
plugins: remotedata-0.2.1, openfiles-0.3.0, doctestplus-0.1.3, arraydiff-0.2
collected 1 item

tests\test_file.py F                                                     [100%]

================================== FAILURES ===================================
______________________________ test_script_func _______________________________

    def test_script_func():
        # a = 1
        # b = 2
>       args = script_with_args.parse_args()

tests\test_file.py:13:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
script_with_args.py:9: in parse_args
    return parser.parse_args()
..\..\Anaconda3\lib\argparse.py:1733: in parse_args
    self.error(msg % ' '.join(argv))
..\..\Anaconda3\lib\argparse.py:2389: in error
    self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = ArgumentParser(prog='pytest.py', usage=None, description='description', f
ormatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_h
elp=True)
status = 2, message = 'pytest.py: error: unrecognized arguments: tests\n'

    def exit(self, status=0, message=None):
        if message:
            self._print_message(message, _sys.stderr)
>       _sys.exit(status)
E       SystemExit: 2

..\..\Anaconda3\lib\argparse.py:2376: SystemExit
---------------------------- Captured stderr call -----------------------------
usage: pytest.py [-h] [--a A] [--b B]
pytest.py: error: unrecognized arguments: tests
========================== 1 failed in 0.19 seconds ===========================

我的问题是,如何在不干扰argparse命令行选项的情况下为pytest指定测试文件目录?

3 个答案:

答案 0 :(得分:1)

不带参数的

parse_args()读取sys.argv[1:]列表。这将包含“测试”字符串。

pytests还将sys.argv[1:]与它自己的解析器一起使用。

使解析器可测试的一种方法是提供可选的argv

def parse_args(argv=None):
    parser = argparse.ArgumentParser(description="description")

    parser.add_argument("--a", type=int, default=3)
    parser.add_argument("--b", type=int, default=5)

    return parser.parse_args(argv)

然后您可以使用以下方法进行测试:

parse_args(['-a', '4'])

并与

一起使用
parse_args()

更改sys.argv也是一种好方法。但是,如果您打算将解析器放入这样的函数中,则最好给它增加灵活性。

答案 1 :(得分:0)

要添加到hpaulj的答案中,您还可以使用类似unittest.mock的库来临时屏蔽sys.argv的值。这样,您的parse args命令将使用“模拟的” argv运行,但 actual sys.argv保持不变。

当您的测试呼叫parse_args()时,他们可以这样做:

with unittest.mock.patch('sys.argv', ['--a', '1', '--b', 2]):
    parse_args()

答案 2 :(得分:0)

我在VS Code中发现了类似的测试发现问题。 VS Code中的运行适配器传入了我的程序无法理解的参数。我的解决方案是使解析器接受未知参数。

更改:

struct TypingText: View {
    typealias ConnectablePublisher = Publishers.Autoconnect<Timer.TimerPublisher>
    private let text: String
    private let timer: ConnectablePublisher
    private let alignment: Alignment

    @State private var visibleChars: Int = 0

    var body: some View {
        ZStack(alignment: self.alignment) {
            Text(self.text).hidden() // fixes the alignment in position
            Text(String(self.text.dropLast(text.count - visibleChars))).onReceive(timer) { _ in
                if self.visibleChars < self.text.count {
                    self.visibleChars += 1
                }
            }
        }
    }

    init(text: String) {
        self.init(text: text, typeInterval: 0.05, alignment: .leading)
    }

    init(text: String, typeInterval: TimeInterval, alignment: Alignment) {
        self.text = text
        self.alignment = alignment
        self.timer = Timer.TimerPublisher(interval: typeInterval, runLoop: .main, mode: .common).autoconnect()
    }
}

收件人:

return parser.parse_args()