[英]Python Run Unittest as Package Import Error
I.前言:應用程序目錄結構和模塊列在帖子的末尾。
II。 問題陳述:如果沒有設置PYTHONPATH應用程序運行,但單元測試失敗並出現ImportError:No模塊名為models.transactions 。 嘗試在app.py中導入事務時會發生這種情況。 如果PYTHONPATH設置為/sandbox/app
,則應用程序和unittest都會運行且沒有錯誤。 解決方案的約束是不必設置PYTHONPATH,並且不必以編程方式修改sys.path。
III。 詳細信息:考慮設置PYTHONPATH並將test_app.py
作為包/sandbox$ python -m unittest tests.test_app
。 查看整個代碼中的__main__
的print語句:
models : app.models.transactions
models : models.transactions
resources: app.resources.transactions
app : app.app
test : tests.test_app
unittest首先導入應用程序,因此有app.models.transactions
。 應用嘗試的下一個導入是resources.transactions
。 當進口它使自己的進口models.transactions
,然后我們看到了__name__
的app.resources.transactions
。 接下來是app.app
導入,最后是unittest模塊tests.test.app
。 設置PYTHONPATH允許應用程序解析models.transactions!
一種解決方案是將models.transactions
放在resources.transaction
。 但還有另一種方法可以解決這個問題嗎?
為了完整性,在運行應用程序時, __main__
的print語句是:
models : models.transactions
resources: resources.transactions
app : __main__
這是預期的,並且沒有嘗試在/sandbox/app
或sideways之上的導入。
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)app:
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)models.transactions
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()
值得一提的是,Flask文檔說要將應用程序作為包運行,並設置環境變量: FLASK_APP
。 然后應用程序從項目根目錄運行: $ python -m flask run
。 現在導入將包括應用程序根目錄,例如app.models.transactions
。 由於unittest以相同的方式運行,作為項目根目錄中的包,所有導入也在那里解析。
問題的症結可以用以下方式描述。 test_app.py需要訪問sideways導入,但如果它作為腳本運行,如:
/sandbox/test$ python test_app.py
它有
。 這意味着,諸如__name__
== __main__
from models.transactions import TransactionsModel
將不會被解析,因為它們是橫向的而不是層次結構中的低級。 要解決此問題,test_app.py可以作為包運行:
/sandbox$ python unittest -m test.test_app
-m
開關告訴Python這樣做。 現在該包可以訪問app.model
因為它在/sandbox
運行。 test_app.py
中的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__
.pyresources
包的示例如下:
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__
.pyresources.py
。 通常會在resources.py
包含以下導入:
import resources
但是,再次,它是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
關於在resources.py
使用sys.path.append(app_root)
更改PYTHONPATH的注釋。 這很好用,並且只需要幾行代碼就可以了。 此外,它僅更改執行文件的路徑並在完成時還原。 似乎是unittest的一個很好的用例。 一個問題可能是應用程序移動到不同的環境。
TL; DR:如果可以,您應該更改兩個導入:
from models.transactions import TransactionsModel
from resources.transactions import Transactions
至
from app.models.transactions import TransactionsModel
from app.resources.transactions import Transactions
更長的版本
當你說:
如果未設置PYTHONPATH,則應用程序運行...
你如何運行應用程序? 我猜的是......
cd sandbox/app
python app.py
因為app.py
中的導入不正確(缺少最頂層的app
模塊),否則運行app
同樣會失敗。
IMO你也不需要(嚴格地)將tests
作為一個模塊(即你可以放棄tests/__init__.py
)並且只需運行測試:
python tests/test_app.py
重點是.
(當前目錄)總是被包含在缺省的PYTHONPATH
,這是從那里的模塊被加載/搜索進口-在這種情況下,當你的測試執行from app.app import create_app
在第一行app.py
:
from models.transactions import TransactionsModel
將導致錯誤(沒有./models
模塊/目錄)。
如果您無法更改app.py
應用程序模塊中的app.py
,或者以其他方式受到限制,我唯一能想到的選項(除了修改PYTHONPATH
或操縱您明確排除的sys.path
之外),唯一的其他選擇就是將要:
cd sandbox/app
python ../tests/test_app.py
但是你必須在你的單元測試中改變你的進口:
from app import create_app
from resources.transactions import Transactions
換句話說,你不能擁有你的蛋糕並吃掉它:)在不改變Python查找路徑的情況下,你需要讓所有模塊從同一個地方( .
)開始,從而保持一致。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.