pytest用例管理
1.安裝
pip install pytest
2.編寫
2.1 編寫規則
- 用例檔案:所有檔名以test_開頭
- 用例類:測試檔案中每個Test開頭的類就是一個測試用例類
- 測試用例:測試類中每一個以test開頭的方法就是一個測試用例
2.2 函式形式編寫測試用例
def test_demo():
assert 100 == 100
使用pytest可以執行所有測試用例
2.3 以類的形式編寫用例
class Test_demo:
def test_demo1():
assert 100 == 100
def test_demo2():
assert 100 == 200
3.執行測試
3.1 執行方式
- 命令列在檔案入口執行pytest
- 程式碼中透過
pytest.main()
執行
3.2 執行引數
-
-v 顯示測試的詳細引數資訊
命令列:pytest -v
程式碼:pytest.main("-v")
-
-s 顯示測試執行的詳細資訊
-
-vs 展示全部資訊
3.3 引數傳遞
-
-k 匹配用例名詞(當前路徑下的 檔名、類名、方法名,備註可以結合or,not 等關鍵字 ,不區分大小寫)
pytest.main(['-vs', '-k', 'up', './test_2.py'])
-
-x 遇到錯誤停止
`pytest.main(['-vs','-x', './test_stop.py'])
-
--maxfail=num 失敗數量達到num個時停止
pytest.main(['-vs', '--maxfail=2', './test_stop.py'])
-
-n=num 開啟num個執行緒去執行用例 (需要安裝 pytest-xdist)
pytest.main(['-vs','./test_thread.py','-n=2'])
-
--reruns=num 設定失敗重跑次數 如果存在用例失敗,用例失敗後重跑的次數num
pytest.main(['-vs', './test_fail.py', '--reruns=2'])
3.4 指定執行的測試目錄和檔案
::用於指定測試類和測試用例
pytest testcase/test_demo::Test_demo::test_demo
4.前後置方法和fixture機制
4.1 函式用例的前後置方法
def setup_function(function):
print("前置方法")
def teardown_function(function):
print("後置方法")
def test_0():
print("執行")
4.2 測試類中的前後置方法
class Test_demo:
def test_0():
print("執行0")
def test_1():
print("執行1")
@classmethod
def setup_class(cls):
print("前置方法")
@classmethod
def teadown_class(cls):
print("後置方法")
4.3 測試用例前後置方法(先執行測試類方法,再執行函式測試用例)
class Test_demo:
def test_0():
print("執行0")
def test_1():
print("執行1")
@classmethod
def setup_class(cls):
print("執行測試類前置")
@clasmethod
def teadown_class(cls):
print("執行測試類後置")
def setup_method(function):
print("測試用例前置")
def teardown_method(function):
print("測試用例後置")
4.4 模組級別前後置方法
def setup_module(module):
print()
def teardewn_module(module):
print()
4.5 fixture機制
4.5.1 測試夾具級別
夾具定義可以透過引數scope指定夾具的級別,如果不指定夾具級別,scope 預設值為function(用例級別)
function | 每個函式或方法都會呼叫 |
---|---|
class | 每個類呼叫一次,一個類中存在多個方法 |
module | 每個.py檔案呼叫一次,一個檔案中存在多個class和多個function |
session | 多個檔案呼叫一次,可以跨.py檔案使用,每個檔案就是一個module |
4.5.2 呼叫fixture
-
函式或類裡面方法直接傳fixture的函式名稱
import pytest @pytest.fixture(scope='function') def test1(): print("前置夾具") def test_01(test1): print("測試函式") class Test_02: def test_02(self, test1): print("類方法")
-
使用裝飾器@pytest.mark.usefixtures()
import pytest @pytest.fixture(scope='function') def test1(): print("前置夾具") @pytest.mark.usefixtures('test1') def test_01(): print("測試函式") @pytest.mark.usefixtures('test1') class Test_01: def test_02(self): print("類方法")
-
疊加usefixtures
import pytest @pytest.fixture(scope='function') def test1(): print("前置夾具1") @pytest.fixture(scope='function') def test2(): print("前置夾具2") @pytest.mark.usefixtures("test1") @pytest.mark.usefixtures("test2") # 按照順序執行夾具 class Test_01: def test_01(self): print("類方法")
-
fixture自動執行 autouse=True
適用於多用例情況,直接執行
import pytest @pytest.fixture(scope='function', autore=True) def test1(): print("前置夾具1") @pytest.fixture(scope='function', autore=True) def test2(): print("前置夾具2") class TestCase: def test_01(self): print("類方法")
4.5.3 yield和終結函式
-
yield
@pytest.fixture(scope="function") def test1(): print("開始執行") yield 1 # 不需要返回值時直接 yield print("執行結束") # 起到分割作用,yield之前的程式碼為setup,後面為teardown # 可以實現引數傳遞
def test2(test1): print(test1) # 1
存在多個fixture存在yield時,先按照線性順序執行前置,執行過後,反向執行yield後的邏輯
5. conftest.py
用於管理fixture,工程根目錄下設定的conftest.py檔案起到全域性作用,不同子目錄下只在該層級以及一下目錄生效
5.1 注意事項
- pytest會預設讀取conftest.py裡面的所有fixture
- conftest.py檔名是固定的,不能移動
- conftest.py只對同一個package下的所有測試用例生效
- 不同目錄可以有自己的conftest.py,一個專案可以有多個conftest.py
- 測試用例檔案不用手動import,pytest會自動查詢
5.2 舉例
conftest.py
import pytest
@pytest.fixture(score="session")
def login():
print("登入")
name = "test"
token = ""
yield name, token # 起到分割作用,yield之前的程式碼為setup,後面為teardown
print("退出登入")
@pytest.fixture(autose=True)
def get_info(logins):
name, token = logins
print(f"列印token: {token}")
get_info(next(login()))
test1.py
def test_get_info(login):
name, token = login
print(f"使用者名稱:{}, token{}")
run.py
import pytest
if __name__ == '__main__':
pytest.main(["-s", "目錄"])
6. 引數化和資料驅動
6.1 fixture傳參
@pytest.fixture(scope="function", params=[1, 2, 4])
def need_data(request):
yield request.param
def test_001(need_data):
print(need_data)# 依次輸出1, 2, 4,經歷三次測試
6.2 mark.parametrize資料驅動
class Test_add:
@pytest.mark.parametrize("arg", [1, 2, 3])
def test_01(self, arg):
print(arg) # 三次測試
read_yaml.py
class Read_Yaml:
def __init__(self, filename):
self.filename = filename
def read_yam(self):
with open(self.filename, "r", encoding="utf-8") as f:
context = yaml.load(f, Loader=yaml.Loader)
return context
requests_paclage.py
class PacKag:
def __init__(self, api_data):
self.api_data = api_data
def packaging(self):
if self.api_data["method"] == "get":
res = requests.request(method=self.api_data["method"], url=self.api_data["url"],
params=self.api_data["params"], headers=self.api_data["headers"])
else:
data = json.dumps(self.api_data["body"])
res = requests.request(method=self.api_data["method"], url=self.api_data["url"],
data=data, headers=self.api_data["headers"])
return res.json()
testcase.py
class Test_001:
test_data = Read_Yaml(setting.yaml_data).read_yam()
# 讀取列表巢狀字典,依次取出字典,存在幾個字典進行幾次用例
@allure.title("使用者登入")
@pytest.mark.parametrize("arg", test_data)
def test_case001(self, arg):
try:
res = PacKag(arg).packaging()
assert res["errorMessage"] == arg["msg"]
logger.info("測試透過,結果為{}".format(res["errorMessage"]))
except Exception as e:
logger.error(e)
6.3 fixture+mark.parametrize
在fixture中實現前後置方法,使用mark進行資料驅動
@pytest.fixture(scope='function')
def add_data(request):
print(request.param) # 根據傳進來的引數依次執行前後置方法
yield request.param["name"]
class Test_001:
test_data = Read_Yaml(setting.authentication_yaml).read_yam()
@allure.title("使用者登入")
@pytest.mark.parametrize("add_data", test_data, indirect=True)
# indirect=True,可以指定資料傳入指定的前後置函式
def test_case001(self, add_data):
try:
res = PacKag(add_data).packaging()
assert res["errorMessage"] == arg["msg"]
logger.info("測試透過,結果為{}".format(res["errorMessage"]))
except Exception as e:
logger.error(e)
6.4 多個傳入變數,列表巢狀元組
class Test_001:
@allure.title("使用者登入")
@pytest.mark.parametrize("tt, ll", [(1, 2), (3, 4)])
# indirect=True,可以指定資料傳入指定的前後置函式
def test_case001(self, tt, ll):
try:
print(tt, ll)
except Exception as e:
logger.error(e)
# 第一次輸出1,2 第二次輸出3,4
6.5 引數組合測試
username = ["lilei", "hanmeimei"]
password = ["123456", "888888"]
@pytest.mark.parametrize("password", password)
@pytest.mark.parametrize("username", username)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
# 同時存在多個mark時,會採用列舉兩兩組合,上文存在四次測試用例
6.5 引數說明(ids)
@pytest.mark.parametrize("args, tt", [(1, 2), (3, 4)], ids=["a:{}-b:{}".format(a, b) for a, b in [(1, 2), (3, 4)]])
# 輸出testcase.py::Test_002::test_case002[a:1-b:2].......
7.外掛
7.1測試執行順序:pytest-ordering
預設執行順序從上而下,使用pytest-ordering實現自定義執行順序
pip install pytest-ordering
import pytest
class TestOrder:
def test_e(self):
print("test_e")
def test_4(self):
print("test_4")
def test_b():
print("test_a")
@pytest.mark.run(order=2)
def test_a():
print("test_a")
@pytest.mark.run(order=1)
def test_2():
print("test_2")
def test_1():
print("test_1")
if __name__ == '__main__':
pytest.main()
7.2 失敗後重新執行:pytest-rerunfailures
測試失敗後要重新執行n次,間隔n秒後執行
執行命令 pytest -v --reruns 5 --reruns-delay 1
,每次等待1秒,重新執行5次
7.3 一個用例多個斷言,前面失敗後依然可以執行
pytest-assume
pytest.assume(1 == 1)
pytest.assume(1 == 2)
# 使用這種斷言不會執行try except,詳情需要實時日誌
7.4 多程序並行和分散式執行
pytest-xdist
pytest -v test.py -n 5
,五個程序執行
8.標記用例執行
只執行標記的部分用例,指定執行某一類或某個場景的測試用例
8.1 pytest.ini
[pytest]
markers =
smoke: marks test as smoke
login
order: 下單場景
@pytest.mark.smoke
def test_01():
print("執行test_01")
def test_02():
print("執行test_02")
8.2 conftest.py
定義鉤子函式
def pytest_configure(config):
marker_list = [
"smoke: ok",
"login",
"order: 場景"
]
for marker in marker_list:
config.addinivalue_line("markers", marker)
import pytest
# 標記測試函式
@pytest.mark.smoke
def test_01():
print("執行test_01")
def test_02():
print("執行test_02")
# 標記測試類
@pytest.mark.order
class TestOrder:
def test_order(self):
print("下單")
def test_pay(self):
print("支付")
# 多個標籤
@pytest.mark.smoke
@pytest.mark.login
def test_login():
print("登入")
8.3 執行
pytest -m smoke
執行單個用例
pytest -m smoke and login
執行多個用例
pytest -m smoke or login
執行或
pytest.main(['-m', 'smoke and login'])
不能在當前模組執行,需要新建run.py,分離執行才會生效
8.4 標記跳過
import pytest
@pytest.mark.skip(reason="不需要執行test_01")
def test_01():
print("執行test_01")
@pytest.mark.skip(2>1, reason="如果2大於1則跳過不執行")
def test_02():
print("執行test_02")
if __name__ == '__main__':
pytest.main(['-s'])
8.5 標記執行失敗
部分場景:對未實現的功能或者尚未修復的錯誤進行測試,使用@pytest.mark.xfail
可以將測試用例標記為失敗
pytest.mark.xfail(condition=None, reason=None, raises=None, run=True, strict=False)
condition: 預期失敗的條件
reason: 失敗原因,會在控制檯展示
run: 預設值為True,當run為false時,pytest不會執行該用例,直接將結果標記為xfail
strict: 當 strict=False 時,如果用例執行失敗,則結果標記為xfail,表示符合預期的失敗;如果用例執行成功,結果標記為XPASS,表示不符合預期的成功;
當strict=True時,如果用例執行成功,結果將標記為failed。
import pytest
# run、strict都為預設,因為用例執行是失敗的,所以該用例執行結果會被標記為xfail
@pytest.mark.xfail(reason="bug待修復")
def test_01():
print("執行test_01")
a = "hello"
b = "hi"
assert a == b
# run、strict都為預設,因為用例執行是透過的,所以該用例執行結果會被標記為xpass
@pytest.mark.xfail(condition=lambda: True, reason="bug待修復")
def test_02():
print("執行test_02")
a = "hello"
b = "hi"
assert a != b
# run=False,該用例不執行,直接將結果標記為xfail
@pytest.mark.xfail(reason="功能尚未開發完成", run=False)
def test_03():
print("執行test_03")
a = "hello"
b = "hi"
assert a == b
# strict=True,因為用例執行是透過的,所以結果會被標記為failed
@pytest.mark.xfail(reason="功能尚未開發完成", strict=True)
def test_04():
print("執行test_04")
a = "hello"
b = "he"
assert b in a
if __name__ == '__main__':
pytest.main(['-s'])
9.實時日誌(方便監控一個用例多個斷言)
9.1 pytest.ini
[pytest]
log_cli = 1
log_cli_leavl = INFO
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
9.2 直接使用pytest- o
pytest test.py -o log_cli=true -o log_cli_level=INFO
格式: -vv -o log_cli=true -o log_cli_level=INFO --log-date-format="%Y-%m-%d %H:%M:%S" --log-format="%(filename)s:%(lineno)s %(asctime)s %(levelname)s %(message)s"
10.測試報告
10.1 pytest-html
pip install pytest-html
-
pytest.main執行
pytest.mian(["--html=測試報告.html"])
在用例檔案中執行該命令不能生成測試報告,需要單獨開一個run.py檔案執行 -
命令列執行:
pytest test.py --html=測試報告.html
pytest test.py --html=E:測試報告.html
10.2 allue
10.2.1 安裝
- 安裝pytest-allure:
pip install pytest-allure
- 安裝allure
- 配置環境變數:path: D:\allure\allure-2.15.0\bin
10.2.2 特性
@allure.epic() | 描述被測軟體系統 |
---|---|
@allure.feature() | 描述被測軟體的某個功能模組 |
@allure.story() | 描述功能模組下的功能點和測試場景 |
@allure.title() | 定義測試的標題 |
@allure.description() | 測試用例的說明描述 |
@allure.severity() | 標記測試用例級別,由高到低:blocker、critical、normal、mirnor、trivial |
@allure.step() | 標記通用函式使之成為測試步驟,測試方法中呼叫此函式的地方會像報告中輸出步驟描述 |
allure.attach() | 插入附件 allure.attach(body, name=None, attachment_type=None, extension=None) ,內容,名稱,型別 |
allure.attach.file() | 插入附件 allure.attach.file(source, name=None, attachment_type=None, extension=None) 內容,名稱,型別 |
with allure.step() | 標記測試步驟 |
allure.issue("url", name='') | 跳轉連結 |
allure.testcase() | 跳轉連結 |
@allure.story("使用者退出登入")
@allure.title("退出登入")
def test_logout(self):
'''這條測試用例僅僅只是為了舉例說明allure.attach.file的使用'''
print("退出登入,並截圖")
# 截圖路徑
testcase_path = (os.path.join(os.path.dirname(os.path.abspath(__file__))), "/screenshot/logout.jpg")
allure.attach.file(
source=testcase_path,
name="退出登入後截圖",
attachment_type=allure.attachment_type.JPG # 圖片型別
)
assert True
with allure.step()
with allure.step("請求登入介面"):
allure.attach(
body="使用者名稱-{},密碼-{}".format(username, password),
name="登入引數",
attachment_type=allure.attachment_type.TEXT
)
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
# 第二步,獲取返回引數進行斷言
with allure.step("斷言"):
assert res['code'] == 1000
# with 方式一般用於截圖或者檔案
# with allure.step("步驟詳情"):
# 具體用例
# 或者裝飾器,這種可以展示步驟傳參
@allure.step("step:新增購物車")
def add_shopping_cart(goods_id="10086"):
'''新增購物車'''
print("新增購物車")
10.2.4 生成測試報告
-
pytest.main()
pytest.main(['test.py', '-s', '-q', '--alluredir', './result']) # 執行test.py測試用例,將測試結果以json的格式儲存在當前目錄的result資料夾中 os.system('allure generate ./result -o ./report --clean') 執行json資料,並生成測試報告,儲存在當前目錄的report資料夾中, --clean刪除之前生成的測試報告
run.py
if __name__ == '__main__': pytest.main(['testcase/test_case.py', '-s', '-q', '--alluredir', './result']) os.system('allure generate ./result -o ./report --clean')
-
命令列
pytest testcase/test_case.py --alluredir ./result
allure generate ./result -o ./report --clean
然後allure open ./report
或者allure serve ./report
10.2.5 定製部分模板
-
envionment,展示此次測試執行的環境資訊
建立envionment.properties,放在--alluredor指定的資料夾
system=win python=3.7.7 version=1.0.1 host=127.0.0.1 test=孔振國
-
categories,用於定製缺陷分類,建立categories.json配置,跟上一個相同目錄
[ { "name": "Passed tests", "matchedStatuses": ["passed"] }, { "name": "Ignored tests", "matchedStatuses": ["skipped"] }, { "name": "Infrastructure problems", "matchedStatuses": ["broken", "failed"], "messageRegex": ".*bye-bye.*" }, { "name": "Outdated tests", "matchedStatuses": ["broken"], "traceRegex": ".*FileNotFoundException.*" } ]
11.CI/CD jenkins
11.1 docker 安裝jenkins並更新版本
docker pull jenkens/jenkins
docker run -it --name jenkins -p 8080:8080 -p 5000:5000 jenkins/jenkins
docker cp jenkins.war jenkins/jenkins: var/share/jenkins
docker restart jenkins/jenkins
11.2 安裝Allure外掛並進行全域性工具配置
本地安裝直接取消自動安裝並寫入allure安裝目錄
11.3 新建windows節點,並啟動
新建本地執行環境
curl.exe -sO http://47.92.76.123:8080/jnlpJars/agent.jar
java -jar agent.jar -jnlpUrl http://47.92.76.123:8080/computer/windows/jenkins-agent.jnlp -secret 724f3a87c3cf0c7278c35d365a28c9b32345c1fccc5859dc2a3ac7a3546714a4 -workDir "E:\Jenkins"
11.4 新建任務
- 限制專案的執行節點
- 使用自定義工作空間
- 構建前命令
pytest testcase.py -vs --alluredir ./allure
- 構建後新增Allure Report 指定json資料目錄,並指定生成報告目錄
12.鉤子函式
12.1 切換測試環境
-
Hook方法
conftest.py
註冊一個自定義的命令列引數
def pytest_addoption(parser): parser.addoption("--env", action="store", default="url", help="將--env新增到pytest配置中") # action="store",允許使用者儲存任何型別的值 # default 在--env不填值時,預設值 # help 說明 @pytest.fixture(scope="session") def get_env(request): """從配置物件中讀取自定義引數的值""" return request.config.getoption("--env") # 可以不用寫 @pytest.fixture(autouse=True) def set_env(get_env): """將自定義引數的值寫入全域性配置檔案""" with open(ENV_TXT_FILE, 'w', encoding='utf-8') as f: f.write(get_env)
test_demo.py
def test_001(get_env): print(get_env) # 傳入主機名,測試用例中可以透過使用不同的主機切換測試環境
-
使用pytest-base-url外掛
-
安裝pytest-base-url
pip install pytest-base-url -i https://pypi.douban.com/simple
-
將base-url引數傳入fixture函式中
@pytest.fixture def driver_setup(base_url): try: URL = base_url start_chrome(URL, options=browser_options(), headless=False) driver = get_driver() except Exception as e: log.error(e) else: yield driver # 如果發生異常,對異常操作,如果正常,執行eles裡面的邏輯 #或者直接使用 def test_001(base_url): print base_url
-
pytest命令列傳參
pytest --base-url https://www.cnblogs.com/
-