英文:
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论