Я хотел бы протестировать функцию, которая вызывает subprocess.Popen
и захватывает stdout
. В частности, мне нужно протестировать stdout
контент, физически записанный в файл на диске, даже не вызывая сам процесс.
Пример функции:
import subprocess
def func():
with open('stdout.txt', 'wt') as out:
subprocess.Popen(_cmdline(), stdout=out)
def _cmdline() -> list[str]:
# Or whatever process we want to run
return ['ls']
Функция тестирования:
from unittest import mock
def test_captures_stdout():
with mock.patch('subprocess.Popen') as mock_popen:
func()
# Try to intercept stdout and write to it
[(_, kwargs)] = mock_popen.call_args_list
with kwargs['stdout']:
buf.write('some standard output')
with open('stdout.txt') as buf:
assert buf.read() == 'some standard output'
Здесь я издеваюсь над subprocess.Popen
, затем перехватываю stdout
, переданный его конструктору, и пытаюсь записать в буфер. Затем я собираюсь выполнить утверждения по содержимому файла stdout.txt
.
Видимо, когда я пытаюсь записать в буфер stdout
, он уже закрыт, и я получаю ошибку ввода-вывода.
================================== FAILURES ===================================
____________________________ test_captures_stdout _____________________________
def test_captures_stdout():
with mock.patch('subprocess.Popen') as mock_popen:
func()
# Try to intercept stdout and write to it
[(_, kwargs)] = mock_popen.call_args_list
> with kwargs['stdout']:
E ValueError: I/O operation on closed file.
test_subprocess.py:22: ValueError
=========================== short test summary info ===========================
Интересно, есть ли удобный способ издеваться Popen
и как-то симулировать stdout
, записанный в файл.
Ваш модульный тест как таковой довольно бессмысленен, поскольку он самостоятельно записывает тестовое содержимое непосредственно в файловый объект после возврата func
, поэтому не только файловый объект уже закрыт и, следовательно, недействителен, но и любая логика после вызова Popen
, которая зависит от выходное содержимое не будет работать, поскольку во время вызова func
в файловый объект не записывается никакое содержимое.
Вместо этого вы можете добавить Popen
побочный эффект функции, которая имитирует конструктор Popen
, записывая тестовое содержимое в данный файловый объект stdout
:
def test_captures_stdout():
def mock_popen(cmd, stdout, **kwargs):
stdout.write('some standard output')
with mock.patch('subprocess.Popen', autospec=True, side_effect=mock_popen):
func()
with open('stdout.txt') as buf:
assert buf.read() == 'some standard output'
Демо: https://replit.com/@blhsing1/FuchsiaMerryScandisk
Спасибо за обновленный и расширенный ответ!
Да, вы все равно можете указать autospec=True
, чтобы фиктивный объект, возвращаемый patch
, был совместим с Popen
в подписи. Поскольку mock_popen
принимает **kwargs
, он также должен быть совместим с любым вызовом.
Спасибо за ваш ответ! Несмотря на то, что это правильное предложение, оно ограничивает подпись Popen значением «mock_popen». На самом деле у меня будут другие ключевые аргументы, которых нет в исходной (упрощенной версии). Что делать, если я хочу использовать autospec=True, чтобы убедиться в правильности подписи?