歡迎您光臨本站 註冊首頁

Python 測試框架: 尋找要測試的模塊

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
最近出現了行業級的 Python 測試框架,這意味著 Python 測試可以編寫得更簡潔、更統一,能夠產生更好的結果報告。本文討論先進的測試框架如何提供健壯的應用程序測試自動發現,以及這如何替代過去維護的集中式測試列表。

Python 編程社區非常重視單元測試和功能性測試。這種風氣不但有助於確保組件和應用程序最初的質量,還促使程序員不斷調整和改進代碼。

本文是討論現代 Python 測試框架的 三篇系列文章 的第二篇。本系列中的 第一篇文章 介紹了 zope.testing、py.test 和 nose,介紹它們如何影響 Python 項目編寫和維護測試的方式。本文介紹如何調用這三種框架、它們如何在項目中發現測試以及如何選擇並運行測試。最後一篇文章將討論如何通過各種報告特性讓測試支持更強大的技術。

Python 測試的黑暗時代

Python 項目測試曾經是非常特殊化、個人化的活動。開發人員可能先在單獨的 Python 腳本中編寫每組測試。然後,編寫一個名為 test_all.py 或 tests.py 的腳本,這個腳本導入並運行他的所有測試。但是,無論這個過程的自動化做得多麼好,這種方式仍然是特殊化的:參與項目的每個開發人員都必須知道測試腳本放在哪裡以及如何調用它們。如果某個 Python 開發人員從事十幾個項目,他就必須記住十幾個測試命令。

test_all.py(或項目採用的其他名稱)還可能手工導入所有其他測試,這可能導致風險。如果這個集中的測試列表過時了(常常是由於開發人員添加了新的測試套件,手工運行它,但是忘了把它添加到中心腳本中),那麼在 Python 包投入生產之前的最後一次測試就會遺漏許多測試。

這種無政府狀態的另一個缺點是,它要求每個測試文件包含樣板代碼,從而能夠作為單獨的命令運行。如果查看 Python 文檔或當今的一些 Python 項目,會看到許多這樣的測試示例:

# test_old.py - The old way of doing things    import unittest    class TruthTest(unittest.TestCase):      def testTrue(self):          assert True == 1        def testFalse(self):          assert False == 0    if __name__ == '__main__':  unittest.main()

本系列的 第一篇文章 已經討論過基於 TestCase 類的測試在現代環境中為什麼常常是不必要的。但是,現在注意最後兩行:它們起什麼作用?答案是,它們檢測什麼時候從命令行單獨運行這個 test_old.py 腳本,在這種情況下,它們運行一個 unittest 簡便函數,這個函數在模塊中搜索測試並運行它們。它們使這個測試文件可以獨立於項目範圍的測試腳本單獨運行。

顯然,在數十甚至數百個測試模塊中複製相同的代碼非常麻煩。另一個不太明顯的缺點是這種做法不利於標準化。如果 test_main() 函數不夠完善,無法檢測出某個模塊的測試,那麼這個模塊的行為可能與其他測試套件不匹配。因此,每個模塊在測試類的名稱、操作方式和運行方式方面稍有差異。





Python 測試的開放時代

由於主流 Python 測試框架的出現,上述的所有問題已經解決了,而且每種框架解決這些問題的方式大致相同。

首先,這三種測試框架都提供了從操作系統命令行運行測試的標準方法。這樣,每個 Python 項目就不再需要在代碼基中維護全局測試腳本。

zope.testing 包運行測試的機制是最特殊化的:因為 Zope 開發人員常常使用 buildout 設置他們的項目,常常通過 buildout.cfg 文件中的 zc.recipe.testrunner recipe 安裝測試腳本。但是,結果在不同的項目上相當一致:在我遇到的每個 Zope 項目中,開發 buildout 都會創建一個 ./bin/test 腳本,可以通過它調用項目的測試。

py.test 和 nose 項目的做法更意思。它們都提供一個命令行工具,所以每個項目完全不需要有自己的測試命令:

# Run "py.test" on the project  # in the current directory...    $ py.test    # Run "nose" on the project  # in the current directory...    $ nosetests

py.test 和 nosetests 工具甚至有幾個相同的命令行選項,比如 -v 選項在執行測試時輸出測試的名稱。可能過不了多久,只要程序員熟悉這兩種工具,就能夠運行大多數公共 Python 包的測試。

但是,還有最後一級標準化!當今的大多數 Python 項目在源代碼中包含一個頂級 setup.py 文件,它支持下面的命令:

# Common commands supported by setup.py files    $ python setup.py build  $ python setup.py install

當今的許多 Python 項目使用 setuptools 包支持標準 Python 沒有提供的 setup.py 命令,包括運行項目的所有測試的 test 命令:

# If a project's setup.py uses "setuptools"  # then it will provide a "test" command too    $ python setup.py test

這是標準化的最高層次:如果項目都以一致的方式支持 setup.py test,開發人員就可以通過統一的介面運行所有 Python 包的測試套件。nose 通過提供一個入口點支持 setup.py,這個入口點調用與 nosetests 命令相同的測試運行常式:

# A setup.py file that uses "nose" for testing    from setuptools import setup    setup(      # ...      # package metadata      # ...      setup_requires = ['nose'],      test_suite = 'nose.collector',      )

當然,即使項目提供了 setup.py 入口點,大多數開發人員可能仍然使用 nosetests,因為 nosetests 提供更強大的命令行選項。但是對於新的開發人員,如果只想在調試 bug 或添加新特性之前檢查包是否能夠在他的平台上工作,那麼 test_suite 入口點是非常方便的。





自動 Python 模塊發現

zope.testing、py.test 和 nose 的一個關鍵特性是,它們都可以搜索項目的源代碼樹,尋找項目的所有測試,所以不需要集中的測試列表。但是,它們採用的測試發現規則不太一樣,在選擇框架時需要考慮到這一點。

測試框架執行的第一步是,選擇將在哪些目錄中搜索包含測試的文件。注意,這三種框架都從整個項目的基目錄開始搜索;如果要測試名為 example 的包,那麼它們會從包含 example 的父目錄開始搜索測試。但是,這三種框架在選擇搜索哪些目錄方面有所差異:

  • zope.testing 工具向下遞歸地搜索是 Python 包的所有目錄,也就是包含 __init__.py 文件的目錄(對於 Python,這說明可以用 import 語句導入它們)。這意味著不檢查非包目錄中的數據和代碼,但是另一方面,這也意味著從理論上說程序員可以用 import 語句導入您編寫的每個測試。一些程序員覺得這讓人不舒服,希望能夠把測試放在包的一般用戶看不到的地方。
  • py.test 命令向下遞歸地搜索項目的每個目錄和子目錄,無論目錄是否是 Python 包。注意,當兩個相鄰目錄包含同名的測試時,它似乎有一個 bug。例如,如果相鄰的 dir1/test.py 和 dir2/test.py 文件都包含名為 test_example 的測試,那麼 py.test 將運行第一個測試兩次,而完全忽略第二個測試!如果為 py.test 編寫測試並把它們放在非包目錄中,就要注意保持名稱是惟一的。
  • nose 測試運行器採用的實現方式介於另兩種工具之間:它向下遞歸地搜索每個 Python 包,但是只檢查目錄名中包含單詞 test 的目錄。這意味著,如果不想讓 nose 搜索某個目錄,那麼只需注意不在目錄名中包含 test 即可。與 py.test 不同,nose 可以正確地處理包含同名測試的相鄰目錄(但是保持測試名稱惟一仍然是有幫助的,這樣在用 -v 選項顯示測試結果時不容易混淆)。

選擇了要搜索的目錄之後,這三種測試工具的做法就非常相似了:它們都尋找與某一模式匹配的 Python 模塊(也就是以 .py 結尾的文件)。zope.testing 工具在默認情況下使用正則表達式 "tests",也就是只尋找名為 tests.py 的文件,忽略其他所有文件。可以使用命令行選項或 buildout.cfg 指定另一個正則表達式:

# Snippet of a buildout.cfg file that searches for tests  # in any Python module starting with "test" or "ftest".    [test]  recipe = zc.recipe.testrunner  eggs = my_package  defaults = ['--tests-pattern', 'f?test']

py.test 更死板,總是尋找名稱以 `test_ 開頭或以 _test 結尾的 Python 模塊。nosetests 命令更靈活,它使用一個正則表達式(“((?:^|[\b_\.-])[Tt]est)”)選擇以 test 或 Test 開頭或這個單詞處於單詞邊界後面的模塊。通過在命令行上使用 -m 選項或在項目的 .noserc 文件中設置這個選項,可以指定另一個正則表達式。

哪種方法最好?儘管一些開發人員喜歡有靈活性,而且許多人認為 zope.testing 工具的搜索範圍應該更寬,不應該只限於文件名為 tests.py 的模塊,但是我實際上更喜歡 py.test 採用的方式。所有使用 py.test 的項目必須在測試命名方面採用一致的約定,這讓其他程序員更容易閱讀和維護測試。在使用另外兩種框架時,閱讀或創建測試文件需要兩步:首先,必須了解這個項目使用的正則表達式,然後才能檢查它的代碼。如果您同時從事多個項目,就必須記住幾種不同的測試文件命名約定。


[火星人 ] Python 測試框架: 尋找要測試的模塊已經有937次圍觀

http://coctec.com/docs/linux/show-post-68813.html