2011-02-01 5 views
5

Я грейдер для начинающего класса программирования с использованием Python. Мой python-fu не очень силен сам, но я хотел бы попытаться автоматизировать некоторые оценки.Как тестировать программы Python для начинающих, которые используют input() (возможно, с unittest?)?

В онлайн-поиске мне нравится комплект тестирования, хотя он, вероятно, немного подавлен тем, что я хочу.

Моя проблема заключается в том, что я не уверен, как передавать тестовые входы. Я хочу выполнять функции ученика, поскольку они не используют аргументы командной строки или даже несколько функций, но получают вход пользователя через функцию input().

Глупый пример:

#/usr/bin/python3.1 
# A silly example python program 
def main(): 
    a = int(input("Enter an integer: ")) 
    b = int(input("Enter another integer: ")) 
    c = a+b 
    print("The sum is %d" % c) 

if __name__ == '__main__' 
    main() 

На мой глупый пример, как бы я написать модульный тест, который может проверить выход для нескольких различных входов? (т. е. если я передаю 2 и 3 на вход, выходная строка должна быть «Сумма равна 5»)

+0

Две строки ввода не имеют правильной круглой скобки. – Javier

+0

@Javier: Исправлено. Спасибо, кто-то отредактировал мой вопрос и добавил 'eval (' но не закрыл другую сторону. – Jason

ответ

8

Редактировать: предлагать это только так, так как пример не является совместимым (и я принимаю начинающие студенты просто будут смущены ограничением)

Если вы просто обеспокоены выходом, соответствующим тому, что вы ищете, почему бы просто не использовать какой-то «глупый» bash? Что-то вроде:

echo -e "2\n3" | python test.py | grep -q "The sum is 5" && echo "Success" 

Если вы делаете относительно тривиальных программ, как это, то это должно быть достаточным, или достаточно хорошо, решение, которое не требует больших усилий.

+0

Это на самом деле самое полезное для меня. Вы правы, что создание тестовой программы не в том, чему учат студентов (это ОЧЕНЬ класс уровня intro), но что-то вроде этого на самом деле было бы лучше. – Jason

+0

@ Джейсон. Я должен прочитать вопрос более подробно, прежде чем отвечать. * sigh * – Omnifarious

4

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

def my_input(prompt): 
    return input(prompt) 

def main(): 
    a = int(eval(my_input("Enter an integer: ")) 

и т.д. Теперь ваш тест может обезьяна патч myscript.my_input вернуть значения, которые вы хотите.

+0

Благодарим вас за разъяснение. много смысла в контексте того, что я прочитал. – Jason

2

Если вам нужно более сложное взаимодействие с программами из командной строки, чем может быть представлено с echo, тогда вы можете посмотреть expect.

2

От docs:

Объектов sys.stdin, sys.stdout и sys.stderr инициализируются в файл объектов, соответствующих стандартный ввод переводчика, выход и потоки ошибок.

Так что, следуя этой логике, кажется, что-то вроде этого.Создайте файл с требуемой ввода:

$ cat sample_stdin.txt 
hello 
world 

Затем перенаправлять sys.stdin, чтобы указать на этот файл:

#!/usr/bin/env python 
import sys 

fh = open('sample_stdin.txt', 'r') 
sys.stdin = fh 

line1 = raw_input('foo: ') 
line2 = raw_input('bar: ') 

print line1 
print line2 

Выход:

$python redirecting_stdin.py 
foo: bar: hello 
world 
2

Короткий ответ, не делай этого. Вы должны проектировать для проверки. Это означает, что вы предоставляете простой способ предоставить интерфейсы для того, чтобы использовать для общения с системными ресурсами, чтобы вы могли предоставлять альтернативные реализации этих интерфейсов при тестировании.

Решение для исправления обезьяны, описанное в другом ответе, действительно работает, но это самый примитивный из ваших вариантов. Лично я бы написал класс интерфейса для взаимодействия с пользователем. Например:

class UserInteraction(object): 
    def get_input(self): 
     raise NotImplementedError() 
    def send_output(self, output): 
     raise NotImplementedError() 

Тогда все, что нужно, чтобы поговорить с пользователем, может получить экземпляр вашего класса в качестве конструктора или аргумента функции. Реализация по умолчанию может вызывать действительную функцию input или что-то еще, но есть версия, используемая для тестирования, которая обеспечивает выборку ввода или буферизацию вывода, чтобы его можно было проверить.

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

0

Возможно, вы можете высмеять функцию input для подачи входов из тестовой среды.

Кажется, что это может сработать. Это непроверено.

0

Замените sys.stdin на объект StringIO (или cStringIO).

2

Мое предложение заключается в рефакторинг кода с помощью одного из двух рамок, Python предоставляет для модульного тестирования: UnitTest (ака PyUnit) и doctest.

Это пример с использованием UnitTest:

import unittest 

def adder(a, b): 
    "Return the sum of two numbers as int" 
    return int(a) + int(b) 

class TestAdder(unittest.TestCase): 
    "Testing adder() with two int" 
    def test_adder_int(self): 
     self.assertEqual(adder(2,3), 5) 

    "Testing adder() with two float" 
    def test_adder_float(self): 
     self.assertEqual(adder(2.0, 3.0), 5) 

    "Testing adder() with two str - lucky case" 
    def test_adder_str_lucky(self): 
     self.assertEqual(adder('4', '1'), 5) 

    "Testing adder() with two str" 
    def test_adder_str(self): 
     self.assertRaises(ValueError, adder, 'x', 'y') 

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

А это пример использования doctest:

# adder.py 

def main(a, b): 
    """This program calculate the sum of two numbers. 
    It prints an int (see %d in print()) 

    >>> main(2, 3) 
    The sum is 5 

    >>> main(3, 2) 
    The sum is 5 

    >>> main(2.0, 3) 
    The sum is 5 

    >>> main(2.0, 3.0) 
    The sum is 5 

    >>> main('2', '3') 
    Traceback (most recent call last): 
     ... 
    TypeError: %d format: a number is required, not str 
    """ 
    c = a + b 
    print("The sum is %d" % c) 

def _test(): 
    import doctest, adder 
    return doctest.testmod(adder) 

if __name__ == '__main__': 
    _test() 

С doctest я сделал еще один пример с использованием ввода() (Я предполагаю, что вы используете Python 3.X):

# adder_ugly.py 

def main(): 
    """This program calculate the sum of two numbers. 
    It prints an int (see %d in print()) 

    >>> main() 
    The sum is 5 
    """ 
    a = int(input("Enter an integer: ")) 
    b = int(input("Enter another integer: ")) 
    c = a+b 
    print("The sum is %d" % c) 


def _test(): 
    import doctest, adder_ugly 
    return doctest.testmod(adder_ugly) 

if __name__ == '__main__': 
    _test() 

Я бы запустить каждый из указанных выше примеров с опцией -v:

python adder_ugly.py -v 

Для справки смотрите:

http://docs.python.org/py3k/library/unittest.html?highlight=unittest#unittest

и

http://docs.python.org/py3k/library/doctest.html#module-doctest