技術面試沒過,居然是沒有用這個測試框架
1、引言
我有一個朋友是做Python自動化測試的。前幾天他告訴我去參加一個大廠面試被刷了。
我問他是有沒有總結被刷下來的原因。他說面試官問了一些 pytest 單元測試框架相關的知識,包括什么插件系統和用力篩選。但是他所在的公司用的技術是基於 unittest 的,沒有用過 pytest。
我跟他說你可以和技術面試官說明,在實際過程當中你沒有使用過 pytest,但是你可以后面再學。這哥們說:我就是這樣跟面試官說的,但是面試官告訴我 pytest 現在已經是行業里面的主流,還在堅持用 unittest 說明我的技術已經過時了,沒有跟上現在測試領域的發展。
實際上他在面試之前已經了解過 pytest 的一些基礎用法,但是網上的一些資料,都是停留在用法和一些知識點的講解,沒有深入到 pytest 內部運行和一些高級特性。所以被問到的時候,自己臨時抱佛腳的一些知識都沒有用上。
后面我給這位朋友補習了一些關於Python的高級特性。現在我連同基礎部分的內容一起貼出來,希望對Python自動化測試的一些朋友有所幫助。
2、為什么用單元測試框架?
首先我要說明一下什么是單元測試框架?
unittest 和 pytest 都是單元測試框架。單元測試指的是在編程過程當中形成的對函數或者是類下面的方法進行測試的一個過程。
在不使用任何框架的前提下,其實也是可以進行單元測試的。比如我們可以通過 if 判斷 、異常處理或者是其他的流程控制來表示測試是否通過。
def add(a, b):
return a + b
def test_add():
ret = add(3, 4)
if ret == 7:
print("add 函數的測試通過")
else:
print("add 函數的測試失敗")
如果要運行這個用例,需要手工調用 test_add 這個函數:
test_add()
接下來,使用 python 運行這個文件,就能得到測試結果:
python test_add.py
雖然說上面我們通過 if 判斷,對一個函數進行了測試,而且得到了測試結果,但是流程是比較復雜的:
- 首先我們需要人工去判斷結果,
- 第2我們需要通過 Python去運行模塊。
- 第3,我們還需要顯性的去調用 test_add 這個函數。
這還只是在我們只測試了一個函數的情況下,當需要測試的函數或者類越來越多的時候,這個過程會越來越復雜。
而使用單元測試框架,可以極大的簡化我們對單元測試的過程,使用單元測試框架以后,框架會幫我們自動去收集用例、運行用例、生成報告。
3、pytest 的基礎使用
上面的測試代碼使用 pytest 編寫,可以這樣寫。
def add(a, b):
return a + b
def test_add():
assert 7 == add(3,4)
寫完測試用例以后,我們只需要在目錄下輸入pytest 指令,就可以自動運行用例,而且呢結果會直接顯示在命令行的下方。
上面講的是單元測試過程,也就是說對某個函數或者是類下面的方法進行測試,有的人可能會不理解。在實際工作過程當中很少進行單元測試啊,測試人員做的更多的是接口測試,UI測試,pytest 怎么用呢?
實際上不管是接口測試還是UI測試,都是可以使用 pytest。當你進行接口測試的時候,你只需要把訪問接口的過程封裝成一個Python函數。
def visit_api():
print("訪問接口,得到結果...")
return response
def test_api():
assert expected_response == visit_api()
當你進行 web測試的時候,你只需要操作瀏覽器的過程封裝成一個函數?
def browser_method():
print("點點點")
return ui_response
def test_web():
assert expected_response == browser_method()
在這種情況下。接口訪問和web操作都是以函數形式存在的,我們直接去測試這個 Python 函數,也是一個單元測試的過程。
因為 pytest 是一個第三方的框架,所以我們先要安裝。安裝方式非常簡單,只需要通過 pip 這個包管理工具安裝就可以了。
pip install -U pytest
安裝完成以后,我們可以向使用上面的那個例子一樣:
- 第1步:定義一個測試函數,這個測試函數通常會調用被測函數。
- 第2步:使用assert斷言,assert 后面可以跟任意的 Python 條件表達式。
assert 4 < 5
assert "yuze" in "yuze wang"
assert isinstance(6, int)
測試用例函數有 2 個注意事項:
- 函數名稱以
test_
開頭; - 保存測試用例的文件以
test_*.py
的形式或者*_test.py
的形式。
例行用了以后呢,在命令行當中會顯示4個部分的內容:
- 第1個部分,測試用例和通過的結果,
- 第2個部分,失敗用例回溯信息,
- 第3個部分,輸出捕獲信息,
- 第4個部分,總結信息。
在拍test當中通過的測試用例,不會有特別詳細的結果,但是這是失敗的測試用例默認會有非常詳細的結果,而且會幫你捕獲錯誤原因。
4、測試夾具(Fixture)是什么?
在測試過程當中,有時你需要提前給你的測試用例去准備一個運行環境。這個測試環境通常來說被稱為測試夾具(Fixture),又被稱為固定裝置、測試固件等。
- 當你要測試一個電器的時候,你需要提供不同的輸入電壓電流的環境,
- 當你測試一台電腦網絡的時候,必須要提供網絡環境,
- 當你要測試一個手機游戲能否被安裝時,你需要提供一台手機環境,
- 當你要測試一個軟件能否登錄的時候,你需要准備用戶名和密碼這樣的用戶環境,
- 當你要測試一個數據庫能否操作的時候,需要提供數據庫的連接環境。
現在我們來舉一個夾具的例子,我們需要測試一個注冊的函數。這個注冊函數提供兩個參數,第1個參數是手機號,第2個參數是密碼。注冊函數的邏輯就是對手機號碼和密碼進行校驗,如果通過校驗表示注冊成功,如果不通過表示注冊失敗。
def is_mobile(number: str):
if re.search(r"^1[3-9][0-9]{9}$", number):
return True
return False
def register(mobile, password):
if is_mobile(mobile) and len(password) >= 6:
return "success"
return "fail"
def test_register():
assert "success" == register("13177778888", "123456")
這個測試用例並沒有什么問題,但是它存在優化的空間。一個優化的空間是每個手機號碼都是我們手工生成的,當需要編寫多組數據測試時,會有一點費時間。現在我們可以編寫一個函數,自動生成一個手機號碼,當我有多組數據需要測試的時候,我只需要重復調用生成手機號碼的函數,就可以獲取測試數據。這個生成手機號碼的函數呢,就是一個夾具。
def gen_a_mobile():
"""隨機生成 13 開頭的手機號碼。"""
random_num = "".join([str(random.randint(1,9)) for i in range(9)])
return "".join(["13", random_num])
pytest 提供了一種叫做依賴注入的機制,當一個函數被聲明為夾具的時候,可以在測試函數中傳入這個夾具的名稱,pytest會自動調用它。
import random
import pytest
@pytest.fixture
def gen_a_mobile():
"""隨機生成 13 開頭的手機號碼。"""
random_num = "".join([str(random.randint(1,9)) for i in range(9)])
return "".join(["13", random_num])
def test_register(gen_a_mobile):
assert "success" == register(gen_a_mobile, "123456")
pytest 當中的夾具系統非常非常的靈活,后面如果有時間的我專門寫文章跟大家講解夾具系統。
5、數據驅動和參數化
現在我們編寫的函數和測試用例是1對1的關系,也就是說,當你想測試某個功能場景的時候,你必須要去編寫一個對應的測試函數。當測試的場景越來越多,測試數據越來越復雜的情況下,需要編寫更多的測心率函數,而這些函數的邏輯基本上是重復的。
在 pytest 當中可以使用參數化這種測試手段,簡化編寫用例函數的過程。我們並不需要為每一組測試數據單獨去編寫一個測試函數,而是采取多種數據共用一個函數的方式。如果測試操作幾乎一致,可以重復使用這一個函數進行測試。
import pytest
cases = [
(1, 2, 3),
("hello", "world", "hello world"),
(1, "world", "1world")
]
@pytest.mark.parametrize("a,b,expected", cases)
def test_add(a, b, expected):
assert expected == add(a, b)
在這個例子當中,cases這個變量存儲了三組測試用例的數據,每一組測試數據用一個元組表示,元組的第1個元素代表a,第2個元素代表B,第3個元素代表預期結果。
@pytest.mark.parametrize("a,b,expected", cases)
這一行代碼的意思是說,每一次從cases變量當中取出一組測試數據。分別用a、b 、expected 三個變量接收,然后我們把這三個變量名作為函數的參數傳遞到 test_add 當中,就實現了參數化的過程。
當使用這一種參數化的手段進行測試的時候,測試數據和測試函數是多對一的關系,對於多組測試數據,我們只需要去編寫一個測試函數,極大的提升了代碼編寫效率。
6、測試報告和插件
最后我們來說一下測試報告。pytest 當中的測試報告,通常是以插件的形式生成的,如果你想生成一個html格式的測試報告,可以先安裝 pytest-html 這個插件。
pip install pytest-html
接下來你需要在運行用例的時候,在 pytest 命令后面加上 --html=<測試報告名稱>.html
pytest --html=report.html
當運行完用例以后,你可以在當前目錄下找到一個 report.html 的文件,打開就可以查看測試報告了。
pytest 之所以成為主流,有很多的原因,其中最重要的一個原因是因為其強大的插件系統。任何一個程序員,只要遵循一些簡單的規范,就可以輕易的編寫插件。后面我們再跟大家深入去研究 pytest 當中的夾具系統,插件系統和鈎子函數這些特性。