无法通过《Python 程序员指南》方法创建 Python 项目测试

4

我正在尝试通过《Python程序员之旅》中描述的方法构建项目。我在测试结构方面遇到了问题。

我的文件结构如下:

.
├── __init__.py
├── sample
│   ├── __init__.py
│   └── core.py
├── setup.py
└── tests
    ├── __init__.py
    ├── context.py
    └── test_core.py

使用:

# sample/core.py

class SampleClass:
    def got_it(self):
        return True 

并且:

# tests/context.py

import os
import sys
sys.path.insert(0, os.path.abspath(
    os.path.join(os.path.dirname(__file__), '..')
))

import sample

并且:

# tests/test_core.py

import unittest

from .context import sample


class SampleClassTest(unittest.TestCase):
    def test_got_it(self):
        # ...
        pass 
    
if __name__ == '__main__':
    unittest.main()

(请注意,我只是在根目录和测试中添加了__init__.py文件来查看是否有所帮助,但事实并非如此。)
当我尝试使用Python 3.7运行tests/test_core.py时,出现以下错误:
ImportError: attempted relative import with no known parent package

如果我从测试目录外部或内部运行测试文件会发生什么。

如果我删除.并在tests/test_core.py中这样做:

from context import sample

所有内容都已经加载,但是我无法访问SampleClass

我尝试过的方法有:

sc = SampleClass()
NameError: name 'SampleClass' is not defined

sc = sample.SampleClass()
AttributeError: module 'sample' has no attribute 'SampleClass'

sc = sample.core.SampleClass()
AttributeError: module 'sample' has no attribute 'core'

sc = core.SampleClass()
NameError: name 'core' is not defined

我也尝试使用Python 2版本,但出现了类似的问题(稍有不同的错误方法)。同时,我还尝试将函数调用代替类调用,但同样遇到了问题。

请问有人能指引我错在哪里吗?


1
我总是忘记这些东西的工作原理,所以我无法真正解释为什么会发生这种情况,但是在测试中使用sys.path.insert hack很奇怪,而且在运行测试之前应该安装您的软件包(创建和激活虚拟环境,然后执行pip install --editable .python ./setup.py develop),并使测试直接导入内容。 - Czaporka
sys.path的更改看起来非常不寻常 - 真的需要吗? - Evgeny
您可能需要参考 https://docs.pytest.org/en/stable/goodpractices.html 了解测试文件夹的布局。同时请注意,从哪个文件夹调用测试以及是否以可编辑模式安装本地包都很重要。 - Evgeny
2个回答

2

这曾经让我头疼不已,直到我意识到这取决于测试的启动方式。如果你使用 python tests/test_core.py,那么你正在启动一个在任何包之外的简单脚本。因此,相对导入是被禁止的。

但是,如果(仍然从项目根目录)你使用:

python -m tests.test_core

如果你在tests包中启动test_core模块,则可以使用相对导入。从那时起,我总是将测试作为模块而不是脚本来启动。更好的是,unittest发现程序了解包,因此,如果您在命名测试文件夹和脚本时始终以test开头,您可以忘记测试数量并只需使用:

python -m unittest discover

这已足够运行整个测试套件。

愉快的测试!


谢谢你的帮助,我也遇到了问题。但我有点困惑,当你说“我总是将我的测试作为模块而不是脚本开始”时,这意味着什么?感激您的帮助! - sunyata
@sunyata:这只是意味着,如果我想单独启动一个测试,我使用 python -m test.test1(作为模块),而不是 python test/test1.py(作为脚本)。 - Serge Ballesta

0

由于您已经拥有一个完整的包,其中包含适当的setup.py文件,因此最好使用pip install -e . 而不是使用sys.path的hack方法。

最小化的setup.py

#setup.py
from setuptools import setup, find_packages
setup(name='sample', version='0.0.1', packages=find_packages(exclude=('tests')))

然后,您应该从任何地方使用相同的语法(即绝对路径)导入sample软件包中的所有内容。例如,要导入SampleClass

from sample.core import SampleClass

另外,回答你为什么会出现相对导入的问题:

ImportError: attempted relative import with no known parent package

如果您要避免使用 python ... 调用文件作为脚本,因为相对导入在脚本中不起作用。您应该使用 python -m ... 将文件作为模块进行调用。这里有一个非常好的解释,已经被浏览了十亿次:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接