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", но я не понимаю, как это сделать. Может ли кто-нибудь сделать простой пример?
Ответы
Ответ 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