Как я могу имитировать вход для stdin для pyunit?

Я пытаюсь проверить функцию, которая принимает входные данные от stdin, который я сейчас тестирую с помощью чего-то вроде этого:

cat /usr/share/dict/words | ./spellchecker.py

Во имя автоматизации тестирования существует ли способ, которым pyunit может подделывать ввод в raw_input()?

Ответ 1

Короткий ответ: патч обезьяны raw_input().

Есть несколько хороших примеров в ответе Как отобразить перенаправленный stdin в Python?

Вот простой, тривиальный пример с использованием lambda, который отбрасывает приглашение и возвращает то, что мы хотим.

Тест системы

cat ./name_getter.py
#!/usr/bin/env python

class NameGetter(object):

    def get_name(self):
        self.name = raw_input('What is your name? ')

    def greet(self):
        print 'Hello, ', self.name, '!'

    def run(self):
        self.get_name()
        self.greet()

if __name__ == '__main__':
    ng = NameGetter()
    ng.run()

$ echo Derek | ./name_getter.py 
What is your name? Hello,  Derek !

Тестовый кейс:

$ cat ./t_name_getter.py
#!/usr/bin/env python

import unittest
import name_getter

class TestNameGetter(unittest.TestCase):

    def test_get_alice(self):
        name_getter.raw_input = lambda _: 'Alice'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Alice')

    def test_get_bob(self):
        name_getter.raw_input = lambda _: 'Bob'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Bob')

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

$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Ответ 2

Обновление - с помощью unittest.mock.patch

Так как python 3.3 есть новый подмодуль для unittest, называемый mock, который делает именно то, что вам нужно сделать. Для тех, кто использует python 2.6 или выше, есть backport mock найден < здесь.

import unittest
from unittest.mock import patch

import module_under_test


class MyTestCase(unittest.TestCase):

    def setUp(self):
        # raw_input is untouched before test
        assert module_under_test.raw_input is __builtins__.raw_input

    def test_using_with(self):
        input_data = "123"
        expected = int(input_data)

        with patch.object(module_under_test, "raw_input", create=True, 
                return_value=expected):
            # create=True is needed as raw_input is not in the globals of 
            # module_under_test, but actually found in __builtins__ .
            actual = module_under_test.function()

        self.assertEqual(expected, actual)

    @patch.object(module_under_test, "raw_input", create=True)
    def test_using_decorator(self, raw_input):
        raw_input.return_value = input_data = "123"
        expected = int(input_data)

        actual = module_under_test.function()

        self.assertEqual(expected, actual)

    def tearDown(self):
        # raw input is restored after test
        assert module_under_test.raw_input is __builtins__.raw_input

if __name__ == "__main__":
    unittest.main()
# where module_under_test.function is:
def function():
    return int(raw_input("prompt> "))

Предыдущий ответ - замена sys.stdin

Я думаю, что модуль sys может быть тем, что вы ищете.

Вы можете сделать что-то вроде

import sys

# save actual stdin in case we need it again later
stdin = sys.stdin

sys.stdin = open('simulatedInput.txt','r') 
# or whatever else you want to provide the input eg. StringIO

raw_input теперь будет считываться из файла simulatedInput.txt всякий раз, когда он вызывается. Если содержимое simulatedInput было

hello
bob

тогда первый вызов raw_input вернет "привет", второй "bob" и третий вызовет EOFError, поскольку текст больше не читается.

Ответ 3

Замените sys.stdin экземпляром StringIO и загрузите экземпляр StringIO с данными, которые вы хотите вернуть через sys.stdin. Кроме того, sys.__stdin__ содержит исходный объект sys.stdin, поэтому восстановление sys.stdin после вашего теста так же просто, как sys.stdin = sys.__stdin__.

Fudge - отличный модуль макета python, с удобными декораторами для исправления, как это для вас, с автоматической очисткой. Вы должны проверить это.

Ответ 4

Вы не описали, какой код находится в spellchecker.py, что дает мне свободу рассуждать.

Предположим, что это примерно так:

import sys

def check_stdin():
  # some code that uses sys.stdin

Чтобы улучшить возможность проверки функции check_stdin, я предлагаю реорганизовать ее так:

def check_stdin():
  return check(sys.stdin)

def check(input_stream):
  # same as original code, but instead of
  # sys.stdin it is written it terms of input_stream.

Теперь большая часть вашей логики находится в функции check, и вы можете вручную использовать любой ввод, который вы можете себе представить, чтобы проверить его правильно, без необходимости иметь дело с stdin.

Мои 2 цента.

Ответ 5

Если вы используете модуль mock (написанный Майклом Фоордом), чтобы высмеять raw_input функцию, вы можете используйте синтаксис типа:

@patch('src.main.raw_input', create=True, new=MagicMock(return_value='y'))
def test_1(self):
    method_we_try_to_test();    # method or function that calls **raw_input**