Pytest: как проверить функцию с помощью входного вызова?

у меня есть консольная программа, написанная на Python. Он задает пользователю вопросы, используя команду:

some_input = input('Answer the question:', ...)

как бы я проверил функцию, содержащую вызов input С помощью pytest? Я бы не хотел заставлять тестировщика вводить текст много раз только для завершения одного тестового запуска.

4 ответов


вы, вероятно, должны издеваться над встроенным input функция, вы можете использовать teardown функциональность pytest чтобы вернуться к исходному input функции после каждого испытания.

import module  # The module which contains the call to input

class TestClass:

    def test_function_1(self):
        # Override the Python built-in input method 
        module.input = lambda: 'some_input'
        # Call the function you would like to test (which uses input)
        output = module.function()  
        assert output == 'expected_output'

    def test_function_2(self):
        module.input = lambda: 'some_other_input'
        output = module.function()  
        assert output == 'another_expected_output'        

    def teardown_method(self, method):
        # This method is being called after each test case, and it will revert input back to original function
        module.input = input  

более элегантным решением было бы использовать mock вместе с with statement. Таким образом, вам не нужно использовать teardown, и исправленный метод будет жить только в with масштаб.

import mock
import module

def test_function():
    with mock.patch.object(__builtin__, 'input', lambda: 'some_input'):
        assert module.function() == 'expected_output'

как предложил компилятор, у pytest есть новое приспособление monkeypatch для этого. А monkeypatch


вы можете заменить sys.stdin С некоторыми пользовательскими текст IO, например, ввод из файла или буфера StringIO в памяти:

import sys

class Test:
    def test_function(self):
        sys.stdin = open("preprogrammed_inputs.txt")
        module.call_function()

    def setup_method(self):
        self.orig_stdin = sys.stdin

    def teardown_method(self):
        sys.stdin = self.orig_stdin

это более надежно, чем только исправление input(), так как этого будет недостаточно, если модуль использует любые другие методы потребления текста из stdin.

Это также можно сделать довольно элегантно с помощью пользовательского контекстного менеджера

import sys
from contextlib import contextmanager

@contextmanager
def replace_stdin(target):
    orig = sys.stdin
    sys.stdin = target
    yield
    sys.stdin = orig

а затем просто используйте его так, например:

with replace_stdin(StringIO("some preprogrammed input")):
    module.call_function()

вы можете сделать это с помощью mock.patch следующим образом.

во-первых, в вашем коде создайте фиктивную функцию для вызовов input:

def __get_input(text):
    return input(text)

в ваших тестовых функциях:

import my_module
from mock import patch

@patch('my_module.__get_input', return_value='y')
def test_what_happens_when_answering_yes(self, mock):
    """
    Test what happens when user input is 'y'
    """
    # whatever your test function does

например, если у вас есть цикл, проверяющий, что единственные допустимые ответы находятся в ['y', 'Y', 'n', 'N'], вы можете проверить, что ничего не происходит при вводе другого значения.

в этом случае мы предположим, что SystemExit возникает при ответе 'N':

@patch('my_module.__get_input')
def test_invalid_answer_remains_in_loop(self, mock):
    """
    Test nothing's broken when answer is not ['Y', 'y', 'N', 'n']
    """
    with self.assertRaises(SystemExit):
        mock.side_effect = ['k', 'l', 'yeah', 'N']
        # call to our function asking for input