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

fir*_*ush 8 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()
Run Code Online (Sandbox Code Playgroud)

~/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"
Run Code Online (Sandbox Code Playgroud)

我注意到如果出现以下情况,可以很方便

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

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

  • 我正在测试的文件没有py扩展名(只是~/candy).
  • 我不在乎是否test_candy有py作为扩展名.
  • 我想要candytest_candy.py不共享一个共同的根(我的主目录除外).

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

Nav*_*vid 6

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

? cat ~/bin/candy

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

if __name__ == '__main__':
  print candy()
Run Code Online (Sandbox Code Playgroud)

这是~/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"
Run Code Online (Sandbox Code Playgroud)

发生了什么变化?

  • 我们添加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
Run Code Online (Sandbox Code Playgroud)


MT0*_*MT0 5

自 Python 3.3 起,该imp包已被弃用,并由该importlib包取代。该答案提供了如何导入单个文件的详细信息。

对于您的单元测试,这将是:

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()
Run Code Online (Sandbox Code Playgroud)

假设文件结构为:

base_directory/bin/candy
base_directory/dev/tests/test_candy.py
Run Code Online (Sandbox Code Playgroud)

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


Par*_*ngh 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"
Run Code Online (Sandbox Code Playgroud)