Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
如果你想快速上手pytest,只關注"Pytest必會知識點"章節就可以了(該章節已經能夠解決基礎的ui和接口自動化測試需求);
如果你想要詳細了解關于Fixture的使用方法,請關注"pytest高級用法"部分。
pytest 是 python 的第三方單元測試框架,比unittest 更簡潔和高效,支持315種以上的插件,同時兼容 nose、unittest 框架。
官網https://docs.pytest.org/en/latest/contents.html
安裝 pip install pytest
命名規則如下:
1. 測試文件應當命名為 test_<something>.py或者<somethins>_test.py
2. 測試函數、測試類方法應當命名為test_<something>
3. 測試類應當命名為Test<Something>.
1)模塊級(setup_module/teardown_module)開始于模塊始末
2)類級(setup_class/teardown_class)開始于類的始末
3)類里面的(setup/teardown)(運行在調用函數的前后)
4)功能級(setup_function/teardown_function)開始于功能函數始末(不在類中)
5)方法級(setup_method/teardown_method)開始于方法始末(在類中)
pytest的mark主要用來標記用例,通過不同的標記實現不同的運行策略
主要用途:
標記和分類用例: @pytest.mark.level1
標記用例執行順順序pytest.mark.run(order=1) (需安裝pytest-ordering)
標記用例在指定條件下跳過或直接失敗 @pytest.mark.skipif()/xfail()
標記使用指定fixture(測試準備及清理方法) @pytest.mark.usefixtures()
參數化 @pytest.mark.parametrize
標記超時時間 @pytest.mark.timeout(60) (需安裝pytest-timeout)
標記失敗重跑次數@pytest.mark.flaky(reruns=5, reruns_delay=1) (需安裝pytest-rerunfailures)
pytest 運行目錄下的所有用例
pytest test_reg.py運行指定模塊中的所有用例
pytest test_reg.py::TestClass::test_method 運行指定模塊指定類指定用例
pytest -m tag 運行包含指定標簽的所有用例
pytest -k "test_a and test_b" 運行名稱包含指定表達式的用例 (支持and or not)
其他常用參數
-q: 安靜模式, 不輸出環境信息
-v: 豐富信息模式, 輸出更詳細的用例執行信息
-s: 顯示程序中的print/logging輸出
pytest的插件也是通過pip install 命令進行安裝,常用的插件如下
pytest-rerunfailures 用例重新執行
pytest-html 生成html報告
pytest-timeout 限制超時時間
pytest-parallel 多線程使用,常用配置命令如下:
–workers (optional) *:多進程運行需要加此參數, *是進程數。默認為1。
–tests-per-worker (optional) *:多線程運行, *是每個worker運行的最大并發線程數。默認為1
定義兩個py文件test_demo1.py和test_demo2.py并放入同一包中
#test_demo1.py
import pytest
import time
class TestPrint:
def setup(self):
print("setup")
@pytest.mark.smoke
def test_test1(self):
time.sleep(2)
assert (1==1)
def test_test2(self):
time.sleep (2)
assert (1==2)
@pytest.mark.smoke
def test_test3(self):
time.sleep (2)
assert (1==1)
def teardown(self):
print ("teardown")
if __name__=='__main__':
pytest.main(['-s', 'demo1.py'])
#test_demo2.py
import pytest
import time
class TestPrint:
def setup(self):
print("setup")
def test_test11(self):
time.sleep(2)
assert (1==1)
@pytest.mark.smoke
def test_test21(self):
time.sleep (2)
assert (1==2)
def test_test31(self):
time.sleep (2)
assert (1==1)
def teardown(self):
print ("teardown")
if __name__=='__main__':
pytest.main(['-s', 'demo2.py'])
在包中運行腳本
pytest -v --tests-per-worker 2 --html=report.html
可以看到兩個py文件中的用例全部被執行,并在該目錄下生成了report.html測試報告,測試結果如下:
============================================================================
test session starts============================================================================
platform win32 -- Python 3.7.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 -- c:\python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.4', 'Platform': 'Windows-10-10.0.17763-SP0', 'Packages': {'pytest': '5.3.2', 'py': '1.8.1', 'pluggy': '0.13.1'}, 'Plugins': {'forked': '1.1.3', 'html': '2.0.1', 'metadata': '1.8.0', 'parallel': '0.0.10', 'rerunfailures': '8.0', '
xdist': '1.31.0'}, 'JAVA_HOME': 'C:\Program Files\Java\jdk1.8.0_151'}
rootdir: C:\Users\Kevin\NewLesson\pytest_demo2
plugins: forked-1.1.3, html-2.0.1, metadata-1.8.0, parallel-0.0.10, rerunfailures-8.0, xdist-1.31.0
collected 6 items
pytest-parallel: 1 worker (process), 2 tests per worker (threads)
test_demo1.py::TestPrint::test_test1 test_demo1.py::TestPrint::test_test2
test_demo1.py::TestPrint::test_test1 PASSED
test_demo1.py::TestPrint::test_test3
test_demo1.py::TestPrint::test_test2 FAILED
test_demo2.py::TestPrint::test_test11
test_demo1.py::TestPrint::test_test3 PASSED
test_demo2.py::TestPrint::test_test21
test_demo2.py::TestPrint::test_test11 PASSED
test_demo2.py::TestPrint::test_test31
test_demo2.py::TestPrint::test_test21 FAILED
test_demo2.py::TestPrint::test_test31 PASSED
===========================================================================FAILURES===========================================================================
________________________________________________________________________________ TestPrint.test_test2 ________________________________________________________________________________
self=<pytest_demo2.test_demo1.TestPrint object at 0x000002B81DFBDEC8>
def test_test2(self):
time.sleep (2)
> assert (1==2)
E assert 1==2
E -1
E +2
test_demo1.py:16: AssertionError
------------------------------------------------------------------------------------------------------------------- Captured stdout setup ------------------------------------------------------------------------------------------------------------------
setupsetup
------------------------------------------------------------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------------------------------------------------------------
teardown
______________________________________________________________________________
TestPrint.test_test21 ______________________________________________________________________________
self=<pytest_demo2.test_demo2.TestPrint object at 0x000002B81E02B108>
@pytest.mark.smoke
def test_test21(self):
time.sleep (2)
> assert (1==2)
E assert 1==2
E -1
E +2
test_demo2.py:16: AssertionError
------------------------------------------------------------------------------------------------------------------- Captured stdout setup --------------------------------------------------------------------------------------------------------------------
setup
------------------------------------------------------------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------------------------------------------------------------
teardown
==============================================================================warnings summary==============================================================================
c:\python37\lib\site-packages\_pytest\mark\structures.py:327
c:\python37\lib\site-packages\_pytest\mark\structures.py:327: PytestUnknownMarkWarning: Unknown pytest.mark.smoke - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
PytestUnknownMarkWarning,
-- Docs: https://docs.pytest.org/en/latest/warnings.html
--------------------------------------------------------------------------------------- generated html file: file://C:\Users\Kevin\NewLesson\pytest_demo2\report.html ----------------------------------------------------------------------------------------
=============================================================================2 failed, 4 passed, 1 warning in 6.42s=============================================================================
備注: 這里主要引用 https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-fixture.html
1.做測試前后的初始化設置,如測試數據準備,鏈接數據庫,打開瀏覽器等這些操作都可以使用fixture來實現
2.測試用例的前置條件可以使用fixture實現
3.支持經典的xunit fixture ,像unittest使用的setup和teardown
fixture區別于unnitest的傳統單元測試(setup/teardown)有顯著改進:
· 有獨立的命名,并通過聲明它們從測試函數、模塊、類或整個項目中的使用來激活。
· 按模塊化的方式實現,每個fixture都可以互相調用。
· fixture的范圍從簡單的單元測試到復雜的功能測試,可以對fixture配置參數,或者跨函數function,類class,模塊module或整個測試session范圍。
· fixture可以實現unittest不能實現的功能,比如unittest中的測試用例和測試用例之間是無法傳遞參數和數據的,但是fixture卻可以解決這個問題
fixture的名字直接作為測試用例的參數,上面的實例就這這種方式,再來看一個實例
# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
return 'fixtureFunc'
def test_fixture(fixtureFunc):
print('我調用了{}'.format(fixtureFunc))
class TestFixture(object):
def test_fixture_class(self, fixtureFunc):
print('在類中使用fixture "{}"'.format(fixtureFunc))
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])
每個函數或者類前使用@pytest.mark.usefixtures('fixture')裝飾器裝飾
實例
# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
print('\n fixture->fixtureFunc')
@pytest.mark.usefixtures('fixtureFunc')
def test_fixture():
print('in test_fixture')
@pytest.mark.usefixtures('fixtureFunc')
class TestFixture(object):
def test_fixture_class(self):
print('in class with text_fixture_class')
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])
指定fixture的參數autouse=True這樣每個測試用例會自動調用fixture(其實這里說的不是很準確,因為還涉及到fixture的作用范圍,那么我們這里默認是函數級別的,后面會具體說fixture的作用范圍)
實例
# test_fixture.py
import pytest
@pytest.fixture(autouse=True)
def fixtureFunc():
print('\n fixture->fixtureFunc')
def test_fixture():
print('in test_fixture')
class TestFixture(object):
def test_fixture_class(self):
print('in class with text_fixture_class')
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])
結果
fixture->fixtureFunc
.in test_fixture
fixture->fixtureFunc
.in class with text_fixture_class
[100%]
==========================2 passed in 0.04 seconds===========================
從結果可以看到每個測試用例執行前都自動執行了fixture
上面所有的實例默認都是函數級別的,所以測試函數只要調用了fixture,那么在測試函數執行前都會先指定fixture。說到作用范圍就不得不說fixture 的第二個參數scope參數。
scope參數可以是session, module,class,function; 默認為function
1.session 會話級別(通常這個級別會結合conftest.py文件使用,所以后面說到conftest.py文件的時候再說)
2.module 模塊級別: 模塊里所有的用例執行前執行一次module級別的fixture
3.class 類級別 :每個類執行前都會執行一次class級別的fixture
4.function :前面實例已經說了,這個默認是默認的模式,函數級別的,每個測試用例執行前都會執行一次function級別的fixture
下面我們通過一個實例具體看一下 fixture的作用范圍
# test_fixture.py
import pytest
@pytest.fixture(scope='module', autouse=True)
def module_fixture():
print('\n-----------------')
print('我是module fixture')
print('-----------------')
@pytest.fixture(scope='class')
def class_fixture():
print('\n-----------------')
print('我是class fixture')
print('-------------------')
@pytest.fixture(scope='function', autouse=True)
def func_fixture():
print('\n-----------------')
print('我是function fixture')
print('-------------------')
def test_1():
print('\n 我是test1')
@pytest.mark.usefixtures('class_fixture')
class TestFixture1(object):
def test_2(self):
print('\n我是class1里面的test2')
def test_3(self):
print('\n我是class1里面的test3')
@pytest.mark.usefixtures('class_fixture')
class TestFixture2(object):
def test_4(self):
print('\n我是class2里面的test4')
def test_5(self):
print('\n我是class2里面的test5')
if __name__=='__main__':
pytest.main(['-v', '--setup-show', 'test_fixture.py'])
其實前面的所有實例都只是做了測試用例執行之前的準備工作,那么用例執行之后該如何實現環境的清理工作呢?這不得不說yield關鍵字了,相比大家都或多或少的知道這個關鍵字,他的作用其實和return差不多,也能夠返回數據給調用者,唯一的不同是被掉函數執行遇到yield會停止執行,接著執行調用處的函數,調用出的函數執行完后會繼續執行yield關鍵后面的代碼(具體原理可以看下我)。看下下面的實例來了解一下如何實現teardown功能
import pytest
from selenium import webdriver
import time
@pytest.fixture()
def fixtureFunc(): '''實現瀏覽器的打開和關閉'''
driver=webdriver.Firefox()
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(['--setup-show', 'test_fixture.py'])
這個實例會先打開瀏覽器,然后執行測試用例,最后關閉瀏覽器。大家可以試試! 通過yield就實現了 用例執行后的teardown功能
1、可以跨.py文件調用,有多個.py文件調用時,可讓conftest.py只調用了一次fixture,或調用多次fixture
2、conftest.py與運行的用例要在同一個pakage下,并且有__init__.py文件
3、不需要import導入 conftest.py,pytest用例會自動識別該文件,放到項目的根目錄下就可以全局目錄調用了,如果放到某個package下,那就在改package內有效,可有多個conftest.py
4、conftest.py配置腳本名稱是固定的,不能改名稱
5、conftest.py文件不能被其他文件導入
6、所有同目錄測試文件運行前都會執行conftest.py文件
主要用途之一共享變量,代碼如下:
import pytest
# conftest.py
@pytest.fixture(scope='session')
def get_token():
token='abcd'
return token
這樣,在多個測試用例中使用fixture get_token()時就可以共享 token值了
元測試的概念
單元測試工具
不同的編程語言都有比較成熟的單元測試框架,語法規則有些差別,其核心思想都是相通的。常見的單
元測試框架有:
Java語言:Junit、TestNG
Python語言:UnitTest、Pytest
UnitTest單元測試框架
UnitTest是Python自帶的一個單元測試框架,用它來做單元測試。也經常應用到UI自動化測試和接口自
動化測試中,用來管理和維護測試用例腳本
使用UnitTest框架的好處:
1. 能夠組織多個用例去執行(可以把多條測試用例封裝成一個測試套件,實現批量執行測試用例)
2. 提供了豐富的斷言方法,方便對用例執行的結果進行判斷
3. 能夠生成HTML格式的測試報告
4. 使用Fixture功能可以減少代碼的冗余
UnitTest核心要素:
TestCase就是表示測試用例
案例
定義一個實現加法操作的函數,并對該函數進行測試
如何定義測試用例
1.導包:importunittest
2.定義測試類:新建測試類必須繼承unittest.TestCase
3.定義測試方法:測試方法名稱命名必須以test開頭
示例代碼:
ytest 框架可以用來做 系統測試 的自動化, 它的特點有
----------------------------------------------------------------------------------------------
pip install pytest
我們還需要產生測試報表,所以要安裝一個第三方插件 pytest-html ,執行如下命令安裝
pip install pytest-html
-----------------------------------------------------------------------------------------
pytest 如何知道你哪些代碼是自動化的測試用例?
官方文檔 給出了 pytest 尋找 測試項 的 具體規則
-----------------------------------------------------------------------------------------
首先,我們編寫的測試用例代碼文件, 必須以 test_ 開頭,或者以 _test 結尾
比如,我們創建一個 文件名為 test_錯誤登錄.py ,放在目錄 autotest\cases\登錄 下面。
其中 autotest 是 我們創建的 自動化項目根目錄
class Test_錯誤密碼:
def test_C001001(self):
print('\n用例C001001')
assert 1==1
def test_C001002(self):
print('\n用例C001002')
assert 2==2
def test_C001003(self):
print('\n用例C001003')
assert 3==2
如果我們把測試用例存放在類中, 類名必須以 Test 為前綴的 類 ,用例對應的方法必須以 test 為前綴的方法。
pytest 中用例的檢查點 直接用 Python 的 assert 斷言。
assert 后面的表達式結果 為 True ,就是 檢查點 通過,結果為False ,就是檢查點 不通過。
運行測試
執行測試非常簡單,打開命令行窗口,進入自動化項目根目錄(我們這里就是 autotest),執行命令程序 pytest 即可
顯示找到3個測試項,2個執行通過,1個不通過。
通過的用例 是用一個綠色小點表示, 不通過的用例用一個紅色的F表示
并且會在后面顯示具體不通過的用例 和不通過的檢查點 代碼細節。
大家可以發現,用例代碼中有些打印語句沒有顯示出內容。
因為pytest 會 截獲print打印的內容。
如果我們希望 顯示測試代碼中print的內容,因為這些打印語句在調試代碼時很有用,可以加上命令行參數 -s
如下
pytest -s
如果我們希望得到更詳細的執行信息,包括每個測試類、測試函數的名字,可以加上參數 -v,這個參數可以和 -s 合并為 -sv
如下
pytest -sv
執行 pytest 時, 如果命令行沒有指定目標目錄 或者 文件, 它會自動搜索當前目錄下所有符合條件的文件、類、函數。
所以上面,就找到了3個測試方法,對應3個用例。
我們目前 項目根目錄 中 只有一個cases 目錄用例存放測試用例, 將來還會有其他目錄,比如:
lib目錄存放庫代碼、cfg目錄存放配置數據 等等。
為了防止 pytest 到其他目錄中找測試用例項,執行測試時,我們可以在命令行加上目標目錄 cases ,就是這樣
pytest cases
--------------------------------------------------------------------------------------------------------------
前面在安裝pytest,我們也安裝了 pytest-html 插件,這個插件就是用來產生測試報告的。
要產生報告,在命令行加上 參數 --html=report.html --self-contained-html ,如下
pytest cases --html=report.html --self-contained-html
這樣就會產生名為 report.html 的測試報告文件,可以在瀏覽器中打開
但是這個工具有個bug,導致測試目錄、文件、類名 中,如果有中文,顯示為亂碼
可以這樣修復:
然后再次運行,就可以發現中文亂碼問題已經解決了。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。