doctests

时间:2017-11-17 14:56:34

标签: python unit-testing doctest

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

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

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

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

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

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

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

编辑:我想从集中测试套件入口点运行doctests。

import doctest
import mymodule

doctest.testmod(mymodule)

1 个答案:

答案 0 :(得分:1)

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

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

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

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,而__init__.py包含test_suite设置,如此

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获取该位置,并为其指定targetdirsetUp现在可能看起来像

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

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

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

OK

此外,由于这是一个真正的测试套件,它可以与unittest模块中的testsuite 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命令可用于执行完整测试套件返回的测试。