对接收和显示用户输入的函数进行文档测试 - Python(让我抓狂)

10 投票
7 回答
4275 浏览
提问于 2025-04-15 22:13

我现在正在用Python(3.1)写一个小应用程序,像个乖孩子一样,我在边写边做测试。不过,我遇到了一个方法,无法进行测试。这个方法里有一个input(),因此我不太确定在测试的“期望”部分应该放什么。

下面是一个示例代码,来说明我的问题:

"""
>>> getFiveNums()
Howdy. Please enter five numbers, hit <enter> after each one
Please type in a number:
Please type in a number:
Please type in a number:
Please type in a number:
Please type in a number:
"""

import doctest

numbers = list()

# stores 5 user-entered numbers (strings, for now) in a list
def getFiveNums():
    print("Howdy. Please enter five numbers, hit <enter> after each one")
    for i in range(5):
        newNum = input("Please type in a number:")
        numbers.append(newNum)
    print("Here are your numbers: ", numbers)

if __name__ == "__main__":
    doctest.testmod(verbose=True)

当我运行测试时,程序在打印“期望”部分后就立刻停止执行,接着让我一个接一个地输入五个数字(没有提示),然后才继续执行。如下所示:

doctest results

我不知道在我的测试的期望部分可以放什么,才能测试一个接收并显示用户输入的方法。所以我最后的问题是,这个函数可以进行测试吗?

7 个回答

3

我找到了一种不同的方法。

"""
>>> get_five_nums(testing=True)
Howdy. Please enter five numbers, hit <enter> after each one.
Please type in a number: 1
Please type in a number: 1
Please type in a number: 1
Please type in a number: 1
Please type in a number: 1
Here is a list of the numbers you entered:  [1, 1, 1, 1, 1]
>>>
"""

import doctest

numbers = []

def get_five_nums(testing=False):
    """Stores 5 user-entered numbers (strings, for now) in a list."""

    print("Howdy. Please enter five numbers, hit <enter> after each one.")
    for i in range(5):
        new_num = int(input("Please type in a number: "))
        if testing:
            print(new_num)
        numbers.append(new_num)
    print("Here is a list of the numbers you entered: ", numbers)


if __name__ == "__main__":
    doctest.testmod(verbose=True)  

把上面的代码保存到一个叫foo.py的文件里。然后再创建一个叫input.txt的文件。

这个文件里只需要写:

1
1
1
1
1

五个数字1,每个数字占一行。

要测试你的程序,可以在终端或命令提示符下这样做(我用的是Mac):

$ python foo.py < input.txt

这个方法可以很方便地用于任何程序的用户输入。这样你就可以复制终端的输出,并把它用作你的测试文档。

注意:在终端调用的函数是get_five_nums()。而在你的测试文档里需要写成get_five_nums(testing=True)

虽然doctest看起来并不是为了这样使用,但这仍然是一个很实用的小技巧。

7

让这个测试变得简单的方法就是使用参数注入

def getFiveNums(input_func=input):
    print("Howdy. Please enter five numbers, hit <enter> after each one")
    for i in range(5):
        newNum = input_func("Please type in a number:")
        numbers.append(newNum)
    print("Here are your numbers: ", numbers)

实际上,你不能指望像这样对输入/输出进行单元测试——你不需要担心调用input可能会失败。最好的办法是传入一个模拟方法;类似于

def fake_input(str):
    print(str)
    return 3

这样在你的文档测试中,你实际上是在测试getFiveNums(fake_input)

而且,通过现在就打破对input的直接依赖,如果你以后要把这段代码移植到其他地方,而那个地方又不使用命令行,你只需替换成新的获取输入的代码(无论是图形界面应用中的对话框,还是网页应用中的Javascript弹窗等等)。

7

我知道你在问关于doctest的答案,但我想说,这种类型的函数可能不太适合用doctest来测试。我自己用doctest更多是为了写文档,而不是单纯的测试。在我看来,用doctest来做这个可能不会是个好文档。

如果用unittest的方法,可能会像这样:

import unittest

# stores 5 user-entered numbers (strings, for now) in a list
def getFiveNums():
    numbers = []
    print "Howdy. Please enter five numbers, hit <enter> after each one"
    for i in range(5):
        newNum = input("Please type in a number:")
        numbers.append(newNum)
    return numbers

def mock_input(dummy_prompt):
    return 1

class TestGetFiveNums(unittest.TestCase):
    def setUp(self):
        self.saved_input = __builtins__.input
        __builtins__.input = mock_input

    def tearDown(self):
        __builtins__.input = self.saved_input

    def testGetFiveNums(self):
        printed_lines = getFiveNums()
        self.assertEquals(printed_lines, [1, 1, 1, 1, 1])

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

这可能并不是在严格测试你提出的函数,但你能明白我的意思。

撰写回答