【问题标题】:Testing script output测试脚本输出
【发布时间】:2017-01-23 11:26:01
【问题描述】:

我有一个接受一个输入(文本文件)的 Python 脚本:./myprog.py file.txt。该脚本根据给定的输入输出一个字符串。

我有一组测试文件,我想用它们来测试我的程序。我知道每个文件的预期输出,并希望确保我的脚本为每个文件生成正确的输出。

进行此类测试的普遍接受的方法是什么?

我正在考虑使用Python的unittest模块作为测试框架,然后通过subprocess.check_output(stderr=subprocess.STDOUT)运行我的脚本,捕获stdoutstderr,然后做一个unittestassertEqual来比较实际和预期的字符串。我想确保我没有错过更好的解决方案。

【问题讨论】:

  • 单元测试通常在单个函数或类上完成(通常不涉及将输出写入文件或使用子进程运行东西)。除了查看 python 单元测试教程之外,我无法推荐其他任何东西,这可能会使这个问题偏离主题。
  • 它在技术上不是单元测试,因为它改变了硬盘上的东西,但无论如何都可以这样做。考虑通过将功能移动到一个函数中来重构您的代码,该函数采用类似文件的对象进行输入和输出。常规代码将传入一个打开的文件句柄和sys.stdout。但是您的单元测试代码将使用 StringIO 缓冲区。写入 stringio 而不是连接到终端的管道时存在差异,因此这并不总是最好的方法。你必须做出决定。
  • 您可以随时运行./myprog.py file.txt > result.txt,然后使用工具来比较文件 - 即。 diff result.txt expected.txt - 但它不如测试有用 - 即使你把所有东西都放在 bash/batch 脚本中。

标签: python testing


【解决方案1】:

这里有两个问题。测试程序,而不是函数库,测试打印的东西,而不是函数返回的值。两者都使测试变得更加困难,因此最好尽可能避开这些问题。

通常的技术是创建一个函数库,然后让你的程序成为一个精简的包装器。这些函数返回它们的结果,并且只有程序进行打印。这意味着您可以对大部分代码使用正常的单元测试技术。

您可以拥有一个既是库又是程序的文件。这是一个简单的例子,如hello.py

def hello(greeting, place):
    return greeting + ", " + place + "!"

def main():
    print(hello("Hello", "World"))

if __name__ == '__main__':
    main()

最后一点是文件如何判断它是作为程序运行还是作为库导入的。它允许使用import hello 访问各个功能,还允许文件作为程序运行。 See this answer for more information.

然后你就可以编写一个基本正常的单元测试了。

import hello
import unittest
import sys
from StringIO import StringIO
import subprocess

class TestHello(unittest.TestCase):
    def test_hello(self):
        self.assertEqual(
             hello.hello("foo", "bar"),
            "foo, bar!"
        )

    def test_main(self):
        saved_stdout = sys.stdout
        try:
            out = StringIO()
            sys.stdout = out
            hello.main()
            output = out.getvalue()
            self.assertEqual(output, "Hello, World!\n")
        finally:
            sys.stdout = saved_stdout

    def test_as_program(self):
        self.assertEqual(
            subprocess.check_output(["python", "hello.py"]),
            "Hello, World!\n"
        )

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

这里test_hello是单元测试hello直接作为一个函数;在更复杂的程序中,会有更多的功能需要测试。我们还有test_mainmain 进行单元测试,使用StringIO 捕获其输出。最后,我们确保程序将作为带有test_as_program 的程序运行。

重要的是测试尽可能多的功能作为返回数据的函数,并尽可能少地测试打印和格式化的字符串,并且几乎没有通过运行程序本身来测试。在我们实际测试程序时,我们需要做的就是检查它是否调用了main

【讨论】:

  • 很好的解释。我认为模块应该命名为example.py,因为我们将其导入为import example
  • @KshitijSaraogi 只是一个疏忽。我在编写代码并且没有重新复制测试时将其从 example.py 重命名为 hello.py
  • 很高兴知道。通过您的示例,我学到了一些关于单元测试的新知识。
  • 感谢您花时间说明不同的测试方法。
猜你喜欢
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 2011-03-24
  • 2019-09-25
  • 2018-11-04
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
相关资源
最近更新 更多