簡體   English   中英

Python運行Unittest作為包導入錯誤

[英]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__ .py使用條件來進行條件導入。 resources包的示例如下:

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 通常會在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM