二.问题陈述:如果未设置PYTHONPATH,则应用程序运行,但是单元测试失败,并出现ImportError:没有名为models.transactions的模块.尝试导入时会发生这种情况
app.py中的交易.如果PYTHONPATH设置为/ sandbox / app,则应用程序和
unittest运行没有错误.解决方案的约束条件是不必设置PYTHONPATH,并且
sys.path不必通过编程方式进行修改.
三,详细信息:考虑以下情况:设置PYTHONPATH并将test_app.py作为软件包/ sandbox $python -m unittest tests.test_app运行.查看__main__的打印语句
遍及整个代码:
models : app.models.transactions
models : models.transactions
resources: app.resources.transactions
app : app.app
test : tests.test_app
单元测试首先导入应用程序,因此有app.models.transactions.下次导入该应用程序
尝试是resources.transactions.导入时,它会自己导入model.transactions和
然后我们看到__name__代表app.resources.transactions.其次是app.app
导入,最后是unittest模块tests.test.app.设置PYTHONPATH允许应用程序解析模型.
一种解决方案是将model.transactions放入resources.transaction内.但是还有另一种方法可以解决这个问题吗?
为了完整起见,在运行应用程序时,__ main__的打印语句为:
models : models.transactions
resources: resources.transactions
app : __main__
这是预期的,并且不会尝试导入高于/ sandbox / app或横向的导入.
IV.附录
A.1目录结构:
|-- sandbox
|-- app
|-- models
|-- __init__.py
|-- transactions.py
|-- resources
|-- __init__.py
|-- transactions.py
|-- __init__.py
|-- app.py
|-- tests
|-- __init__.py
|-- test_app.py
A.2模块:
(1)应用程式:
from flask import Flask
from models.transactions import TransactionsModel
from resources.transactions import Transactions
print ' app : ', __name__
def create_app():
app = Flask(__name__)
return app
app = create_app()
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000, debug=True)
(2)模型.交易
print ' model : ', __name__
class TransactionsModel:
pass
(3)resources.transactions:
from models.transactions import TransactionsModel
print ' resources: ', __name__
class Transactions:
pass
(4)tests.test_app
import unittest
from app.app import create_app
from app.resources.transactions import Transactions
print ' test : ', __name__
class DonationTestCase(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_transactions_get_with_none_ids(self):
self.assertEqual(0, 0)
if __name__ == '__main__':
unittest.main()
问题的症结可以用以下方式描述. test_app.py需要访问横向导入,但是如果它作为脚本运行,例如:
/sandbox/test$python test_app.py
它具有__name __ == __ main__.这意味着导入(例如来自models.transactions import TransactionsModel)将无法解析,因为它们是横向的,并且在层次结构中并不低.要解决此问题,可以将test_app.py打包运行:
/sandbox$python unittest -m test.test_app
-m开关告诉Python执行此操作.现在,该软件包可以访问app.model,因为它在/ sandbox中运行. test_app.py中的导入必须反映此更改并变为类似以下内容:
from app.models.transactions import TransactionsModel
为了进行测试运行,应用程序中的导入现在必须是相对的.例如,在app.resources中:
from ..models.transactions import TransactionsModel
因此测试成功运行,但是如果应用程序运行,它将失败!这是问题的症结所在.当应用程序从/ sandbox / app $python app.py作为脚本运行时,它将命中此相对导入..models.transactions,并返回错误,表示该程序正尝试在最高级别上导入.解决一个,然后打破另一个.
无需设置PYTHONPATH,如何解决此问题?可能的解决方案是在包__init__.py中使用条件进行条件导入.资源包的示例如下:
if __name__ == 'resources':
from models.transactions import TransactionsModel
from controllers.transactions import get_transactions
elif __name__ == 'app.resources':
from ..models.transactions import TransactionsModel
from ..controllers.transactions import get_transactions
要克服的最后一个障碍是如何将其拉入resources.py.在__init__.py中完成的导入已绑定到该文件,不能用于resources.py.通常,其中将在resource.py中包括以下导入:
import resources
但是,还是资源还是app.resources?看来困难对我们来说已经走得更远了. importlib提供的工具可以在这里提供帮助,例如,以下将进行正确的导入:
from importlib import import_module
import_module(__name__)
还有其他可以使用的方法.例如,
TransactionsModel = getattr(import_module(__name__), 'TransactionsModel')
这解决了当前情况下的错误.
另一个更直接的解决方案是在模块本身中使用绝对导入.例如,在资源中:
models_root = os.path.join(os.path.dirname(__file__), '..', 'models')
fp, file_path, desc = imp.find_module(module_name, [models_root])
TransactionsModel = imp.load_module(module_name, fp, file_path,
desc).TransactionsModel
TransactionType = imp.load_module(module_name, fp, file_path,
desc).TransactionType
只是有关使用sys.path.append(app_root)更改PYTHONPATH的说明
在resources.py中.这很好用,并且只需几行代码即可.此外,它仅更改执行文件的路径,并在完成后还原.似乎是单元测试的好用例.一个问题可能是当应用程序移至不同的环境时.