Как мне смоделировать ввод в stdin для pyunit? - PullRequest
18 голосов
/ 08 июня 2011

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

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

Есть ли способ автоматизации тестирования?что pyunit может подделать ввод для raw_input()?

Ответы [ 5 ]

22 голосов
/ 08 июня 2011

Краткий ответ: monkey patch raw_input().

В ответе на есть несколько хороших примеров. Как отобразить перенаправленный стандартный ввод в 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
12 голосов
/ 08 июня 2011

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

Начиная с python 3.3, есть новый подмодуль для unittest, называемый mock, который делает именно то, что вам нужно.Для тех, кто использует Python 2.6 или выше, есть обратный порт 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 голосов
/ 08 июня 2011

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

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

2 голосов
/ 08 июня 2011

Вы не описали, какой код находится в 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 цента.

1 голос
/ 04 сентября 2013

Если вы используете макет модуля (написанный Майклом Фоордом), для макета функции 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**
...