Pytest 基本概念
開始使用 Pytest 執行測試吧。 如上一個單元所述,Pytest 具有高度可設定性,而且可以處理複雜的測試套件,但不需要太多才能開始撰寫測試。 事實上,能讓您輕鬆撰寫測試的架構是再好不過了。
本課程結束後,您應該已經有開始撰寫第一個測試所需的所有項目,並使用 Pytest 執行測試。
慣例
在深入介紹如何撰寫測試之前,我們必須先瞭解 Pytest 使用的一些測試慣例。
Python 中測試檔案、測試目錄或一般測試版面配置沒有太多硬式規則。 瞭解這些規則後,您就可以使用自動測試探索和執行功能,且不需任何額外的設定。
測試目錄和測試檔案
測試的主要目錄就是「tests」目錄。 您可以將此目錄放在專案的根層級,但放在程式碼模組旁也很常見。
注意
在本課程模組中,我們預設會在專案的根目錄中使用 測試 。
來看看名為「jformat
」的小型 Python 專案根目錄會如何顯示吧:
.
├── README.md
├── jformat
│ ├── __init__.py
│ └── main.py
├── setup.py
└── tests
└── test_main.py
「tests」目錄位於內含單一測試檔案的專案根目錄。 在此案例中,我們將測試檔案命名為「test_main.py」。 此範例示範兩個重要慣例:
- 使用 [tests] 目錄放置測試檔案和巢狀測試目錄。
- 使用「test」作為測試檔案的前置詞。 前置詞表示該檔案內含測試程式碼。
警告
請勿使用「test
」(單數) 作為目錄名稱。 「test
」名稱是 Python 模組,因此建立同名目錄後,該檔案就會被覆寫。 請一律改為使用複數型 tests
。
測試函式
使用 Pytest 的強大論點是它允許您撰寫測試函式。 與測試檔案類似,測試函式必須以「test_
」作為前置詞。 「test_
」前置詞可確保 Pytest 會收集測試並加以執行。
以下是簡單測試函式看起來的樣子:
def test_main():
assert "a string value" == "a string value"
注意
如果您熟悉 unittest
,在測試函式中看到 assert
的用法可能會出人意料。 我們稍後會更詳細地介紹一般判斷提示,但使用 Pytest 後,您將收到有關一般判斷提示的豐富失敗報告。
測試類別和測試方法
與檔案和函式的慣例類似,測試類別和測試方法會使用下列慣例:
- 測試類別以「
Test
」作為前置詞 - 測試方法以「
test_
」作為前置詞
與 Python unittest
程式庫的核心差異是不需進行任何繼承。
下列範例會針對類別和方法使用這些前置詞和其他 Python 命名慣例。 它示範正在檢查應用程式中使用者名稱的小型測試類別。
class TestUser:
def test_username(self):
assert default() == "default username"
執行測試
Pytest 既是測試架構,也是測試執行器。 測試執行器是在命令列中運行的可執行檔,其主要功能包括:
- 尋找測試回合的所有測試檔案、測試類別和測試函式,方便您執行測試集合。
- 執行所有測試,方便您開始測試回合。
- 追蹤失敗、錯誤和已通過的測試。
- 在測試回合結束時提供內容豐富的報告。
注意
因為 Pytest 是外部連結庫,所以 必須 安裝才能使用它。
我們可以透過「test_main.py」檔案中的內容瞭解 Pytest 如何執行測試:
# contents of test_main.py file
def test_main():
assert True
我們可以在命令列中,與「test_main.py」檔案所在的相同路徑上執行可執行檔 pytest
:
$ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item
test_main.py . [100%]
============================ 1 passed in 0.00s =============================
Pytest 會在幕後收集測試檔案中的範例測試,而不需進行任何設定。
強大的判斷提示陳述式
到目前為止,我們的測試範例全都使用一般 assert
呼叫。 通常,在 Python 中, assert
語句不會用於測試,因為它在判斷提示失敗時缺少適當的報告。 不過,Pytest 沒有這個限制。 在幕後,Pytest 可讓 語句執行豐富的比較,而不需要強制使用者撰寫更多程式代碼或設定任何專案。
藉由使用純 assert
文字語句,您可以使用 Python 的運算元;例如、 >
、 <
、 !=
、 >=
或 <=
。 所有 Python 運算子都是有效的。 這可能是 Pytest 最重要的功能:您不需學習新的語法就能撰寫判斷提示。
我們來看看這會對使用 Python 物件處理一般比較造成什麼影響。 在此案例中,讓我們仔細檢查比較長字串時的失敗報告:
================================= FAILURES =================================
____________________________ test_long_strings _____________________________
def test_long_strings():
left = "this is a very long strings to be compared with another long string"
right = "This is a very long string to be compared with another long string"
> assert left == right
E AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E - This is a very long string to be compared with another long string
E ? ^
E + this is a very long strings to be compared with another long string
E ? ^ +
test_main.py:4: AssertionError
Pytest 會顯示失敗的相關實用內容:字串開頭的不正確大小寫,以及單字中的額外字元。 但除了字串之外,Pytest 也能協助其他物件和資料結構。 例如,以下是它針對清單的運作模式:
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E At index 1 diff: 'wheat' != 'coffee'
E Full diff:
E - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E ? ---------
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? +++++++++
test_main.py:9: AssertionError
此報告會識別出索引 1 (即清單中的第二個項目) 不同。 它不僅會識別索引編號,還會提供失敗情況的表示法。 除了項目比較之外,它也可以回報是否有項目遺漏並提供相關資訊,方便您瞭解遺漏的究竟是哪些項目。 在下列案例中,就是 "milk"
:
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E At index 2 diff: 'coffee' != 'salt'
E Left contains one more item: 'milk'
E Full diff:
E - ['sugar', 'wheat', 'salt', 'water', 'milk']
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? ++++++++++
test_main.py:9: AssertionError
最後,我們來看看針對字典的運作模式。 如果發生失敗情況,要比較兩個大型字典就很困難,但 Pytest 十分擅長提供內容並找出失敗情況:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E Omitting 3 identical items, use -vv to show
E Differing items:
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E {'number': 39} != {'number': 38}
E Full diff:
E {
E 'county': 'Frett',...
E
E ...Full output truncated (12 lines hidden), use '-vv' to show
在此測試中,字典裡有兩處失敗。 其中一處是 "street"
為不同值,另一處則是 "number"
不相符。
Pytest 準確地偵測到這些差異(即使這是單一測試中的一個失敗)。 因為字典包含許多項目,Pytest 會省略相同的部分,並且只顯示相關的內容。 我們來看看如果使用建議的 -vv
旗標來提升輸出的詳細程度,究竟會發生什麼事:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E Common items:
E {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E Differing items:
E {'number': 39} != {'number': 38}
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E Full diff:
E {
E 'county': 'Frett',
E - 'number': 38,
E ? ^
E + 'number': 39,
E ? ^
E 'state': 'Nevada',
E - 'street': 'Ferry Lane',
E ? - ^
E + 'street': 'Ferry Ln.',
E ? ^
E 'zipcode': 30877,
E }
執行 pytest -vv
可讓報告中的資訊更詳細,並提供精細的比較。 此報告不僅會偵測並顯示失敗情況,還可讓您快速進行變更以針對問題進行補救。