Имитировать стандартный вывод в поддельном подпроцессе.Popen

Я хотел бы протестировать функцию, которая вызывает 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, записанный в файл.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш модульный тест как таковой довольно бессмысленен, поскольку он самостоятельно записывает тестовое содержимое непосредственно в файловый объект после возврата 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

Спасибо за ваш ответ! Несмотря на то, что это правильное предложение, оно ограничивает подпись Popen значением «mock_popen». На самом деле у меня будут другие ключевые аргументы, которых нет в исходной (упрощенной версии). Что делать, если я хочу использовать autospec=True, чтобы убедиться в правильности подписи?

Maxim Ivanov 01.03.2024 07:17

Спасибо за обновленный и расширенный ответ!

Maxim Ivanov 01.03.2024 07:24

Да, вы все равно можете указать autospec=True, чтобы фиктивный объект, возвращаемый patch, был совместим с Popen в подписи. Поскольку mock_popen принимает **kwargs, он также должен быть совместим с любым вызовом.

blhsing 01.03.2024 07:24

Другие вопросы по теме