Подтвердить что ты не робот

Python mocking raw input в unittests

Предположим, что у меня есть этот код python:

def answer():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        print 'you entered yes'
    if ans == 'no':
        print 'you entered no'

Как мне написать unittest для этого? Я знаю, что мне нужно использовать "Mock", но я не понимаю, как это сделать. Может ли кто-нибудь сделать простой пример?

4b9b3361

Ответ 1

Вы не можете вставлять патч, но можете использовать его для использования mock.patch(). Вот решение:

from unittest.mock import patch
from unittest import TestCase


def get_input(text):
    return input(text)


def answer():
    ans = get_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'


class Test(TestCase):

    # get_input will return 'yes' during this test
    @patch('yourmodule.get_input', return_value='yes')
    def test_answer_yes(self, input):
        self.assertEqual(answer(), 'you entered yes')

    @patch('yourmodule.get_input', return_value='no')
    def test_answer_no(self, input):
        self.assertEqual(answer(), 'you entered no')

Имейте в виду, что этот фрагмент будет работать только в версиях Python 3.3 +

Ответ 2

Хорошо, во-первых, я считаю необходимым указать, что в исходном коде есть две вещи, которые необходимо решить:

  • raw_input (побочный эффект ввода) необходимо высмеять.
  • print (выходной побочный эффект).

В идеальной функции для модульного тестирования побочных эффектов не было. Функция будет просто проверена путем передачи аргументов, и ее выход будет проверен. Но часто мы хотим проверить функции, которые не являются идеальными, IE, в таких функциях, как ваш.

Так что нам делать? Что ж, в Python 3.3 обе перечисленные выше проблемы стали тривиальными, потому что модуль unittest получил возможность издеваться и проверять наличие побочных эффектов. Но с начала 2014 года только 30% программистов на Python перешли на 3.x, поэтому для других 70% программистов на Python, все еще использующих 2.x, я опишу ответ. При текущей скорости 3.x не догонит 2.x до ~ 2019, а 2.x не исчезнет до ~ 2027. Поэтому я считаю, что этот ответ будет полезен уже несколько лет.

Я хочу решить проблемы, перечисленные выше по одному, поэтому я собираюсь изначально изменить вашу функцию, используя print в качестве вывода для использования return. Никаких сюрпризов, вот этот код:

def answerReturn():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'

Итак, все, что нам нужно сделать, это mock raw_input. Достаточно легко - Омид Раха отвечает на этот вопрос, показывает нам, как это сделать, выполняя реализацию __builtins__.raw_input с помощью нашей макетной реализации. За исключением того, что его ответ был неправильно организован в TestCase и функции, поэтому я продемонстрирую это.

import unittest    

class TestAnswerReturn(unittest.TestCase):
    def testYes(self):
        original_raw_input = __builtins__.raw_input
        __builtins__.raw_input = lambda _: 'yes'
        self.assertEqual(answerReturn(), 'you entered yes')
        __builtins__.raw_input = original_raw_input

    def testNo(self):
        original_raw_input = __builtins__.raw_input
        __builtins__.raw_input = lambda _: 'no'
        self.assertEqual(answerReturn(), 'you entered no')
        __builtins__.raw_input = original_raw_input

Небольшое примечание только о соглашениях об именах Python - переменные, которые требуются синтаксическому анализатору, но не используемые, обычно называются _, как в случае неиспользуемой переменной лямбда (которая обычно является приглашением, отображаемым пользователю в случае raw_input, если вы задаетесь вопросом, почему это вообще необходимо в этом случае).

В любом случае это беспорядочно и избыточно. Поэтому я собираюсь покончить с повторением, добавив в contextmanager, что позволит использовать простые инструкции with.

from contextlib import contextmanager

@contextmanager
def mockRawInput(mock):
    original_raw_input = __builtins__.raw_input
    __builtins__.raw_input = lambda _: mock
    yield
    __builtins__.raw_input = original_raw_input

class TestAnswerReturn(unittest.TestCase):
    def testYes(self):
        with mockRawInput('yes'):
            self.assertEqual(answerReturn(), 'you entered yes')

    def testNo(self):
        with mockRawInput('no'):
            self.assertEqual(answerReturn(), 'you entered no')

Я думаю, что это прекрасно отвечает на первую часть этого. На второй части - проверка print. Я нашел это намного сложнее - я бы хотел услышать, есть ли у кого-то лучший ответ.

В любом случае оператор print не может быть переопределен, но если вы вместо этого используете функции print() (как и должны) и from __future__ import print_function, вы можете использовать следующее:

class PromiseString(str):
    def set(self, newString):
        self.innerString = newString

    def __eq__(self, other):
        return self.innerString == other

@contextmanager
def getPrint():
    promise = PromiseString()
    original_print = __builtin__.print
    __builtin__.print = lambda message: promise.set(message)
    yield promise
    __builtin__.print = original_print

class TestAnswer(unittest.TestCase):
    def testYes(self):
        with mockRawInput('yes'), getPrint() as response:
            answer()
            self.assertEqual(response, 'you entered yes')

    def testNo(self):
        with mockRawInput('no'), getPrint() as response:
            answer()
            self.assertEqual(response, 'you entered no')

Трудный бит здесь заключается в том, что вам нужно yield ответить до того, как будет введен блок with. Но вы не можете знать, какой ответ будет до тех пор, пока не будет вызван print() внутри блока with. Это было бы хорошо, если строки были изменчивыми, но это не так. Поэтому вместо этого было сделано небольшое обещание или класс прокси - PromiseString. Он выполняет только две вещи: позволяет установить строку (или что-нибудь, действительно) и сообщить нам, если она равна другой строке. A PromiseString является yield ed, а затем устанавливается значение, которое обычно равно print в блоке with.

Надеюсь, вы оцените всю эту хитрость, которую я написал, так как мне потребовалось около 90 минут, чтобы собраться сегодня вечером. Я протестировал весь этот код и проверил, что все это работает с Python 2.7.

Ответ 3

Я использую Python 3.4 и должен был адаптировать ответы выше. Мое решение выдает общий код в пользовательский метод runTest и показывает, как исправлять как input(), так и print(). Здесь выполняется код:   import unittest   из io import StringIO   из патча импорта unittest.mock

def answer():
    ans = input('enter yes or no')
    if ans == 'yes':
        print('you entered yes')
    if ans == 'no':
        print('you entered no')


class MyTestCase(unittest.TestCase):
    def runTest(self, given_answer, expected_out):
        with patch('builtins.input', return_value=given_answer), patch('sys.stdout', new=StringIO()) as fake_out:
            answer()
            self.assertEqual(fake_out.getvalue().strip(), expected_out)

    def testNo(self):
        self.runTest('no', 'you entered no')

    def testYes(self):
        self.runTest('yes', 'you entered yes')

if __name__ == '__main__':
    unittest.main()

Ответ 4

Просто столкнулся с той же проблемой, но я просто высмеял __builtin__.raw_input.

Проверено только на Python 2. pip install mock, если у вас еще нет установленного пакета.

from mock import patch
from unittest import TestCase

class TestAnswer(TestCase):
    def test_yes(self):
        with patch('__builtin__.raw_input', return_value='yes') as _raw_input:
            self.assertEqual(answer(), 'you entered yes')
            _raw_input.assert_called_once_with('enter yes or no')

    def test_no(self):
        with patch('__builtin__.raw_input', return_value='no') as _raw_input:
            self.assertEqual(answer(), 'you entered no')
            _raw_input.assert_called_once_with('enter yes or no')

В качестве альтернативы, используя библиотеку genty, вы можете упростить два теста:

from genty import genty, genty_dataset
from mock import patch
from unittest import TestCase

@genty
class TestAnswer(TestCase):
    @genty_dataset(
        ('yes', 'you entered yes'),
        ('no', 'you entered no'),
    )
    def test_answer(self, expected_input, expected_answer):
        with patch('__builtin__.raw_input', return_value=expected_input) as _raw_input:
            self.assertEqual(answer(), expected_answer)
            _raw_input.assert_called_once_with('enter yes or no')

Ответ 5

def answer():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'


def test_answer_yes():
    assert(answer() == 'you entered yes')

def test_answer_no():
    assert(answer() == 'you entered no')

origin_raw_input = __builtins__.raw_input

__builtins__.raw_input = lambda x: "yes"
test_answer_yes()

__builtins__.raw_input = lambda x: "no"
test_answer_no()

__builtins__.raw_input = origin_raw_input

Ответ 6

Вот что я делаю в Python 3:

class MockInputFunction:
    def __init__(self, return_value=None):
        self.return_value = return_value
        self._orig_input_fn = __builtins__['input']

    def _mock_input_fn(self, prompt):
        print(prompt + str(self.return_value))
        return self.return_value

    def __enter__(self):
        __builtins__['input'] = self._mock_input_fn

    def __exit__(self, type, value, traceback):
        __builtins__['input'] = self._orig_input_fn

который затем может быть использован в любом контексте. Например, pytest использует обычные операторы assert.

def func():
    """ function to test """
    x = input("What is x? ")
    return int(x)

# to test, you could simply do:
with MockInputFunction(return_value=13):
    assert func() == 13