5

我是使用 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”)

4

8 回答 8

8

编辑:仅提出此建议,因为该示例不可进行单元测试(并且我假设初学者只会被约束弄糊涂)

如果您只关心与您正​​在寻找的输出匹配,为什么不使用一些“愚蠢的”bash?就像是:

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

如果您正在执行这样的相对琐碎的程序,那么这应该是一个足够或足够好的解决方案,只需要很少的努力。

于 2011-02-01T16:39:16.267 回答
4

你不能真正对它进行单元测试。编写单元测试的其中一件事是,您经常需要以不同的方式编写代码以允许对其进行单元测试。因此,在这种情况下,您需要将输入的调用分解到一个单独的函数中,然后您可以对其进行修补。

def my_input(prompt):
    return input(prompt)

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

等等。现在你的测试可以猴子补丁myscript.my_input来返回你想要的值。

于 2011-02-01T16:39:57.000 回答
2

如果您需要与命令行程序进行的交互比提供的更复杂,echo那么您可能需要查看expect.

于 2011-02-01T16:46:40.187 回答
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,我使用 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

于 2011-02-28T14:30:21.413 回答
2

文档

对象 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
于 2011-02-01T16:51:52.907 回答
2

简短的回答,不要那样做。您必须针对可测试性进行设计。这意味着提供一种简单的方法来提供用于与系统资源对话的接口,以便您可以在测试时提供这些接口的替代实现。

另一个答案中描述的猴子修补解决方案确实有效,但它是您选择中最原始的。就个人而言,我会为用户交互编写一个接口类。例如:

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

然后,需要与用户交谈的事情可以获取您的类的实例作为构造函数或函数参数。默认实现可以调用实际input函数或其他任何东西,但是有一个用于测试的版本提供样本输入或缓冲输出以便可以检查。

顺便说一句,这就是我讨厌 Singleton 的原因(无论如何它都不能在 Python 中真正有效地实现)。它通过创建一个可全局访问的实例来破坏您的测试能力,该实例无法使用存根版本进行测试。

于 2011-02-01T16:52:57.137 回答
0

您可以模拟该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
于 2011-02-01T16:57:55.727 回答
0

将 sys.stdin 替换为 StringIO(或 cStringIO)对象。

于 2011-02-02T04:51:29.860 回答