Как проверить программы Python для начинающих студентов, которые используют input () (возможно, с unittest?)? - PullRequest
5 голосов
/ 01 февраля 2011

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

Глядя онлайн, мне нравится комплект тестирования PyUnit , хотя, вероятно, этонемного перегружен для того, что я хочу.

Моя проблема в том, что я не уверен, как передать нужные входные данные теста функциям учащегося, так как они еще не используют аргументы командной строки или даже несколько функций, нополучение пользовательского ввода через функцию 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»)

Ответы [ 8 ]

8 голосов
/ 01 февраля 2011

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

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

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

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

4 голосов
/ 01 февраля 2011

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

def my_input(prompt):
    return input(prompt)

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

и т. Д.Теперь ваш тест может monkey-patch myscript.my_input вернуть нужные значения.

2 голосов
/ 28 февраля 2011

Я предлагаю реорганизовать ваш код, используя одну из двух платформ, которые 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 Я сделал еще один пример с использованием input () (я предполагаю, что вы используете 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

2 голосов
/ 01 февраля 2011

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

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

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

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

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

2 голосов
/ 01 февраля 2011

Из документов :

Объекты 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 голосов
/ 01 февраля 2011

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

0 голосов
/ 02 февраля 2011

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

0 голосов
/ 01 февраля 2011

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

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

class MockInput( object ):
    def __init__( self, *values ):
        self.values= list(values)
        self.history= []
    def __call__( self, *args, **kw ):
        try:
            response= self.values.pop(0)
            self.history.append( (args, kw, response) )
            return response
        except IndexError:
            raise EOFError()

class TestSomething( unittest.TestCase ):
    def test_when_input_invalid( self ):
        input= MockInput( "this", "and", "that" )
        # some test case based on the input function
...