繁体   English   中英

如何测试使用input()的初学者学生Python程序(可能与unittest?)?

[英]How do I test beginner student Python programs that use input() (maybe with unittest?)?

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

编辑:只提出这个,因为这个例子不是单元测试的(而且我假设初学者会被约束混淆)

如果你只关心输出匹配你想要的东西,为什么不只是使用一些“愚蠢的”bash? 就像是:

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

如果你正在做这样相对简单的程序,那么这应该是一个足够的,或足够好的解决方案,只需要很少的努力。

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

def my_input(prompt):
    return input(prompt)

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

现在你的测试可以修补myscript.my_input以返回你想要的值。

如果你需要使用命令行程序更复杂的互动相比,可以提供有echo ,那么你可能想看看expect

来自文档

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

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

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

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

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

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

我的建议是使用Python为单元测试提供的两个框架之一来重构代码: unittest (aka 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

您可以模拟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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM