【问题标题】:Robust relative paths in doctests文档测试中的健壮相对路径
【发布时间】:2017-11-17 14:56:34
【问题描述】:

我有一个将文件路径作为参数的函数。

output = process('path/to/file.txt')

我想知道我是否可以轻松地对这样的功能进行 doctest。我在源代码的某处提供了一个示例输入文件,我可以将输出与我期望的(字符串、python 对象,或者可能是另一个文件的内容)进行比较。

问题是我的测试中的路径必然是相对的。相对于调用脚本的工作目录

这意味着文档字符串中的所有路径都必须知道测试套件的入口点。显然这并不理想。在更复杂的测试环境中,我可以使用__file__ 使路径成为绝对路径,但在 doctest 中,__file__ 不存在。

将刺激作为文件提供时的通常设置是什么?

我希望听到一些更好的解决方案,而不仅仅是“始终从同一个工作目录运行测试套件”。

编辑:我想从一个集中的测试套件入口点运行文档测试。

import doctest
import mymodule

doctest.testmod(mymodule)

【问题讨论】:

  • 设置此处引用的文档测试的代码是什么?我的意思是,它们是来自模块内的文档字符串,还是来自文本文件的集合?
  • 添加了一些信息。感谢您的提问。

标签: python unit-testing doctest


【解决方案1】:

由于您是在模块级别执行此操作,我假设您也有 packaged your modules as a proper Python package 使用类似 setuptools 的东西,并被部署到随后将执行测试的某个环境中。此外,在__file__ 不存在的假设中,您只是部分正确-它也没有为从压缩的 Python 鸡蛋导入的模块定义(随着轮子成为事实上的包装方法,这种情况越来越少见,但它们可以而且确实存在)。

有许多可能的方法,其复杂性和权衡各不相同,它是否有效取决于要测试的模块的结构。

1) (不推荐,但无论如何都包含在内,因为有时这在最简单的示例中效果最好。)最懒惰,但最稳定,自包含和跨平台的方式 - 它假设文件打开方法是仅在要测试的一个模块中完成并使用相同的调用完成(例如open),extraglobs 参数的用法可用于替换 open 调用。例如

from io import StringIO
import doctest
import mymodule

files = {
    'path/to/file1.txt': '... contents of file1.txt ...', 
    'path/to/file2.txt': '... contents of file2.txt ...',
} 

def lazyopen(p, flag='r'):
    result = StringIO(files[p] if flag == 'r' else '')
    result.name = p 
    return result

doctest.testmod(mymodule, extraglobs={'open': lazyopen})

2)创建一个真正的测试套件,而不是通过doctest.testmod使用内置的测试套件

虽然速记很有用,但由于它是独立的,因此受到了太多限制,它不能与可能构建的其他测试套件结合使用。考虑创建一个专用的测试模块(例如mymodule/tests.py)。我通常更喜欢创建一个名为 mymodule/tests 的目录,其中单元测试名为 test_mysubmodule.py,以及一个包含 test_suite 设置的 __init__.py,如下所示

def make_suite():
    import mymodule
    import os

    def setUp(suite):
        suite.olddir = os.getcwd()  # save the current working directory
        os.chdir(targetdir)  # need to define targetdir

    def tearDown(suite):
        os.chdir(suite.olddir)  # restore the original working directory

    return doctest.DocTestSuite(mymodule, setUp=setUp, tearDown=tearDown)

所以我们已经介绍了基本的,但是需要定义targetdir。同样,您可以考虑多种因素:

1) 创建一个临时目录并使用setupos.chdir 将所需文件填充到该目录中,并删除tearDown 中的临时目录。要么在测试模块中手动写入存储为字符串的数据,要么从项目中复制,要么从存档中提取,但我们如何实际获取这些数据?这导致...

2) 如果源文件在您的项目中,并且setuptools 可用/安装在环境中,只需使用pkg_resources.resource_filename 获取位置,并将targetdir 分配给该位置。 setUp 现在可能看起来像

    def setUp(suite):
        suite.olddir = os.getcwd()
        targetdir = pkg_resources.resource_filename('mymodule', '')
        os.chdir(targetdir)

最后,由于这是一个真正的测试套件,由mymodules.tests 中的make_suite 函数生成,因此必须使用测试运行器来执行该测试套件,幸运的是,它包含在默认单元测试中框架作为一个简单的命令,可以这样完成:

$ python -m unittest mymodule.tests.make_suite
.
----------------------------------------------------------------------
Ran 1 test in 0.014s

OK

此外,由于这是一个真正的测试套件,它可以与来自 unittest 模块的测试套件 globbing 集成,以将所有内容组合成一个完整的测试套件,用于您的整个包。

def make_suite():
    # ... the other setup code

    # this loads all unittests in mymodule from `test_*.py` files
    # inside `mymodule.tests`
    test_suite = test_loader.discover(
        'mymodule.tests', pattern='test_*.py')
    test_suite.addTest(
        doctest.DocTestSuite(mymodule, setUp=setUp, tearDown=tearDown))
    return test_suite

同样,python -m unittest 命令可用于执行完整测试套件返回的测试。

【讨论】:

  • 非常感谢!我非常感谢您为这个答案付出的努力。似乎pkg_resources.resource_filename 是我想要的。明天我会和它一起玩。如果到那时没有其他答案出现,我会接受这个答案。现在,请 +1。
  • 再次感谢,您的回答既很有启发性又很中肯。对于它的价值,我现在选择了一个自动应用于所有 doctest 的 py.test 固定装置(这是我使用的,而不是 unittest)。它会按照您的建议更改 cwd。 AFAICT 运行良好。
  • 是的,py.test 很好,它简化了很多测试工具设置工作流程,(一个非常)轻微的缺点是其他想要测试您的代码的人需要安装 py.test在他们的环境中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-07
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
  • 2020-02-08
  • 2011-09-09
相关资源
最近更新 更多