python - 如何使用 Unittest 测试测试 Python 脚本中的标准输入和标准输出?

标签 python io python-unittest

我正在尝试测试一个 Python 脚本 (2.7),我在其中使用标准输入(使用 raw_input() 读取并使用简单打印写入),但我没有找到如何执行此操作,我确定这个问题是很简单。

这是我的脚本的非常非常非常简历代码:

def example():
    number = raw_input()
    print number

if __name__ == '__main__':
    example()

我想编写一个单元测试来检查这一点,但我不知道如何进行。我已经尝试过 StringIO 和其他东西,但我没有找到真正简单的解决方案。

有人有想法吗?

PD:当然,在实际脚本中,我使用包含多行和其他类型数据的数据块。

非常感谢。

编辑:

非常感谢您的第一个非常具体的答案,它完美无缺,只是我在导入 StringIO 时遇到了一点问题,因为我在做 import StringIO 并且我需要像 from StringIO import StringIO 一样导入(我真的不明白为什么),但尽管如此,它确实有效。

但是我使用这种方式发现了另一个问题,在我的项目中,我需要用这种方式测试脚本(由于您的支持,它可以完美运行)但我想这样做:
我有一个包含大量测试的文件要通过脚本,所以我打开文件并读取带有结果块的信息块,我希望代码能够处理块检查其结果并执行与其他和另一个相同...

像这样的东西:
class Test(unittest.TestCase):
    ...
    #open file and process saving data like datablocks and results
    ...
    allTest = True
    for test in tests:
        stub_stdin(self, test.dataBlock)
        stub_stdouts(self)
        runScrip()
        if sys.stdout.getvalue() != test.expectResult:
            allTest = False

    self.assertEqual(allTest, True)

我知道也许 unittest 现在没有意义,但你可以对我想要的做一个想法。所以,这种方式失败了,我不知道为什么。

最佳答案

典型的技术包括模拟标准 sys.stdinsys.stdout与您想要的元素。如果您不关心 Python 3 兼容性,您可以使用 StringIO模块,但是如果您想要前瞻性思维并且愿意限制为 Python 2.7 和 3.3+,那么通过 io 无需太多工作即可以这种方式支持 Python 2 和 3。模块(但需要一些修改,但暂时搁置这个想法)。

假设你已经有一个 unittest.TestCase接下来,您可以创建一个实用程序函数(或同一类中的方法)来替换 sys.stdin/sys.stdout如概述。首先是进口:

import sys
import io
import unittest

在我最近的一个项目中,我为 stdin 做了这个,它需要一个 str对于用户(或通过管道的其他程序)将作为标准输入输入到您的输入中的输入:
def stub_stdin(testcase_inst, inputs):
    stdin = sys.stdin

    def cleanup():
        sys.stdin = stdin

    testcase_inst.addCleanup(cleanup)
    sys.stdin = StringIO(inputs)

至于标准输出和标准错误:
def stub_stdouts(testcase_inst):
    stderr = sys.stderr
    stdout = sys.stdout

    def cleanup():
        sys.stderr = stderr
        sys.stdout = stdout

    testcase_inst.addCleanup(cleanup)
    sys.stderr = StringIO()
    sys.stdout = StringIO()

请注意,在这两种情况下,它都接受一个测试用例实例,并调用其 addCleanup 添加 cleanup 的方法当测试方法的持续时间结束时,函数调用会将它们重置回原来的位置。效果是从测试用例中调用 this 到结束的持续时间,sys.stdout和 friend 将被替换为io.StringIO版本,这意味着您可以轻松检查其值,而不必担心留下一团糟。

最好以此为例。要使用它,您可以简单地创建一个测试用例,如下所示:
class ExampleTestCase(unittest.TestCase): 

    def test_example(self):
        stub_stdin(self, '42')
        stub_stdouts(self)
        example()
        self.assertEqual(sys.stdout.getvalue(), '42\n')

现在,在 Python 2 中,这个测试只有在 StringIO 时才会通过。类(class)来自 StringIO模块,而在 Python 3 中不存在这样的模块。您可以做的是使用 io 中的版本模块进行了修改,使其在接受的输入方面稍微宽松一些,以便 unicode 编码/解码将自动完成而不是触发异常(例如,Python 2 中的 print 语句在没有下列的)。我通常这样做是为了 Python 2 和 3 之间的交叉兼容性:
class StringIO(io.StringIO):
    """
    A "safely" wrapped version
    """

    def __init__(self, value=''):
        value = value.encode('utf8', 'backslashreplace').decode('utf8')
        io.StringIO.__init__(self, value)

    def write(self, msg):
        io.StringIO.write(self, msg.encode(
            'utf8', 'backslashreplace').decode('utf8'))

现在将您的示例函数和此答案中的每个代码片段插入一个文件中,您将获得可在 Python 2 和 3 中运行的自包含单元测试(尽管您需要将 print 作为 Python 3 中的函数调用)进行测试反对 stdio。

再注意一点:您可以随时输入 stub_ setUp 中的函数调用TestCase的方法如果每个测试方法都需要。

当然,如果你想使用各种与模拟相关的库来 stub stdin/stdout,你可以自由地这样做,但如果这是你的目标,这种方式不依赖于外部依赖。

对于您的第二个问题,测试用例必须以某种方式编写,它们必须封装在方法中而不是在类级别,您的原始示例将失败。但是,您可能想要执行以下操作:
class Test(unittest.TestCase):

    def helper(self, data, answer, runner):
        stub_stdin(self, data)
        stub_stdouts(self)
        runner()
        self.assertEqual(sys.stdout.getvalue(), answer)
        self.doCleanups()  # optional, see comments below

    def test_various_inputs(self):
        data_and_answers = [
            ('hello', 'HELLOhello'),
            ('goodbye', 'GOODBYEgoodbye'),
        ]

        runScript = upperlower  # the function I want to test 

        for data, answer in data_and_answers:
            self.helper(data, answer, runScript)

您可能想调用 doCleanups 的原因是为了防止清理堆栈变得和所有 data_and_answers 一样深对在那里,但这会将所有东西从清理堆栈中弹出,所以如果你有任何其他需要在最后清理的东西,这可能最终会出现问题 - 你可以自由地将它留在那里作为所有与 stdio 相关的对象将在最后以相同的顺序恢复,所以真正的永远在那里。现在我想测试的功能:
def upperlower():
    raw = raw_input()
    print (raw.upper() + raw),

所以是的,对我所做的一些解释可能会有所帮助:记住在 TestCase 中类,框架严格依赖于实例的 assertEqual和 friend 们让它发挥作用。因此,为了确保在正确的级别进行测试,您确实希望始终调用这些断言,以便在错误发生时显示有用的错误消息,而输入/答案并未完全正确显示,而不是直到最后,就像你对 for 循环所做的一样(这会告诉你出了什么问题,但不知道是数百个中的哪一个,现在你很生气)。还有 helper方法 - 你可以随意调用它,只要它不以 test 开头因为这样框架就会尝试将它作为一个整体运行,但它会失败得可怕。所以只要遵循这个约定,你基本上可以在你的测试用例中使用模板来运行你的测试 - 然后你可以像我所做的那样在带有一堆输入/输出的循环中使用它。

至于你的另一个问题:

only I've had a little problem importing StringIO, because I was doing import StringIO and I needed to import like from StringIO import StringIO (I don't understand really why), but be that as It may, it works.



好吧,如果您查看我的原始代码,我确实向您展示了 import io 是如何做到的然后覆盖 io.StringIO类通过定义 class StringIO(io.StringIO) .但是,它对您有用,因为您严格从 Python 2 执行此操作,而鉴于 Python 2 将(可能这次肯定)在不到 5 年的时间内不受支持,我确实尝试尽可能将我的答案定位到 Python 3。想想 future 可能正在阅读这篇文章的用户,他们遇到了与您类似的问题。反正是的,原来的from StringIO import StringIO有效,因为那是 StringIO来自 StringIO 的类模块。虽然 from cStringIO import StringIO应该可以导入 C版本StringIO模块。它之所以有效,是因为它们都提供了足够接近的接口(interface),因此它们基本上可以按预期工作(当然,除非您尝试在 Python 3 下运行它)。

同样,将所有这些与我的代码放在一起应该会导致 self-contained working test script .请记住查看文档并遵循代码的形式,而不是发明自己的语法并希望事情起作用(以及究竟为什么您的代码不起作用,因为“测试”代码是在类的位置定义的正在构建,所以所有这些都是在 Python 导入您的模块时执行的,并且由于测试运行所需的任何东西都不可用(即类本身甚至还不存在),整个事情只是在抽搐的痛苦中死去)。在这里提出问题也有帮助,即使您面临的问题非常普遍,但没有一个快速简单的名称来搜索您的确切问题确实会让您很难找出哪里出错了,我想? :) 无论如何祝你好运,祝你努力测试你的代码。

还有其他方法,但考虑到我在 SO 上看到的其他问题/答案似乎没有帮助,我希望这是一个。其他供引用:
  • How to supply stdin, files and environment variable inputs to Python unit tests?
  • python mocking raw input in unittests

  • 自然地,重复所有这些都可以使用 unittest.mock 来完成。在 Python 3.3+ 或 original/rolling backport version on pypi 中可用,但鉴于这些库隐藏了实际发生的一些复杂情况,它们最终可能会隐藏一些关于实际发生(或需要发生)或重定向实际如何发生的细节。如果您愿意,可以阅读 unittest.mock.patch 并稍微下降到 StringIO修补 sys.stdout部分。

    关于python - 如何使用 Unittest 测试测试 Python 脚本中的标准输入和标准输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38861101/

    相关文章:

    python - 运行所有 Python bdd 行为和单元测试

    python - 当依赖项不可用时跳过 Python 测试

    python - 为什么我无法在 theano 中评估 reshape 的张量变量?

    python - 如何在matplotlib中向各个方向旋转图形

    javascript - AJAX Post 请求不将值发送到 Django View

    java - 使用 JUnit 测试多线程代码 - 奇怪的行为

    将 double 从文件传输到数组时,Java 跳过包含字符串/boolean 值的行

    python - 在 soup.find() 方法中传递变量 - Beautifulsoup Python

    c# - 过早中止 BeginRead 和 BeginWrite 的正确方法?

    python - 非关键单元测试失败