Biopython 測試框架
Biopython 有一個回歸測試框架 (檔案 run_tests.py
),基於 unittest,這是 Python 的標準單元測試框架。為模組提供全面的測試是確保 Biopython 程式碼在發布前盡可能沒有錯誤的最重要方面之一。這也往往是貢獻中最被低估的方面之一。本章旨在使執行 Biopython 測試和撰寫良好的測試程式碼盡可能容易。理想情況下,每個進入 Biopython 的模組都應該有一個測試(並且也應該有文件!)。我們強烈鼓勵所有開發人員和任何從原始碼安裝 Biopython 的人執行單元測試。
執行測試
當您下載 Biopython 原始碼或從我們的原始碼儲存庫檢出時,您應該會找到一個名為 Tests
的子目錄。其中包含關鍵腳本 run_tests.py
、許多名為 test_XXX.py
的個別腳本,以及許多其他子目錄,其中包含測試套件的輸入檔案。
作為建構和安裝 Biopython 的一部分,您通常會在 Biopython 原始碼頂層目錄的命令列中執行完整測試套件,如下所示
$ python setup.py test
這實際上等同於前往 Tests
子目錄並執行
$ python run_tests.py
您通常會只想執行某些測試,這可以這樣做
$ python run_tests.py test_SeqIO.py test_AlignIO.py
在給定測試清單時,.py
副檔名是可選的,因此您也可以只輸入
$ python run_tests.py test_SeqIO test_AlignIO
若要執行 docstring 測試(請參閱 撰寫 doctest 章節),您可以使用
$ python run_tests.py doctest
您也可以透過新增 --offline
來略過任何已設定明確線上元件的測試,例如
$ python run_tests.py --offline
預設情況下,run_tests.py
會執行所有測試,包括 docstring 測試。
如果個別測試失敗,您也可以嘗試直接執行它,這可能會給您更多資訊。
基於 Python 標準 unittest
框架的測試會 import unittest
,然後定義 unittest.TestCase
類別,每個類別都有一個或多個子測試作為以 test_
開頭的方法,這些方法會檢查程式碼的某些特定方面。
使用 Tox 執行測試
與大多數 Python 專案一樣,您也可以使用 Tox 在多個 Python 版本上執行測試,前提是這些版本已安裝在您的系統中。
我們不會在我們的程式碼庫中提供組態檔 tox.ini
,因為難以確定使用者特定的設定(例如 Python 版本的執行檔名稱)。您可能也只對針對我們支援的 Python 版本子集測試 Biopython 感興趣。
如果您有興趣使用 Tox,您可以從下面顯示的範例 tox.ini
開始
[tox]
envlist = pypy,py38,py39
[testenv]
changedir = Tests
commands = {envpython} run_tests.py --offline
deps =
numpy
reportlab
使用上面的範本,執行 tox
將針對 PyPy、Python 3.8 和 3.9 測試您的 Biopython 程式碼。它假設這些 Python 的執行檔名稱對於 Python 3.8 是「python3.8」,依此類推。
撰寫測試
假設您想為名為 Biospam
的模組撰寫一些測試。這可以是您撰寫的模組,或是不包含任何測試的現有模組。在以下範例中,我們假設 Biospam
是一個執行簡單數學運算的模組。
每個 Biopython 測試都包含一個包含測試本身的腳本,以及一個可選的目錄,其中包含測試使用的輸入檔案
test_Biospam.py
– 您模組的實際測試程式碼。Biospam
[選用]– 任何必要輸入檔案將放置的目錄。如果您有任何應該手動檢閱的輸出檔案,請將它們輸出到這裡(但不建議這樣做)以防止主 Tests 目錄擁塞。一般來說,請使用暫存檔案/資料夾。
任何在 Tests
目錄中具有 test_
前綴的腳本都會被 run_tests.py
找到並執行。下面,我們顯示一個範例測試腳本 test_Biospam.py
。如果您將此腳本放在 Biopython Tests
目錄中,則 run_tests.py
將會找到它並執行其中包含的測試
$ python run_tests.py
test_Ace ... ok
test_AlignIO ... ok
test_BioSQL ... ok
test_BioSQL_SeqIO ... ok
test_Biospam ... ok
test_CAPS ... ok
test_Clustalw ... ok
...
----------------------------------------------------------------------
Ran 107 tests in 86.127 seconds
使用 unittest
撰寫測試
unittest
-框架自版本 2.1 起就包含在 Python 中,並在 Python 函式庫參考中記載(我知道您會按照建議將其放在枕頭下)。還有 unittest 的線上文件。如果您熟悉 unittest
系統(或類似的系統,如 nose 測試框架),您應該不會有任何問題。您可能會發現查看 Biopython 中的現有範例也很有幫助。
以下是 Biospam
的最小 unittest
-樣式測試腳本,您可以複製貼上以開始使用
import unittest
from Bio import Biospam
class BiospamTestAddition(unittest.TestCase):
def test_addition1(self):
result = Biospam.addition(2, 3)
self.assertEqual(result, 5)
def test_addition2(self):
result = Biospam.addition(9, -1)
self.assertEqual(result, 8)
class BiospamTestDivision(unittest.TestCase):
def test_division1(self):
result = Biospam.division(3.0, 2.0)
self.assertAlmostEqual(result, 1.5)
def test_division2(self):
result = Biospam.division(10.0, -2.0)
self.assertAlmostEqual(result, -5.0)
if __name__ == "__main__":
runner = unittest.TextTestRunner(verbosity=2)
unittest.main(testRunner=runner)
在除法測試中,我們使用 assertAlmostEqual
而不是 assertEqual
以避免因四捨五入誤差而導致測試失敗;請參閱 Python 文件中的 unittest
章節,以瞭解詳細資訊和 unittest
中可用的其他功能 (線上參考)。
這些是基於 unittest
的測試的重點
測試案例儲存在繼承自
unittest.TestCase
的類別中,並涵蓋您程式碼的一個基本方面您可以將方法
setUp
和tearDown
用於任何重複的程式碼,這些程式碼應在每個測試方法之前和之後執行。例如,setUp
方法可用於建立您正在測試的物件的實例,或開啟檔案控制代碼。tearDown
應該執行任何「清理」工作,例如關閉檔案控制代碼。測試以
test_
作為前綴,每個測試都應該涵蓋您嘗試測試的特定部分。您可以在一個類別中執行任意數量的測試。在測試腳本結尾,您可以使用
if __name__ == "__main__": runner = unittest.TextTestRunner(verbosity=2) unittest.main(testRunner=runner)
在腳本自行執行時執行測試(而不是從
run_tests.py
匯入)。如果您執行此腳本,您會看到類似以下內容$ python test_BiospamMyModule.py test_addition1 (__main__.TestAddition) ... ok test_addition2 (__main__.TestAddition) ... ok test_division1 (__main__.TestDivision) ... ok test_division2 (__main__.TestDivision) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.059s OK
為了更清楚地指示每個測試的作用,您可以將 docstring 新增到每個測試。這些會在執行測試時顯示,如果測試失敗,這會很有用。
import unittest from Bio import Biospam class BiospamTestAddition(unittest.TestCase): def test_addition1(self): """An addition test""" result = Biospam.addition(2, 3) self.assertEqual(result, 5) def test_addition2(self): """A second addition test""" result = Biospam.addition(9, -1) self.assertEqual(result, 8) class BiospamTestDivision(unittest.TestCase): def test_division1(self): """Now let's check division""" result = Biospam.division(3.0, 2.0) self.assertAlmostEqual(result, 1.5) def test_division2(self): """A second division test""" result = Biospam.division(10.0, -2.0) self.assertAlmostEqual(result, -5.0) if __name__ == "__main__": runner = unittest.TextTestRunner(verbosity=2) unittest.main(testRunner=runner)
現在執行腳本將會顯示
$ python test_BiospamMyModule.py An addition test ... ok A second addition test ... ok Now let's check division ... ok A second division test ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK
如果您的模組包含 docstring 測試(請參閱 撰寫 doctest 章節),您可能會希望將這些測試包含在要執行的測試中。您可以透過修改 if __name__ == "__main__":
下的程式碼來達成,如下所示:
if __name__ == "__main__":
unittest_suite = unittest.TestLoader().loadTestsFromName("test_Biospam")
doctest_suite = doctest.DocTestSuite(Biospam)
suite = unittest.TestSuite((unittest_suite, doctest_suite))
runner = unittest.TextTestRunner(sys.stdout, verbosity=2)
runner.run(suite)
只有在您想要在執行 python test_Biospam.py
時執行 docstring 測試,且該測試有複雜的執行階段相依性檢查時,才需要這麼做。
一般來說,應將 docstring 測試加入到 run_tests.py
中,如下所述。
撰寫 doctest
Python 模組、類別和函式支援使用 docstring 的內建文件。 doctest 框架(包含在 Python 中)允許開發人員將可執行的範例嵌入到 docstring 中,並自動測試這些範例。
目前只有 Biopython 的一部分包含 doctest。 run_tests.py
腳本負責執行 doctest。為此,在 run_tests.py
腳本的頂部有一個手動編譯的要跳過的模組列表,這在可能未安裝的可選外部相依性(例如 Reportlab 和 NumPy 函式庫)時非常重要。因此,如果您已在 Biopython 模組的 docstring 中新增了一些 doctest,為了將它們排除在 Biopython 測試套件之外,您必須更新 run_tests.py
以包含您的模組。目前,run_tests.py
的相關部分如下所示:
# Following modules have historic failures. If you fix one of these
# please remove here!
EXCLUDE_DOCTEST_MODULES = [
"Bio.PDB",
"Bio.PDB.AbstractPropertyMap",
"Bio.Phylo.Applications._Fasttree",
"Bio.Phylo._io",
"Bio.Phylo.TreeConstruction",
"Bio.Phylo._utils",
]
# Exclude modules with online activity
# They are not excluded by default, use --offline to exclude them
ONLINE_DOCTEST_MODULES = ["Bio.Entrez", "Bio.ExPASy", "Bio.TogoWS"]
# Silently ignore any doctests for modules requiring numpy!
if numpy is None:
EXCLUDE_DOCTEST_MODULES.extend(
[
"Bio.Affy.CelFile",
"Bio.Cluster",
# ...
]
)
請注意,我們主要將 doctest 視為文件,因此您應該堅持使用典型用法。一般來說,處理錯誤條件等複雜範例最好留給專用的單元測試。
請注意,如果您想撰寫涉及檔案剖析的 doctest,定義檔案位置會使事情複雜化。理想情況下,應使用相對路徑,假設程式碼將從 Tests
目錄執行,請參閱 Bio.SeqIO
doctest 以取得範例。
若要僅執行 docstring 測試,請使用:
$ python run_tests.py doctest
請注意,doctest 系統很脆弱,需要小心確保您的輸出在 Biopython 支援的所有不同 Python 版本中都匹配(例如,浮點數的差異)。
在教學文件中撰寫 doctest
您正在閱讀的本教學文件包含許多程式碼片段,這些片段通常格式化為 doctest。我們在檔案 test_Tutorial.py
中有自己的系統,允許將教學文件原始碼中的程式碼片段標記為 Python doctest 來執行。這是透過在每個 Python 主控台(pycon)區塊之前新增特殊的 .. doctest
註解行來實現,例如:
.. doctest
.. code:: pycon
>>> from Bio.Seq import Seq
>>> s = Seq("ACGT")
>>> len(s)
4
通常,程式碼範例不是獨立的,而是從前一個 Python 區塊繼續。在這裡,我們使用神奇的註解 .. cont-doctest
,如下所示:
.. cont-doctest
.. code:: pycon
>>> s == "ACGT"
True
特殊的 .. doctest
註解行可以採用工作目錄(相對於 Doc/
資料夾),如果您有任何範例資料檔案,則可以使用該目錄,例如 .. doctest examples
將使用 Doc/examples
資料夾,而 .. doctest ../Tests/GenBank
將使用 Tests/GenBank
資料夾。
在目錄參數之後,您可以指定任何為了執行測試而必須存在的 Python 相依性,方法是新增 lib:XXX
來表示 import XXX
必須有效,例如:.. doctest examples lib:numpy
您可以透過以下方式執行教學文件的 doctest:
$ python test_Tutorial.py
或
$ python run_tests.py test_Tutorial.py