初次接觸測試框架的你,肯定希望能更快速的編寫自己的測試代碼,那么我們開始吧!
1.Pytest介紹
pytest是python的一種單元測試框架,與python自帶的unittest測試框架類似,但更簡潔并高效。
官方網站優點簡介:
- 非常容易上手,入門簡單,文檔豐富,文檔中有很多實例可以參考
- 能夠支持簡單的單元測試和復雜的功能測試
- 支持參數化
- 執行測試過程中可以將某些測試跳過,或者對某些預期失敗的case標記成失敗
- 支持重復執行失敗的case
- 支持運行由nose, unittest編寫的測試case
- 具有很多第三方插件,并且可以自定義擴展
- 方便的和持續集成工具集成
介紹到此吧,開始入手吧
2. 安裝pytest
pip install -U pytest
easy_install -U pytest
二選一即可
安裝完成驗證安裝的版本
py.test --version
3. 官方實例
-
通過該實例,基本有個認識
# content of test_sample.py def func(x): return x+1 def test_func(): assert func(3) == 5
在文件所在目錄打開終端,輸入pytest即可執行并查看成功or失敗原因了
-
多測試case
# content of test_class.py class TestClass: def test_one(self): x = "this" assert 'h' in x def test_two(self): x = "hello" assert hasattr(x, 'check')
運行:
pytest test_class.py
執行結果如下
$ py.test -q test_class.py .F ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ self = <test_class.TestClass object at 0x7fbf54cf5668> def test_two(self): x = "hello" > assert hasattr(x, 'check') E assert hasattr('hello', 'check') test_class.py:8: AssertionError 1 failed, 1 passed in 0.01 seconds
4.編寫測試樣例與規范
-
python命名規范
一 包名、模塊名、局部變量名、函數名 全小寫+下劃線式駝峰 example: this_is_var 二 全局變量 全大寫+下劃線式駝峰 example: GLOBAL_VAR 三 類名 首字母大寫式駝峰,否則會報錯提示語法錯誤 example: ClassName()
-
測試用例規則
- 測試文件以test_開頭(以_test結尾也行) - 測試類以Test開頭,并且不能帶有__init__方法 - 測試函數以test_開頭 - 斷言使用基本的assert即可
5.入門實戰之批量測試
這里有一個實際應用,我想批量檢查一批機器上的CPU,內存,和機器上的2個分區,并將CPU大于80%,內存大于95,和分區大于80%的機器找出來,如何實現呢?
假設這里已經提供好了API,可以讀取到所有機器上的CPU、內存、分區信息,API地址為http://api/latestMeteris?userCode=xxx&token=xxx&host=’172.20.116.70,172.20.116.72’&service=CPU,Memory,Disk
訪問API時返回一串JSON,信息如下:
{
"message":"success",
"result":"success",
"start":"2017-02-28 13:54:53",
"data":{
"Memory":{
"172.20.116.72":{
"swap_used":["9.60%"],
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"ram_used":["25.52%"]
},
"172.20.116.70":{
"swap_used":["6.17%"],
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"ram_used":["25.97%"]
}
},
"CPU":{
"172.20.116.72":{
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"cpu_prct_used":["3.00%"]
},
"172.20.116.70":{
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"cpu_prct_used":["1.00%"]
}
},
"Disk":{
"172.20.116.72":{
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"/export":["25.06%"],
"/":["21.6%"]
},
"172.20.116.70":{
"datetime":["2017-02-28 13:54:41"],
"merge_time":["2017-02-28 13:54:41"],
"/export":["44.68%"],
"/":["36.15%"]
}
}
},
"host_size":2,
"end":"2017-02-28 13:54:53"
}
pytest腳本如下
#!/usr/bin/python
import os
import sys
import json
import urllib
import urllib2
import pytest
iplist = ["172.20.116.70", "172.20.116.72"] #定義IP列表
ips = ','.join(iplist)
url = 'http://api/latestMeteris?userCode=xxx&token=xxx&host=' + ips + '&service=CPU,Memory,Disk'
req = urllib.urlopen(url)
result = req.read() #get a string type
a = json.loads(result) #transfer string type to dict type
@pytest.mark.parametrize('ip', iplist)
def test_cpu(ip):
value = a["data"]["CPU"][ip]["cpu_prct_used"][0]
assert float(value.strip("%")) < 80
@pytest.mark.parametrize('ip', iplist)
def test_memory(ip):
value = a["data"]["Memory"][ip]["ram_used"][0]
assert float(value.strip("%")) < 95
@pytest.mark.parametrize('ip', iplist)
def test_disk(ip):
value_root = a["data"]["Disk"][ip]['/'][0]
value_export = a["data"]["Disk"][ip]['/export'][0]
assert float(value_root.strip("%")) < 80 and float(value_export.strip("%")) < 80
運行結果如下
$ py.test 2.py
========================= test session starts =========================
platform linux2 -- Python 2.7.4, pytest-3.0.6, py-1.4.31, pluggy-0.4.0
rootdir: /home/zhukun/0224, inifile:
collected 6 items
2.py ......
====================== 6 passed in 0.05 seconds ======================
表示6個用例全部通過。
-
@pytest.mark.parametrize('ip', iplist)
至此,可能有些人會和我一樣,對于其中的
@pytest.mark.parametrize('ip', iplist)
很困惑,雖然我知道這是一個裝飾器,但是并不了解它有什么作用,其實很簡單,他就是把iplist這個參數分解,并依次賦值給ip這個參數,類似于for ip in iplist:
,所以iplist中有幾個參數,該方法就會運行幾次
6. pytest高級進階——Fixture
-
何為fixture
fixture是pytest的一個閃光點,fixture是pytest特有的功能,它用pytest.fixture標識,定義在函數前面。在你編寫測試函數的時候,你可以將此函數名稱做為傳入參數,pytest將會以依賴注入方式,將該函數的返回值作為測試函數的傳入參數。
fixture有明確的名字,在其他函數,模塊,類或整個工程調用它時會被激活。
fixture是基于模塊來執行的,每個fixture的名字就可以觸發一個fixture的函數,它自身也可以調用其他的fixture。fixture主要的目的是為了提供一種可靠和可重復性的手段去運行那些最基本的測試內容。比如在測試網站的功能時,每個測試用例都要登錄和退出,利用fixture就可以只做一次,否則每個測試用例都要做這兩步也是冗余。
-
Fixture入門實例
把一個函數定義為Fixture很簡單,只能在函數聲明之前加上“@pytest.fixture”。其他函數要來調用這個Fixture,只用把它當做一個輸入的參數即可。
test_fixture_basic.pyimport pytest @pytest.fixture() def before(): print '\nbefore each test' def test_1(before): print 'test_1()' def test_2(before): print 'test_2()' assert 0
下面是運行結果,test_1和test_2運行之前都調用了before,也就是before執行了兩次。默認情況下,fixture是每個測試用例如果調用了該fixture就會執行一次的。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_basic.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items test_fixture_basic.py::test_1 before each test test_1() PASSED test_fixture_basic.py::test_2 before each test test_2() FAILED ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ before = None def test_2(before): print 'test_2()' > assert 0 E assert 0 test_fixture_basic.py:12: AssertionError ===================== 1 failed, 1 passed in 0.23 seconds ======================
-
調用fixture有三種方式,個人推薦如下自動調用,了解更多可查看底部引用
使用autos調用fixture
fixture decorator一個optional的參數是autouse, 默認設置為False。
當默認為False,就可以選擇用上面兩種方式來試用fixture。
當設置為True時,在一個session內的所有的test都會自動調用這個fixture。
權限大,責任也大,所以用該功能時也要謹慎小心。import time import pytest @pytest.fixture(scope="module", autouse=True) def mod_header(request): print('\n-----------------') print('module : %s' % request.module.__name__) print('-----------------') @pytest.fixture(scope="function", autouse=True) def func_header(request): print('\n-----------------') print('function : %s' % request.function.__name__) print('time : %s' % time.asctime()) print('-----------------') def test_one(): print('in test_one()') def test_two(): print('in test_two()')
從下面的運行結果,可以看到mod_header在該module內運行了一次,而func_header對于每個test都運行了一次,總共兩次。該方式如果用得好,還是可以使代碼更為簡潔。
但是對于不熟悉自己組的測試框架的人來說,在pytest里面去新寫測試用例,需要去了解是否已有一些fixture是module或者class級別的需要注意。C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_auto.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items test_fixture_auto.py::test_one ----------------- module : test_fixture_auto ----------------- ----------------- function : test_one time : Sat Mar 18 06:56:54 2017 ----------------- in test_one() PASSED test_fixture_auto.py::test_two ----------------- function : test_two time : Sat Mar 18 06:56:54 2017 ----------------- in test_two() PASSED
fixture scope
- function:每個test都運行,默認是function的scope
- class:每個class的所有test只運行一次
- module:每個module的所有test只運行一次
- session:每個session只運行一次
-
fixture 返回值
在上面的例子中,fixture返回值都是默認None,我們可以選擇讓fixture返回我們需要的東西。如果你的fixture需要配置一些數據,讀個文件,或者連接一個數據庫,那么你可以讓fixture返回這些數據或資源。
如何帶參數
fixture還可以帶參數,可以把參數賦值給params,默認是None。對于param里面的每個值,fixture都會去調用執行一次,就像執行for循環一樣把params里的值遍歷一次。
test_fixture_param.pyimport pytest @pytest.fixture(params=[1, 2, 3]) def test_data(request): return request.param def test_not_2(test_data): print('test_data: %s' % test_data) assert test_data != 2
可以看到test_not_2里面把用test_data里面定義的3個參數運行里三次。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_param.py ============================= test session starts ============================= platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 3 items test_fixture_param.py::test_not_2[1] test_data: 1 PASSED test_fixture_param.py::test_not_2[2] test_data: 2 FAILED test_fixture_param.py::test_not_2[3] test_data: 3 PASSED ================================== FAILURES =================================== ________________________________ test_not_2[2] ________________________________ test_data = 2 def test_not_2(test_data): print('test_data: %s' % test_data) > assert test_data != 2 E assert 2 != 2 test_fixture_param.py:9: AssertionError ===================== 1 failed, 2 passed in 0.24 seconds ======================
-
本文從pytest基礎到深入使用fixture進行了講解,謝謝支持
參考文章
使用pytest進行批量測試