2014-01-10 2 views
17

Предположим, что у меня есть этот питон код:питон насмешливый необработанный ввод в UnitTests

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

+0

возможно дубликат [входы питания на питон UnitTests] (http://stackoverflow.com/questions/2617057/supply -inputs-to-python-unittests) – jonrsharpe

+0

Я не могу найти ответ там – user3156971

+1

Один из трех ответов * буквально * об использовании 'mock' для проверки' raw_input' – jonrsharpe

ответ

24

Вы не можете вставлять патч, но вы можете обернуть его, чтобы использовать 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+

+8

@ArtOfWarfare mock является новым в python3.3 https: //docs.python.org/3/library/unittest.mock.html Существует backport https://pypi.python.org/pypi/mock – gawel

+1

Вы должны указать версию python в своем ответе. Спасибо @gawel –

+1

Вам не нужно обернуть ввод. '@patch ('builtins.input', return_value = 'yes')' должен делать трюк. – Finn

0
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 
18

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

  1. raw_input (входной побочный эффект) необходимо высмеять.
  2. 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' 

Так все, что нужно сделать, это насмешка raw_input. Достаточно легко - Omid Raha's answer to this very question показывает нам, как это сделать, выполняя реализацию __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.

5

Просто наткнулся на ту же проблему, но я просто высмеял __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') 
2

Я использую Python 3.4 и пришлось адаптировать ответы выше. Мое решение выдает общий код в обычном методе runTest и показывает, как исправлять как input(), так и print(). Вот код, который работает: импорт UnitTest Ио импортировать 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() 
+1

Я адаптировал свой ответ для своей установки с помощью 'nose2' (не используя' unittest' напрямую), и это сработало для меня. Следует отметить, что если вы меняете 'fakeout.getvalue()' на 'fakeout.getvalue(). Strip()' вы можете избежать передачи дополнительной строки новой строки. –

Смежные вопросы