繁体   English   中英

使用argparse进行Pytest:如何测试用户是否提示确认?

[英]Pytest with argparse: how to test user is prompted for confirmation?

我有一个CLI工具,并希望测试是否提示用户使用input()确认选择。 这与在Python 2中使用raw_input()

要测试的(释义)代码如下:

import sys
import argparse


def confirm():
    notification_str = "Please respond with 'y' or 'n'"
    while True:
        choice = input("Confirm [Y/n]?").lower()
        if choice in 'yes' or not choice:
            return True
        if choice in 'no':
            return False
        print(notification_str)


def parse_args(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--destructive', action='store_true')
    return parser.parse_args()


def main():
    args = parse_args(sys.argv[1:])
    if args.destructive:
        if not confirm():
            sys.exit()
    do_stuff(args)


if __name__ == '__main__':
    main()

我使用pytest作为我的框架。 如何制作它以便我可以测试CLI中是否显示确认提示? 如果我尝试比较stdout我得到错误: OSError: reading from stdin while output is captured

我想确保:

  1. 设置破坏性标志时会显示确认信息
  2. 它不会出现

我将在另一个文件中使用以下代码:

import pytest
from module_name import main


def test_user_is_prompted_when_destructive_flag_is_set():
    sys.argv['', '-d']
    main()
    assert _  # What the hell goes here?


def test_user_is_not_prompted_when_destructive_flag_not_set():
    sys.argv['',]
    main()
    assert _  # And here too?

我建议使用confirm()函数开始测试是一种更好的单元测试策略。 这允许在本地sys.stdio inputsys.stdio类的东西。 然后,一旦确认确认按预期工作,就可以编写测试以验证是否以特定方式调用。 您可以为此编写测试,并在这些测试期间模拟confirm()

这是confirm()的单元测试,它使用pytest.parametrizemock来处理用户输入和输出:

码:

@pytest.mark.parametrize("from_user, response, output", [
    (['x', 'x', 'No'], False, "Please respond with 'y' or 'n'\n" * 2),
    ('y', True, ''),
    ('n', False, ''),
    (['x', 'y'], True, "Please respond with 'y' or 'n'\n"),
])
def test_get_from_user(from_user, response, output):
    from_user = list(from_user) if isinstance(from_user, list) else [from_user]

    with mock.patch.object(builtins, 'input', lambda x: from_user.pop(0)):
        with mock.patch('sys.stdout', new_callable=StringIO):
            assert response == confirm()
            assert output == sys.stdout.getvalue()

这是如何运作的?

pytest.mark.parametrize允许在条件下多次调用测试函数。 以下是4个简单的步骤,它们将测试confirm大部分功能:

@pytest.mark.parametrize("from_user, response, output", [
    (['x', 'x', 'No'], False, "Please respond with 'y' or 'n'\n" * 2),
    ('y', True, ''),
    ('n', False, ''),
    (['x', 'y'], True, "Please respond with 'y' or 'n'\n"),
])

mock.patch可用于临时替换模块中的函数(以及其他用途)。 在这种情况下,它用于替换inputsys.stdout以允许注入用户输入和捕获打印的字符串

with mock.patch.object(builtins, 'input', lambda x: from_user.pop(0)):
    with mock.patch('sys.stdout', new_callable=StringIO):

最后运行测试中的函数并验证函数的输出和打印的任何字符串:

assert response == confirm()
assert output == sys.stdout.getvalue()

测试代码(测试代码):

import sys
from io import StringIO
import pytest
from unittest import mock
import builtins

def confirm():
    notification_str = "Please respond with 'y' or 'n'"
    while True:
        choice = input("Confirm [Y/n]?").lower()
        if choice in 'yes' or not choice:
            return True
        if choice in 'no':
            return False
        print(notification_str)

@pytest.mark.parametrize("from_user, response, output", [
    (['x', 'x', 'No'], False, "Please respond with 'y' or 'n'\n" * 2),
    ('y', True, ''),
    ('n', False, ''),
    (['x', 'y'], True, "Please respond with 'y' or 'n'\n"),
])
def test_get_from_user(from_user, response, output):
    from_user = list(from_user) if isinstance(from_user, list) \
        else [from_user]
    with mock.patch.object(builtins, 'input', lambda x: from_user.pop(0)):
        with mock.patch('sys.stdout', new_callable=StringIO):
            assert response == confirm()
            assert output == sys.stdout.getvalue()

pytest.main('-x test.py'.split())

结果:

============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: C:\Users\stephen\Documents\src\testcode, inifile:
collected 4 items

test.py ....                                                             [100%]

========================== 4 passed in 0.15 seconds ===========================

测试电话confirm()

要测试在预期时调用confirm,并且程序在调用时按预期响应,可以使用unittest.mock来模拟confirm()

注意:在通常的unittest方案中, confirm将在不同的文件中,并且mock.patch可以以类似的方式用于在此示例中修补sys.argv方式。

检查确认调用的测试代码confirm()

import sys
import argparse

def confirm():
    pass

def parse_args(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--destructive', action='store_true')
    return parser.parse_args()


def main():
    args = parse_args(sys.argv[1:])
    if args.destructive:
        if not confirm():
            sys.exit()


import pytest
from unittest import mock

@pytest.mark.parametrize("argv, called, response", [
    ([], False, None),
    (['-d'], True, False),
    (['-d'], True, True),
])
def test_get_from_user(argv, called, response):
    global confirm
    original_confirm = confirm
    confirm = mock.Mock(return_value=response)
    with mock.patch('sys.argv', [''] + argv):
        if called and not response:
            with pytest.raises(SystemExit):
                main()
        else:
            main()

        assert confirm.called == called
    confirm = original_confirm

pytest.main('-x test.py'.split())

结果:

============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: C:\Users\stephen\Documents\src\testcode, inifile:
collected 3 items

test.py ...                                                              [100%]

========================== 3 passed in 3.26 seconds ===========================
enter code here

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM