如何为bin目录中的脚本编写Python单元测试

时间:2015-11-02 01:12:41

标签: python unit-testing python-unittest

Python unittest模块似乎假设一个项目的目录结构,其中有一个项目根级目录,其中包含源代码和该目录下的测试。

但是,我希望在我的~/bin目录中编写Python脚本,并在另一个目录(例如~/dev/tests)中对其进行测试。有没有办法让我使用命令行界面运行单元测试而不设置我的PYTHONPATH环境变量并创建__init__.py文件等等?

这是一个展示我想要的简单例子:

~/bin/candy

#!/usr/bin/env python

def candy():
    return "candy"

if __name__ == '__main__':
    print candy()

~/dev/tests/test_candy.py

#!/usr/bin/env python

import unittest
import candy

class CandyTestCase(unittest.TestCase):

    def testCandy(self):
        candyOutput = candy.candy()

        assert candyOutput == "candy"

我注意到在以下情况下可以方便地完成所有工作:

  • 这两个文件以py扩展名(candy.pytest_candy.py
  • 命名
  • 这两个文件位于同一目录中
  • 测试目录中运行以下测试: $ python -m unittest test_candy

我可以使用unittest模块运行python来执行以下操作,而无需在我的环境中明确设置任何内容:

  • 我的测试文件没有py扩展名(只有~/candy)。
  • 我不在乎test_candy是否有py作为扩展名。
  • 我希望candytest_candy.py不共享公共根目录(我的主目录除外)。

如果通过python -m unittest的简单调用无法做到这一点,那么实现这一目标的最简单方法是什么?

4 个答案:

答案 0 :(得分:3)

这是Candy可执行文件(不变):

➜ cat ~/bin/candy

#!/usr/bin/env python    
def candy():
  return "candy"

if __name__ == '__main__':
  print candy()

这是~/dev/tests/test_candy.py(已更改):

➜ cat ~/dev/tests/test_candy.py

#!/usr/bin/env python

import imp
import unittest

from os.path import expanduser, join

# use expanduser to locate its home dir and join bin and candy module paths
candy_module_path =  join(expanduser("~"), "bin", "candy")

# load the module without .py extension
candy = imp.load_source("candy", candy_module_path)


class CandyTestCase(unittest.TestCase):

    def testCandy(self):
        candyOutput = candy.candy()

        assert candyOutput == "candy"

发生了什么变化?

  • 我们添加了imp.load_source来导入~/bin/candy(扩展名为*.py的模块)

  • 我们增加了使用~

  • 来查找主目录提及的内容,即expanduser
  • 我们正在使用os.path.join来加入~/bin/candy

  • 的路径

现在,您可以使用discover模块的unittest选项运行测试。

检查python -m unittest --help以获得更多详细信息。

以下摘录

-s directory Directory to start discovery ('.' default)

-p pattern Pattern to match test files ('test*.py' default)

➜ python -m unittest discover -s ~/bin/ -p 'test*' -v ~/dev/tests
testCandy (test_candy.CandyTestCase) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

答案 1 :(得分:1)

从Python 3.3开始,imp软件包已被弃用,而importlib软件包将其替换。 This answer详细介绍了如何导入单个文件。

对于您的单元测试,应为:

from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
import os.path
import types
import unittest


def import_from_source( name : str, file_path : str ) -> types.ModuleType:
    loader : SourceFileLoader = SourceFileLoader(name, file_path)
    spec : ModuleSpec = spec_from_loader(loader.name, loader)
    module : types.ModuleType = module_from_spec(spec)
    loader.exec_module(module)
    return module

script_path : str = os.path.abspath(
    os.path.join(
        os.path.dirname(os.path.abspath(__file__)), "..", "..", "bin", "candy",
    )
)

candy : types.ModuleType = import_from_source("candy", script_path)


class CandyTestCase(unittest.TestCase):
    def testCandy(self : "CandyTestCase" ) -> None:
        self.assertEqual( candy.candy(), "candy" )


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

假设文件结构为:

base_directory/bin/candy
base_directory/dev/tests/test_candy.py

(注意:此单元测试采用测试的固定相对路径,而不是固定的绝对路径,因此您可以将包移动到另一个目录,并且只要包中的文件不中断测试,不要更改相对位置。)

答案 2 :(得分:0)

我还没有在unittest上尝试过,但是对于这些问题,我的快速解决方案是只使用os模块在脚本内更改我的工作目录。但这应该为您工作。

#!/usr/bin/env python

import unittest
import os
os.chdir("/usr/bin/candy")
import candy

class CandyTestCase(unittest.TestCase):

    def testCandy(self):
        candyOutput = candy.candy()

        assert candyOutput == "candy"

答案 3 :(得分:0)

简短回答,不。问题在于您需要导入要测试的模块,因此至少您必须更改PYTHONPATH。我的建议是在执行测试时临时更改它,如下所示:

import sys
sys.path.extend(['~/dir1','~/dir2','~/anotherdir'])

@Paritosh_Singh的解决方案很好。

从长远来看,是要安装像 tox 这样的测试运行程序并对其进行配置,以使其“看到”您的模块。但我认为您不想这样做。