8

我目前正在使用 Python (3.1) 编写一个小型应用程序,并且像一个好孩子一样,我边做边做文档测试。但是,我遇到了一种我似乎无法进行测试的方法。它包含一个input(), an 因此,我不完全确定在 doctest 的“预期”部分中放置什么。

说明我的问题的示例代码如下:

"""
>>> 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)

运行 doctests 时,程序在打印“Expecting”部分后立即停止执行,等待我一个接一个地输入五个数字(没有提示),然后继续。如下所示:

文档测试结果

我不知道我可以在我的 doctest 的 Expecting 部分放置什么,以便能够测试接收然后显示用户输入的方法。所以我的问题(最后)是,这个函数是否可测试?

4

5 回答 5

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

因此,在您的 doctest 中,您实际上测试了getFiveNums(fake_input).

input 此外,通过打破对now的直接依赖,如果您稍后将此代码移植到使用命令行的其他代码中,您可以直接放入新代码以检索输入(无论这是否是一个对话框GUI 应用程序或基于 Web 的应用程序中的 Javascript 弹出窗口等)。

于 2010-05-01T01:53:34.720 回答
6

我知道您要的是 doctest 答案,但我可以建议这种类型的函数可能不是 doctest 的好候选者。我将 doctests 用于文档而不是测试,并且 doctest 不会成为好的文档恕我直言。

最统一的方法可能如下所示:

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()

它可能没有完全测试您提出的功能,但您明白了。

于 2010-05-01T03:42:12.620 回答
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

五个。每行一个。

要测试您的程序,请在终端或命令提示符下执行以下操作(我使用的是 mac):

$ python foo.py < input.txt

对于任何程序上的任何类型的用户输入,这都是很容易改变的。有了这个,您现在可以复制终端会话的输出并将其用作您的 doctest。

注意:终端中的函数调用是get_five_nums()。在您的 doctest 中,它需要是get_five_nums(testing=True)

尽管 doctest 似乎并不打算以这种方式使用,但它仍然是一个方便的 hack。

于 2010-10-27T03:54:13.133 回答
2

我可以同意这种笨拙,但是为了让它少一点,为什么不添加另一个为你保留大部分笨拙的小功能(并在你使用它时为它添加一个测试:)

我确实同意 doctest 可能不是此类测试的最佳解决方案,但我发现自己使用 doctest 进行 TDD,我喜欢不必离开文件的简单性,甚至是编写测试时的函数,所以我可以就像最终想要以同样的方式进行这样的测试一样。也就是说,编写 getFiveNums() 的方法可能应该更改为更适合测试的方法,例如前面提到的参数注入。

def redirInput(*lines):
    """
    >>> import sys
    >>> redirInput('foo','bar')
    >>> sys.stdin.readline().strip()
    'foo'
    >>> sys.stdin.readline().strip()
    'bar'
    """
    import sys,io
    sys.stdin = io.StringIO(chr(10).join(lines))


def getFiveNums():
    """
    >>> redirInput('1','2','3','4','5')
    >>> getFineFums()
    ... rest as already written ...

于 2020-02-23T00:59:18.193 回答
1

这是我想出的解决方法。这有点笨拙,但是当只需要一行输入时它就可以工作:

def capitalize_name():
    """
    >>> import io, sys ; sys.stdin = io.StringIO("Bob")  # input
    >>> capitalize_name()
    What is your name?  Your name is BOB!
    """
    name = input('What is your name?  ')
    print('Your name is ' + name.upper() + '!')

不幸的是,当输入包含换行符时它会抱怨(例如,“Bob\nAlice”)。我怀疑这是由于doctest解析器不堪重负(但我不能肯定地说)。

您可以通过使用chr(10)来解决“\n”问题,如下所示:

# stores 5 user-entered numbers (strings, for now) in a list
def getFiveNums():
    """
    >>> import io, sys ; sys.stdin = io.StringIO(chr(10).join(['1','2','3','4','5']))  # 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:Here are your numbers:  ['1', '2', '3', '4', '5']
    """
    print("Howdy. Please enter five numbers, hit <enter> after each one")
    numbers = []
    for _ in range(5):
        newNum = input("Please type in a number:")
        numbers.append(newNum)
    print("Here are your numbers: ", numbers)

这更加笨拙,但确实有效。您需要记住,所有提示文本(通过 input() 函数)都显示为输出,而没有伴随的用户输入。(这就是为什么“请输入一个数字:”连续出现五次,其实例之间没有空格或换行符。)

虽然这个解决方案确实有效,但请记住,它比其他一些给定的解决方案更难阅读和维护。在决定使用哪种方法时,这是需要考虑的事情。

于 2019-05-28T23:45:46.443 回答