pytest模塊的使用
pytest是第三方測試框架,是基於unittest的擴展框架,比unittest更簡潔,更高效。安裝pytest模塊使用pip install pytest即可。安裝好之后,到cmd中輸入pytest --version檢查是否安裝成功。
pytest運行方法
想要用pytest運行,首先要import pytest
比如創建一個demo.py文件,內容為:
import pytest # 導入包 def test_sucess(): # 定義第一個測試用例,assert 0表示斷言失敗 print("test sucess") assert 0 def test_fail(): # 定義第二個測試用例,沒有assert,默認成功 print("test fail") if __name__ == "__main__": #定義一個列表,列表內容為測試文件名,也可以為元組,表示需要運行的文件為demo.py test_list = ['demo.py'] # 用pytest模塊的main()方法,參數為上面定義好的列表或者元組。 pytest.main(test_list) # pytest.main(['-s','demo.py']) # 也可以這樣寫,這樣寫和上面那樣寫會運行結果會有所不同,可以自己試試看。
運行出來的結果是:
demo.py F.
F:表示測試失敗
.表示測試成功
也可以在cmd下面運行,先進入需要測試的文件夾路徑下,輸入命令:
pytest -s demo.py
這里要提醒一點,pytest必須遵循以下規則:
1、測試文件名必須以“test_”開頭或者以”_test”結尾
2、測試方法必須以“test_”開頭
3、測試類命名以Test開頭
那么執行“pytest -s demo.py”這句話的時候,python就會自動去尋找test_開頭或者結尾的函數來執行。
setup()方法teardown()方法
setup()方法和teardown()方法是兩個特殊方法,setup()是在每一個編寫的測試用例執行前必然會執行的方法,teardown()方法是在每個測試用例執行后執行的方法。
比如上面的兩個測試用例,修改為:
class TestLogin: def setup(self): print("setup") def teardown(self): print("teardown") def test_sucess(self): print("test sucess") def test_fail(self): print("test fail")
上面這段代碼,定義了一個測試類,有兩個測試腳本test_sucess和test_fail,並且還增加了 setup()和teardown()方法,此時去終端執行pytest -s demo.py,運行結果為:
setup # setup方法打印出來的
test sucess #第一個測試用例打印出來的
.teardown #前面的點表示測試用例test_sucess執行成功,teardown是teardown
方法打印出來的
setup # 執行第二個測試用例的時候,會再次執行setup方法和teardown方法
test fail
.teardown
setup_class()、teardown_class()方法
如果理解了上面講解的setup和teardown方法,那么setup_class和teardown_class方法也容易理解,這兩個方法是針對測試類的,是每一個類執行前后必須執行的方法。
class TestLogin: def setup_class(self): print("setup_class") def teardown_class(self): print("teardown_class") def setup(self): print("setup") def teardown(self): print("teardown") def test_sucess(self): print("test sucess") def test_fail(self): print("test fail")
運行結果為:
setup_class # 類運行時先運行的方法
setup
test sucess
.teardown
setup
test fail
.teardown
teardown_class # 類中的方法都運行結束后,再運行的方法
一般情況下,比如setup_class()方法內,可以放連接手機的代碼,對應的teardown_class方法就可以寫退出driver的代碼。也可以寫的setup里面,根據自己的需求來定,寫到setup中可能會更保險,寫case的時候也會更簡單,只是效率低了些。
pytest配置文件
pytest運行時可以有一個配置文件,名字為pytest.ini,並且這個配置文件名字必須這么寫,一般放到測試項目目錄中。
pytest.ini的內容為:
[pytest]
# 參數
addopts = -s
# 搜索哪個文件夾
testpaths = ./scripts
# 搜索的文件名以test_開頭
python_files = test_*.py
# 搜索的類名以Test開頭
python_classes = Test*
# 搜索的方法名以test_開頭
python_functions = test_*
有了這個配置文件,就等同於告訴了python要運行哪個文件夾下的哪些文件。到cmd中運行的時候,進入到你自己的項目路徑,不需要寫參數,直接輸入pytest就可以,就會按照你的配置文件來運行該運行的用例。
pytest常用插件
生成測試報告
pytest-html
安裝方式: pip install pytest-html
安裝成功后,運行的方式,有兩種,第一種:
在終端從pytest改成pytest --html=report/report.html
意思是創建一個文件夾為report,在report文件夾中創建一個html文件,該文件里面存的就是運行的測試用例結果
如圖:
第二種運行方式:
修改pytest.ini配置文件:
addopts = -s --html=report/report.html
然后在終端依舊輸入pytest運行,出來的結果是一樣的,推薦第二種方式,更簡單。
addopts還有很多其他參數,后面還會有講到一些
__pycache__錯誤:如果是報這個錯誤,可能是在拷貝東西的時候出錯誤的,一般把文件夾中的__pycache__這個文件夾刪掉就好,這個是一個自動生成的編譯文件,在編譯的時候發現已經有這個文件了,就會報錯。
pytest控制函數執行順序
安裝插件:pip install pytest-ordering
這個插件的意義在於,如果你不想讓你的case按照從上往下的順序執行,就可以通過這個控件來控制一下。
具體用法如下:
import pytest # 注意導包 class TestLogin: @pytest.mark.run(order=0) def test_sucess(self): # 定義的第一個case,上面有一個裝飾器 print("test sucess1") @pytest.mark.run(order=2) def test_sucess2(self): print("test sucess2") def test_fail(self): print("test fail3") @pytest.mark.run(order=1) def test_sucess4(self): print("test sucess4")
這里的裝飾器:@pytest.mark.run(order=n)
用法是固定的用法,就是上面這樣寫,唯一變化的是order的值n,n可以為負數、正數、0,包括小數,執行順序和order的值有關系,可以自己試驗一下,order的值為0的時候是首先執行的用例,其次執行正數(由小到大的順序),再其次執行沒有此裝飾器做標記的,最后執行order為負數的(由小到大的順序)。
數字如果寫的一樣的話,就按照從上到下的順序了。
失敗重試
首先也安裝插件:pip install pytest-rerunfailures
用法一:命令行輸入命令:pytest --reruns n
n是重試的次數
比如:pytest --reruns 2,即失敗后再重試兩次,總共執行三次。
用法二,修改配置文件:
addopts = -s --html=report/report.html --reruns 3
修改完配置文件,直接命令行輸入pytest運行即可。
失敗重試的含義:assert 0或者assert False,都是return False的意思,失敗重試的意思就是遇到return False,就重跑這個case,直到跑完n次或者直到這個case返回了Ture就停止嘗試。
pytest高級用法
跳過測試函數
有些測試用例本在某種條件下不希望執行測試,那么就可以使用跳過測試函數:
skipif(condition,reason=None)
condition:跳過的條件,條件返回True的時候就跳過這個case。參數必須寫
reason:跳過的原因,內容可以不寫,參數必須有。
使用方法:
@pytest.mark.skipif(condition,reason=None)
比如:
@pytest.mark.skipif(True, reason='')
def test_sucess4(self):
print("test sucess4")
這樣執行的結果就會跳過這個case,控制台輸出的是一個s,s表示跳過用例沒有執行。
結果:
scripts\test_login.py
test sucess2.
test sucess1.
s
test sucess4.
標記為預期失敗的函數
標記為預期失敗的意思就是這個case失敗了才是對的,我們期望它是失敗的。
xfail(condition,reason=None)
condition:這里如果返回True,意思就是希望是預期失敗的,False表示預期成功。
reason:原因,內容可以不寫,參數必須有。
使用方法:
@pytest.mark.xfail(condition,reason=None)
示例代碼:
class TestLogin:
# 預期失敗,結果成功
@pytest.mark.xfail(True, reason='') # 這里返回True表示預期失敗
def test_sucess1(self):
print("test sucess1")
assert True # 結果返回True 這應該是一個失敗的用例
# 預期失敗,結果失敗
@pytest.mark.xfail(True, reason='')
def test_sucess2(self):
print("test sucess2")
assert False
# 預期成功,結果失敗
@pytest.mark.xfail(False, reason='') # False表示預期成果
def test_fail(self):
print('test fail')
assert False
# 預期成功,結果成功
@pytest.mark.xfail(False, reason='')
def test_sucess4(self):
print("test sucess4")
assert True
上面有四個用例,這四個用例,預期失敗結果失敗,或者預期成功結果成功,就算用例成功。其他都失敗,運行出來的結果為:
XPassed XFailed Failed Passed
上面的用例,從運行結果的顏色上面來看,紅色表示失敗,綠色成功,橙色就是預期失敗結果失敗的。預期和結果是不一樣的,就會失敗。
另外可以看到結果的前面有的有一個X,X表示這是一個預期失敗的用例。
函數數據參數化
這點非常重要。
方便測試函數獲取測試數據,
方法:parametrize(argnames,argvalues,indirect=Flase,ids=None,scope=None)
常用參數:
argnames:參數名
argvalues:參數值,類型必須為list,參數只有一個時,格式為[value1,value2,value3...];參數有多個時,格式為[(參數1值,參數2值...),(參數1值,參數2值...)...]
使用方法:
@pytest.mark.parametrize(argnames,argvalues)
例如,一個參數的時候:
@pytest.mark.parametrize('search_values', ['wlan', '藍牙', '1'])
多個參數的時候:
@pytest.mark.parametrize(('username','password'),[(‘zhangsan’,’mima1’),(‘lisi’,‘mima2’)]) # 注意這里第一個參數是元組
參數有幾個,測試用例就運行幾次。
下面這個代碼示例,是進入到設置頁面,點擊放大鏡,依次搜索’wlan’,’藍牙’,’1’:
import time import pytest from appium import webdriver class TestSearch: # 連接手機,進入到設置頁面 def setup(self): server = r'http://localhost:4723/wd/hub' # Appium Server, 端口默認為4723 desired_capabilities = { 'platformName': 'Android', 'deviceName': '127.0.0.1:62001', # 需替換成你的deviceName 'platformVersion': '5.1.1', 'appPackage': 'com.android.settings', 'appActivity': '.Settings' } self.driver = webdriver.Remote(server, desired_capabilities) # 參數化方法,注意參數名和下面用到的參數名一致 @pytest.mark.parametrize('search_values', ['wlan', '藍牙', '1']) def test_search(self, search_values): # 參數名,和上面的參數化方法中的一致 # 點擊放大鏡按鈕 self.driver.find_element_by_id('com.android.settings:id/search').click() time.sleep(2) # 輸入搜索內容 self.driver.find_element_by_id('android:id/search_src_text'). send_keys(search_values) print('search %s success' % search_values) assert 1
還有多個參數的情況,比如說測試登錄,需要輸入用戶名和密碼兩個參數,下面的代碼示例是在通訊錄添加姓名和電話號碼:
# 連接手機,進入到通訊錄頁面 def setup(self): server = r'http://localhost:4723/wd/hub' # Appium Server, 端口默認為4723 desired_capabilities = { 'platformName': 'Android', 'deviceName': '127.0.0.1:62001', # 需替換成你的deviceName 'platformVersion': '5.1.1', 'appPackage': 'com.android.contacts', 'appActivity': '.activities.PeopleActivity' } self.driver = webdriver.Remote(server, desired_capabilities) @pytest.mark.parametrize(('username', 'tel'), [('zhangsan', '123'), ('lisi', '456')]) def test_add_tel(self, username, tel): # 進入手機通訊錄頁面,點擊新增聯系人 self.driver.find_element_by_id('com.android.contacts:id/floating_action_button').click() time.sleep(2) # 我的手機添加通訊錄的時候會有一個彈框提示,所以有下面這句 self.driver.find_element_by_xpath("//*[contains(@text, '本地')]").click() time.sleep(2) # 輸入姓名 self.driver.find_element_by_xpath("//*[contains(@text, '姓名')]").send_keys(username) # 輸入電話 self.driver.find_element_by_xpath("//*[contains(@text, '電話')]").send_keys(tel) # 點擊返回 self.driver.keyevent(4)
pytest-fixture用法詳解
fixture簡介
fixture的目的是提供一個固定基線,在該基線上測試可以可靠地和重復地執行。fixture提供了區別於傳統單元測試(setup/teardown)有顯著改進:
- 有獨立的命名,並通過聲明它們從測試函數、模塊、類或整個項目中的使用來激活。
- 按模塊化的方式實現,每個fixture都可以互相調用。
- fixture的范圍從簡單的單元擴展到復雜的功能測試,允許根據配置和組件選項對fixture和測試用例進行參數化,或者跨函數function、類class、模塊module或整個測試會話sessio范圍。
fixture用途
1.做測試前后的初始化設置,如測試數據准備,鏈接數據庫,打開瀏覽器等這些操作都可以使用fixture來實現
2.測試用例的前置條件可以使用fixture實現
3.支持經典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以實現unittest不能實現的功能,比如unittest中的測試用例和測試用例之間是無法傳遞參數和數據的,但是fixture卻可以解決這個問題
fixture()方法
定義一個fixture,和定義普通函數差不多,只是多了一個裝飾器@pytest.fixture(),並且fixture命名不要以test_開頭,盡量和用例區別開。fixture是可以有返回值的,如果沒return,默認返回None。用例調用fixture的返回值,直接就是把fixture的函數名稱當成變量名稱
fixture(scope='function',params=None,autouse=False,ids=None,name=None):
scope:有四個級別參數"function"(默認),"class","module","session"
params:一個可選的參數列表,它將導致多個參數調用fixture功能和所有測試使用它。
autouse:如果True,則為所有測試用例不需要傳參也會調用這個fixture。如果為False則需要顯示的調用fixture。
ids:每個字符串id的列表,每個字符串對應於params這樣他們就是測試ID的一部分。如果沒有提供ID它們將從params自動生成
name:fixture的名稱。這默認為裝飾函數的名稱。如果fixture在定義它的統一模塊中使用,夾具的功能名稱將被請求夾具的功能arg遮蔽,解決這個問題的一種方法時將裝飾函數命令"fixture_<fixturename>"然后使用"@pytest.fixture(name='<fixturename>')"。
scope為默認值function:
它的作用范圍是每個測試用例來之前運行一次,銷毀代碼在測試用例之后運行。
示例:
import pytest @pytest.fixture() def test1(): a = 'su' print('\ntest1方法傳出a') return a @pytest.fixture(scope='function') def test2(): b = '男' print('\ntest2方法傳出b') return b class TestFixture: def test_3(self, test1): name = 'su' print('找到name') assert test1 == name def test_4(self, test2): sex = '男' print('找到sex') assert test2 == sex 執行的結果為: test1方法傳出a 找到name . test2方法傳出b 找到sex .
scope="class" fixture為class級別的時候,如果一個class里面有多個用例,都調用了此fixture,那么此fixture只在此class里所有用例開始前執行一次。 示例:
@pytest.fixture(scope='class') def test1(): b = '男' print('傳出了%s, 且只在class里所有用例開始前執行一次!!!' % b) return b class TestCase: def test_3(self, test1): name = 'su' print('找到name') assert test1 == name def test_4(self, test1): sex = '男' print('找到sex') assert test1 == sex
執行結果為:
傳出了男, 且只在class里所有用例開始前執行一次!!!
找到name
F
找到sex
scope="module"
在當前.py腳本里面所有用例開始前只執行一次
@pytest.fixture(scope='module')
scope="session"
fixture為session級別是可以跨.py模塊調用的,也就是當我們有多個.py文件的用例的時候,如果多個用例只需調用一次fixture,那就可以設置為scope="session",並且寫到conftest.py文件里。
conftest.py文件名稱是固定的,pytest會自動識別該文件。放到項目的根目錄下就可以全局調用了,如果放到某個package下,那就在該package內有效。
fixture自動使用autouse=True
當用例很多的時候,每次都傳fixture這個參數,會很麻煩。fixture里面有個參數autouse,默認是False沒開啟的,可以設置為True開啟自動使用fixture功能,這樣用例就不用每次都去傳參了
autouse設置為True,自動調用fixture功能
@pytest.fixture(scope='module', autouse=True) def test1(): print('\n開始執行module') @pytest.fixture(scope='class', autouse=True) def test2(): print('\n開始執行class') @pytest.fixture(scope='function', autouse=True) def test3(): print('\n開始執行function') # 上面定義fixture的時候設置了autouse=True,那么使用的時候就不需要傳參了,該調用的時候自動會調用 def test_a(): print('---用例a執行---') def test_d(): print('---用例d執行---') class TestCase: def test_b(self): print('---用例b執行---') def test_c(self): print('---用例c執行---')
fixture嵌套使用
這里還有需要注意的是,fixture可以嵌套使用,如果你想在一個fixture執行前先執行另一個fixture:
order = [] @pytest.fixture def f1(f3): # 這里f3這個fixture傳到f1這個fixture中 order.append("f1") @pytest.fixture() def f3(): order.append("f3") def test_1(f1): # 參數為f1,調用f1的時候會先執行f1的參數f3 print(order) assert order == ["f3", "f1"] 執行結果: ['f3', 'f1']
fixture參數化
需要使用params參數進行參數化,然后通過request.param取出
例如,如果只有一個參數:
@pytest.fixture(params=['男', '女']) #將一個list給params參數 def fix(request): # request參數必須這樣寫 return request.param # reques.param會依次將params里面的值返回去 def test_9(fix): print(fix) 運行結果: 男 . 女 .
如果有多個參數:
li = [{'name':'zhangsan', 'pwd':'mima1'},{'name':'lisi', 'pwd':'mima2'} @pytest.fixture(params=li) def fix(request): return request.param def test_9(fix): print(fix['name']) print(fix['pwd'])
fixture使用方式
第一種方式:
就是像上面用到的那樣,當成參數傳入到測試用例方法中。或者結合autouse=True的方式
第二種方式:
使用裝飾器@pytest.mark.usefixtures()修飾需要運行的用例
示例:
@pytest.fixture() def test1(): print('\n開始執行function') @pytest.mark.usefixtures('test1') def test_a(): print('---用例a執行---') @pytest.mark.usefixtures('test1') class TestCase: def test_b(self): print('---用例b執行---') def test_c(self): print('---用例c執行---') 執行結果為: 開始執行function ---用例a執行--- . 開始執行function ---用例b執行--- . 開始執行function ---用例c執行--- .
也可以疊加使用,在上面那段代碼基礎上加入tese2,然后使用的時候寫兩個裝飾器:
@pytest.fixture() def test2(): print('\n開始執行function2') @pytest.mark.usefixtures('test1') @pytest.mark.usefixtures('test2') def test_a(): print('---用例a執行---')
usefixtures與傳fixture區別
如果fixture有返回值,那么usefixture就無法獲取到返回值,這個是裝飾器usefixture與用例直接傳fixture參數的區別。
當fixture需要用到return出來的參數時,只能使用參數名稱直接當參數傳入的方式,不需要用到return出來的參數時,兩種方式都可以。
fixture做后置處理
在pytest中我們有teardown之類的后置處理,fixture也可以支持相關操作,通過yield關鍵字來實現。
比如示例:
@pytest.fixture(scope='module', autouse=True) def test1(): print('\n開始執行module') yield test1 # 因為scope='module',所以這個后置處理在該py模塊執行完后再執行,所以執行結果是在最后打印了這句話 print('\ntest1的后置處理') @pytest.fixture(scope='class', autouse=True) def test2(): print('\n開始執行class') yield test2 # scope='class',所以在每次類運行后執行 print('\ntest2的后置處理') @pytest.fixture(scope='function', autouse=True) def test3(): print('\n開始執行function') yield test3 # scope='function',所以在每個方法執行完畢后執行 print('\ntest3的后置處理') def test_a(): print('---用例a執行---') class TestCase: def test_b(self): print('---用例b執行---') def test_c(self): print('---用例c執行---') 執行結果: 開始執行module 開始執行class 開始執行function ---用例a執行--- . test3的后置處理 test2的后置處理 開始執行class 開始執行function ---用例b執行--- . test3的后置處理 開始執行function ---用例c執行--- . test3的后置處理 test2的后置處理 test1的后置處理
示例二:
打開火狐瀏覽器,百度首頁搜索pytest,檢查是否搜索成功示例:
import pytest from selenium import webdriver import time @pytest.fixture() def fixtureFunc(): '''實現瀏覽器的打開和關閉''' driver = webdriver.Firefox() # 類似return driver,將driver返回給調用者,不同的是被掉函數執行時遇到yield會停止執行,接着執行調用處的函數,調用處的函數執行完后會繼續執行yield關鍵后面的代碼 yield driver driver.quit() def test_search(fixtureFunc): '''訪問百度首頁,搜索pytest字符串是否在頁面源碼中''' driver = fixtureFunc driver.get('http://www.baidu.com') driver.find_element_by_id('kw').send_keys('pytest') driver.find_element_by_id('su').click() time.sleep(3) source = driver.page_source assert 'pytest' in source if __name__ == '__main__': pytest.main(['-s', 'test_fixture_yield.py'])