英文:
Make pytest 'capsys' fixture treat stdout the same regardless of whether or not the -s option is used
问题
我使用pytest
和capsys
装置来检查stdout
的文本输出是否符合预期。
我的问题是,当我使用-s
选项运行测试时,断言会通过。但是当我不使用-s
时,断言会失败,因为stdout
中的一些文本被换行,意味着实际文本不再与预期文本相同。
是否有办法让capsys
装置每次都像使用-s
时一样捕获stdout
?
示例代码
import argparse
import pytest
class Args:
class _HelpFormatter(argparse.HelpFormatter):
def __init__(self, prog: str) -> None:
super().__init__(prog, max_help_position=50)
def __init__(self) -> None:
global_parser = argparse.ArgumentParser(add_help=False, prog='wwaz',
formatter_class=self._HelpFormatter)
global_group = global_parser.add_argument_group('Global Options')
global_group.add_argument('--help', action='help',
help='Show this help message and exit.')
global_group.add_argument('--verbose', action='store_true',
default=False, help='Whether to output verbose logs. Also whether to show verbose test output. (default: False)')
self.parser = argparse.ArgumentParser(add_help=False, prog='wwaz',
formatter_class=self._HelpFormatter, parents=[global_parser],
description='Run routine Azure actions.')
@classmethod
def parse(self, args: list=None) -> argparse.Namespace:
return Args().parser.parse_args(args)
@pytest.fixture(scope='module')
def expected():
return (
'usage: wwaz [--help] [--verbose]'
'\n\nRun routine Azure actions.'
'\n\nGlobal Options:'
'\n --help Show this help message and exit.'
'\n --verbose Whether to output verbose logs. Also whether to show verbose test output. (default: False)\n'
)
help_args = [pytest.param(['--help'], id='--help')]
@pytest.mark.parametrize('input_args', help_args)
def test_stdout(capsys, expected, input_args):
capsys.readouterr()
with pytest.raises(SystemExit):
Args.parse(input_args)
actual, _ = capsys.readouterr()
assert actual == expected
成功的测试运行
假设包含示例代码的文件是~/src/test_argparse.py -
user@computer:~/src$ pytest test_argparse.py -q -s
.
1 passed in 0.00s
未成功的测试运行
假设包含示例代码的文件是~/src/test_argparse.py -
user@computer:~/src$ pytest test_argparse.py -q
F [100%]
============================================================================================= FAILURES =============================================================================================
_______________________________________________________________________________________ test_stdout[--help] ________________________________________________________________________________________
capsys = <_pytest.capture.CaptureFixture object at 0x7f6edf733520>
expected = 'usage: wwaz [--help] [--verbose]\n\nRun routine Azure actions.\n\nGlobal Options:\n --help Show this help message and exit.\n --verbose Whether to output verbose logs. Also whether to show verbose test output. (default: False)\n'
input_args = ['--help']
@pytest.mark.parametrize('input_args', help_args)
def test_stdout(capsys, expected, input_args):
capsys.readouterr()
with pytest.raises(SystemExit):
Args.parse(input_args)
actual, _ = capsys.readouterr()
> assert actual == expected
E AssertionError: assert 'usage: wwaz ...ult: False)\n' == 'usage: wwaz ...ult: False)\n'
E Skipping 192 identical leading characters in diff, use -v to show
E - rbose test output. (default: False)
E + rbose test
E + output. (default: False)
test_argparse.py:51: AssertionError
===================================================================================== short test summary info ======================================================================================
FAILED test_argparse.py::test_stdout[--help] - AssertionError: assert 'usage: wwaz ...ult: False)\n' == 'usage: wwaz ...ult: False)\n'
1 failed in 0.01s
英文:
I'm usingpytest
with the capsys
fixture to check text output to stdout
is as expected.
My issue is that when I run tests using the -s
options, the assertions passes. But when I don't use the -s
, the assertion fails because some of the text in stdout
is wrapped, meaning the actual text is no longer the same as the expected text.
Is there a way of making the capsys
fixture capture stdout
in the same way as when -s
was used every time?
Example code
import argparse
import pytest
class Args:
class _HelpFormatter(argparse.HelpFormatter):
def __init__(self, prog: str) -> None:
super().__init__(prog, max_help_position=50)
def __init__(self) -> None:
global_parser = argparse.ArgumentParser(add_help=False, prog='wwaz',
formatter_class=self._HelpFormatter)
global_group = global_parser.add_argument_group('Global Options')
global_group.add_argument('--help', action='help',
help='Show this help message and exit.')
global_group.add_argument('--verbose', action='store_true',
default=False, help='Whether to output verbose logs. Also whether to show verbose test output. (default: False)')
self.parser = argparse.ArgumentParser(add_help=False, prog='wwaz',
formatter_class=self._HelpFormatter, parents=[global_parser],
description='Run routine Azure actions.')
@classmethod
def parse(self, args: list=None) -> argparse.Namespace:
return Args().parser.parse_args(args)
@pytest.fixture(scope='module')
def expected():
return (
'usage: wwaz [--help] [--verbose]'
'\n\nRun routine Azure actions.'
'\n\nGlobal Options:'
'\n --help Show this help message and exit.'
'\n --verbose Whether to output verbose logs. Also whether to show verbose test output. (default: False)\n'
)
help_args = [pytest.param(['--help'], id='--help')]
@pytest.mark.parametrize('input_args', help_args)
def test_stdout(capsys, expected, input_args):
capsys.readouterr()
with pytest.raises(SystemExit):
Args.parse(input_args)
actual, _ = capsys.readouterr()
assert actual == expected
Successful test run
Assuming the file containing the example code is ~/src/test_argparse.py -
user@computer:~/src$ pytest test_argparse.py -q -s
.
1 passed in 0.00s
Unsuccessful test run
Assuming the file containing the example code is ~/src/test_argparse.py -
user@computer:~/src$ pytest test_argparse.py -q
F [100%]
============================================================================================= FAILURES =============================================================================================
_______________________________________________________________________________________ test_stdout[--help] ________________________________________________________________________________________
capsys = <_pytest.capture.CaptureFixture object at 0x7f6edf733520>
expected = 'usage: wwaz [--help] [--verbose]\n\nRun routine Azure actions.\n\nGlobal Options:\n --help Show this help message and exit.\n --verbose Whether to output verbose logs. Also whether to show verbose test output. (default: False)\n'
input_args = ['--help']
@pytest.mark.parametrize('input_args', help_args)
def test_stdout(capsys, expected, input_args):
capsys.readouterr()
with pytest.raises(SystemExit):
Args.parse(input_args)
actual, _ = capsys.readouterr()
> assert actual == expected
E AssertionError: assert 'usage: wwaz ...ult: False)\n' == 'usage: wwaz ...ult: False)\n'
E Skipping 192 identical leading characters in diff, use -v to show
E - rbose test output. (default: False)
E + rbose test
E + output. (default: False)
test_argparse.py:51: AssertionError
===================================================================================== short test summary info ======================================================================================
FAILED test_argparse.py::test_stdout[--help] - AssertionError: assert 'usage: wwaz ...ult: False)\n' == 'usage: wwaz ...ult: False)\n'
1 failed in 0.01s
答案1
得分: 0
在与 pytest
社区联系后,他们告诉我,这种行为是因为在使用 -s
时,capsys
会捕获来自终端的 stdout
,而在不使用时则会捕获来自非终端输出的 stdout
。
正如在这个讨论中所解释的 -
> 这是因为 argparse.HelpFormatter 使用 shutil.get_terminal_size() 来获取包装宽度(如果没有给定宽度),当 stdout 不是终端时,它会回退到 80x24。
所以我的解决方案很简单,就是指定我的格式化器的宽度,对于我的用例来说,这足够了。
class _HelpFormatter(argparse.HelpFormatter):
def __init__(self, prog: str, m=25, w=120) -> None:
super().__init__(prog, max_help_position=m, width=w)
英文:
After reaching out to the pytest
community, I was informed that this behaviour was due to capsys
capturing stdout
to the terminal when using -s
vs capturing it from non-terminal output when not.
As explained in this discussion -
> This is because argparse.HelpFormatter uses shutil.get_terminal_size() to get a wrapping width (if none is given), which then falls back to 80x24 when stdout is not a terminal
So my solution was simply to specify the width of my formatter, which for my use case this is sufficient.
class _HelpFormatter(argparse.HelpFormatter):
def __init__(self, prog: str, m=25, w=120) -> None:
super().__init__(prog, max_help_position=m, width=w)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论