欧美成人午夜精品久久久,国产?V天堂一区二区三区,欧美精品va在线观看,亚洲一区二区三区免费在线观看,av无码精品一区二区久久,欧美性爱视频不卡一区三区,欧美乱人伦视频在线观看,国产一级牲交高潮

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營(yíng)的一線實(shí)戰(zhàn)洞察。

pytest接口測(cè)試框架進(jìn)階實(shí)戰(zhàn):fixture、數(shù)據(jù)驅(qū)動(dòng)與CI集成

pytest接口測(cè)試框架進(jìn)階實(shí)戰(zhàn):fixture、數(shù)據(jù)驅(qū)動(dòng)與CI集成 開篇為什么寫接口測(cè)試 pytest 的續(xù)集前一篇講 pytest 基礎(chǔ)實(shí)戰(zhàn)時(shí)很多朋友留言說(shuō)想看更進(jìn)階的內(nèi)容——不是教你怎么寫一條用例而是怎么把 pytest 真正落到接口測(cè)試工程里讓它扛得住幾十人協(xié)作、幾萬(wàn)條用例、多環(huán)境切換、頻繁回歸的那種壓力。這篇就當(dāng)續(xù)集來(lái)聊?;A(chǔ)語(yǔ)法我不重復(fù)直接聚焦在框架設(shè)計(jì)、數(shù)據(jù)驅(qū)動(dòng)、mock 隔離、hook 擴(kuò)展、CI 集成這些硬核場(chǎng)景。你在網(wǎng)上搜接口測(cè)試 pytest 框架出來(lái)的大多還是 demo 級(jí)別的教程注冊(cè)一個(gè)用戶、查一個(gè)列表、斷言幾個(gè)字段就結(jié)束了。但真實(shí)項(xiàng)目里你要面對(duì)的是接口依賴、動(dòng)態(tài)鑒權(quán)、環(huán)境隔離、測(cè)試數(shù)據(jù)污染、失敗重試、報(bào)告歸檔這一連串問題。這篇文章里我以自己的實(shí)踐經(jīng)驗(yàn)為主線把一個(gè)可落地、可維護(hù)、可擴(kuò)展的 pytest 接口測(cè)試框架拆開講。內(nèi)容偏實(shí)戰(zhàn)代碼是我在實(shí)際項(xiàng)目中沉淀過(guò)一版又一版的方案可以作為你搭建或者重構(gòu)自己測(cè)試框架的直接參考。先說(shuō)一句這個(gè)內(nèi)容適合誰(shuí)用過(guò) pytest 但沒用明白的人用 postman/jmeter 測(cè)了不少、想往代碼化測(cè)試轉(zhuǎn)的人以及已經(jīng)在做接口測(cè)試但覺得代碼越寫越亂、想系統(tǒng)整理工程結(jié)構(gòu)的人??赐耆绻挥涀∫痪湓捨蚁M莗ytest 只是一把螺絲刀真正決定框架好不好用的是夾具設(shè)計(jì)、請(qǐng)求封裝和數(shù)據(jù)組織。1. 接口測(cè)試框架的整體設(shè)計(jì)思路1.1 為什么不用 postman/jmeter 而要轉(zhuǎn)向 pytest 代碼化很多測(cè)試團(tuán)隊(duì)起步都用 postman 或 jmeter尤其是接口量不大、老板又催得緊的時(shí)候這兩個(gè)工具確實(shí)能快速跑起來(lái)。但做久了你會(huì)碰到幾個(gè)繞不開的坎。第一是維護(hù)成本。postman 里的腳本用 JavaScript 寫jmeter 里的腳本可視化節(jié)點(diǎn)拖來(lái)拖去一旦接口字段調(diào)整或者增加數(shù)據(jù)驅(qū)動(dòng)維度你要在界面上點(diǎn)半天還容易漏。而 pytest 的用例本質(zhì)是 Python 代碼加裝飾器配合版本控制工具你可以 review、可以 diff、可以回滾這套協(xié)作流程在界面上是做不到的。第二是工程能力。pytest 生態(tài)里不僅有斷言、夾具、參數(shù)化這些原生機(jī)制還能通過(guò) conftest.py 做全局共享通過(guò) hook 動(dòng)測(cè)試收集和執(zhí)行流程通過(guò)第三方插件擴(kuò)展報(bào)告、重試、并發(fā)。你可以把測(cè)試腳本管得像業(yè)務(wù)代碼一樣嚴(yán)謹(jǐn)這是 jmeter 和 postman 很難達(dá)到的。我見過(guò)不少團(tuán)隊(duì)jmeter 腳本堆到上千個(gè)連自己都找不到哪個(gè)腳本在測(cè)什么接口。第三是數(shù)據(jù)閉環(huán)。接口測(cè)試做深了必然要連數(shù)據(jù)庫(kù)造數(shù)、清數(shù)、比對(duì)狀態(tài)這些 postman、jmeter 配合起來(lái)非常別扭但在 pytest 里你只是把 pymysql、redis 這些庫(kù)引進(jìn)來(lái)它就變成了測(cè)試代碼的一部分。我實(shí)際經(jīng)驗(yàn)是凡是希望測(cè)試長(zhǎng)期維護(hù)、和 CI 深度集成的團(tuán)隊(duì)最終都會(huì)走向代碼化。1.2 從業(yè)務(wù)場(chǎng)景倒推框架需求不夸張地說(shuō)大多數(shù)接口測(cè)試框架的通病就是為了框架而框架。一上來(lái)先把 requests 包一層、封裝統(tǒng)一的請(qǐng)求方法、搞一個(gè) basepage 類結(jié)果業(yè)務(wù)一復(fù)雜處處感覺被框架束縛。這里的關(guān)鍵在于設(shè)計(jì)的起點(diǎn)不是代碼結(jié)構(gòu)而是你的測(cè)試場(chǎng)景到底有哪些類型。以我做過(guò)的一個(gè)電商中臺(tái)項(xiàng)目為例接口測(cè)試要覆蓋四層場(chǎng)景基礎(chǔ)接口無(wú)需登錄或只帶公共參數(shù)例如獲取驗(yàn)證碼、查詢商品詳情、拉取配置信息。業(yè)務(wù)接口依賴登錄態(tài)接口之間有數(shù)據(jù)流轉(zhuǎn)例如先創(chuàng)建訂單才能支付先加購(gòu)物車才能結(jié)算。這類用例是接口測(cè)試的主體。異常與邊界參數(shù)缺失、字段類型錯(cuò)誤、金額為負(fù)數(shù)、手機(jī)號(hào)格式非法這些點(diǎn)恰恰是穩(wěn)定線上系統(tǒng)的護(hù)城河。很多測(cè)試團(tuán)隊(duì)發(fā)現(xiàn)只測(cè)正向流程根本兜不住回歸問題基本都出在異常用例的覆蓋上。外部依賴被測(cè)服務(wù)依賴的第三方接口經(jīng)常不穩(wěn)定比如支付回調(diào)、短信服務(wù)、物流查詢。測(cè)試環(huán)境里如果連著真實(shí)的第三方你根本分不清到底是自己系統(tǒng)有 bug 還是對(duì)方返回慢了。對(duì)應(yīng)這些場(chǎng)景框架設(shè)計(jì)需要的能力就清楚了統(tǒng)一的請(qǐng)求入口和鑒權(quán)管理、數(shù)據(jù)驅(qū)動(dòng)支持用來(lái)覆蓋大量異常和邊界用例、接口間依賴傳遞后一個(gè)用例要取前一個(gè)用例的結(jié)果、外部依賴 mock、以及一套便于回歸篩選的標(biāo)記體系。你把這些想清楚了再去找 pytest 的能力點(diǎn)你會(huì)發(fā)現(xiàn)路線非常自然。1.3 工程目錄到底該怎么拆很多網(wǎng)上教程推薦按接口模塊拆目錄比如用戶模塊訂單模塊支付模塊。這種做法在用例少的時(shí)候看起來(lái)清晰但等到用例上了規(guī)模你會(huì)發(fā)現(xiàn)公共代碼和業(yè)務(wù)用例混在一起改一處公共邏輯可能要碰十幾個(gè)文件。我實(shí)踐下來(lái)比較順手的目錄結(jié)構(gòu)是這樣的api_test_project/ ├── pytest.ini ├── pyproject.toml ├── requirements.txt ├── config/ │ ├── __init__.py │ ├── base_config.py │ └── env_config.yaml ├── common/ │ ├── __init__.py │ ├── http_client.py │ ├── assert_utils.py │ ├── db_client.py │ ├── auth_provider.py │ └── data_cleaner.py ├── testcases/ │ ├── __init__.py │ ├── conftest.py │ ├── test_user.py │ ├── test_order.py │ └── test_payment.py ├── data/ │ ├── user_cases.yaml │ └── order_cases.yaml ├── mock_server/ │ ├── __init__.py │ └── third_party_mock.py ├── reports/ │ ├── allure_results/ │ └── logs/ └── run.py要點(diǎn)在于testcases目錄里只放測(cè)試用例文件common目錄放能夠獨(dú)立復(fù)用的底層封裝config管環(huán)境配置。這樣公共層不依賴業(yè)務(wù)切換新項(xiàng)目時(shí)你甚至可以把 common、config 整套拷過(guò)去只重寫 testcases 和 data。另外一個(gè)容易忽視的目錄就是mock_server。真實(shí)的接口測(cè)試執(zhí)行時(shí)第三方服務(wù)經(jīng)常會(huì)掉鏈子我后面會(huì)在 mock 章節(jié)里詳細(xì)說(shuō)明但先說(shuō)一句把 mock 腳本和測(cè)試用例放在同一個(gè)工程里管理比單獨(dú)建一個(gè) mock 服務(wù)工程要省心得多因?yàn)樗梢院陀美黄鹱甙姹?、一起發(fā) CI。2. fixture 是 pytest 接口測(cè)試的靈魂2.1 一個(gè)真實(shí)的登錄態(tài) fixture寫 pytest 基礎(chǔ)教程的人都會(huì)舉登錄那個(gè)例子但大多數(shù)人講到session作用域就停了。真實(shí)項(xiàng)目里登錄遠(yuǎn)比想象復(fù)雜登錄可能依賴圖形驗(yàn)證碼、滑塊可能需要手機(jī)驗(yàn)證碼token 還可能有過(guò)期自動(dòng)刷新機(jī)制。如果只做一個(gè)靜態(tài)的 login fixture 一次登錄跑一小時(shí)用例后 token 過(guò)期了后面的用例照樣失敗。我這些年踩坑后總結(jié)出一個(gè)相對(duì)通用的登錄態(tài)管理方案思路是測(cè)環(huán)境的測(cè)試賬號(hào)信息從配置文件讀取登錄通過(guò)調(diào)用單點(diǎn)登錄接口拿到 token然后使用session作用域的 fixture 把這個(gè) token 保存下來(lái)再提供一個(gè)session_scope的 requests.Session 對(duì)象供全局用例復(fù)用。為了避免 token 過(guò)期我增加邏輯如果用例收到 401自動(dòng)重新登錄再重放一次。先看基礎(chǔ)版本import pytest import requests from config.base_config import Config pytest.fixture(scopesession) def auth_token(): 獲取并緩存登錄 token整個(gè)測(cè)試會(huì)話只登錄一次 username Config.get_env(ACCOUNT_USERNAME) password Config.get_env(ACCOUNT_PASSWORD) resp requests.post( Config.get_env(SSO_LOGIN_URL), json{username: username, password: password} ) assert resp.status_code 200, f登錄失敗: {resp.text} token resp.json()[data][token] yield token print(f會(huì)話結(jié)束可在此處做登錄態(tài)清理如注銷 token) pytest.fixture(scopesession) def http_session(auth_token): 返回?cái)y帶統(tǒng)一請(qǐng)求頭和 cookie 的 requests.Session session requests.Session() session.headers.update({ Authorization: fBearer {auth_token}, Content-Type: application/json }) return session這個(gè)寫法有幾個(gè)關(guān)鍵點(diǎn)。scopesession保證了整個(gè)測(cè)試過(guò)程只登錄一次能大幅降低接口壓力也避免每次用例都觸發(fā)登錄導(dǎo)致測(cè)試時(shí)間不可控。http_session又依賴了auth_token如果后續(xù)重構(gòu)登錄邏輯只需要改一個(gè)地方。但我再補(bǔ)充一下工作里的細(xì)節(jié)你要為不同環(huán)境切換準(zhǔn)備多個(gè)賬號(hào)比如測(cè)試環(huán)境有管理員賬號(hào)、普通用戶賬號(hào)、黑名單用戶賬號(hào)。所以更好的做法是讓 fixture 參數(shù)化而不是寫死一個(gè)賬號(hào)。下面就是參數(shù)化登錄態(tài)的進(jìn)階寫法或者說(shuō)叫fixture 工廠。2.2 fixture 做成工廠比滿世界寫 fixture 更優(yōu)雅fixture 本身可以返回一個(gè)函數(shù)讓調(diào)用方動(dòng)態(tài)決定數(shù)據(jù)這就是fixture 工廠模式。它的好處是避免為每個(gè)不同參數(shù)的場(chǎng)景寫一堆幾乎重復(fù)的 fixture。pytest.fixture(scopesession) def get_token(): fixture 工廠按角色返回對(duì)應(yīng)的登錄 token tokens {} def _inner(role: str) - str: if role not in tokens: user_config Config.get_user(role) resp requests.post( Config.get_env(SSO_LOGIN_URL), json{username: user_config[username], password: user_config[password]} ) assert resp.status_code 200, f{role} 登錄失敗 tokens[role] resp.json()[data][token] return tokens[role] return _inner調(diào)用的時(shí)候用例完全可以寫成 管理員登錄后創(chuàng)建商品普通用戶登錄后下單每個(gè)用例僅聲明它需要的角色。def test_admin_create_goods(get_token, http_session): token get_token(admin) headers {Authorization: fBearer {token}} resp http_session.post(/api/v1/goods, headersheaders, json{...}) assert resp.status_code 200這種設(shè)計(jì)在 pytest 里最常見的痛點(diǎn)是fixture 重名污染。如果兩個(gè) conftest.py 都定義了login內(nèi)層自動(dòng)覆蓋外層測(cè)試結(jié)果就會(huì)變得莫名其妙。所以我的習(xí)慣是給 fixture 取名盡量帶上場(chǎng)景詞比如admin_token、user_session而不是籠統(tǒng)的login或session。2.3 fixture 的動(dòng)態(tài)調(diào)用場(chǎng)景大多數(shù)教程會(huì)告訴你 fixture 通過(guò)函數(shù)參數(shù)注入這是 pytest 最基礎(chǔ)的用法。但實(shí)際處理接口依賴時(shí)你往往希望在當(dāng)前用例里拿到某個(gè)參數(shù)然后立刻去創(chuàng)建另一條數(shù)據(jù)。這個(gè)時(shí)候你可以直接把 fixture 工廠函數(shù)作為參數(shù)傳進(jìn)來(lái)也可以借助request.getfixturevalue實(shí)現(xiàn)動(dòng)態(tài)獲取。工作中我遇到過(guò)這么個(gè)問題某個(gè)清理用例需要先創(chuàng)建訂單再刪除訂單最后校驗(yàn)列表為空。如果我在用例里寫死一個(gè)create_orderfixturepytest 會(huì)在用例執(zhí)行前把訂單已經(jīng)建好但創(chuàng)建訂單的時(shí)間點(diǎn)可能不是你想要的。我需要的動(dòng)作非常明確——先刪除、后斷言必須要保證創(chuàng)建動(dòng)作發(fā)生在刪除動(dòng)作之前但不影響核心用例邏輯。這種情況就適合在用例函數(shù)內(nèi)動(dòng)態(tài)調(diào)用 fixturedef test_delete_order(request, http_session): # 動(dòng)態(tài)獲取創(chuàng)建訂單的 fixture而不是在函數(shù)參數(shù)里聲明 order_fixture request.getfixturevalue(create_order) order_id order_fixture() # 這里 order_id 已經(jīng)在上面的調(diào)用里創(chuàng)建了 del_resp http_session.delete(f/api/v1/orders/{order_id}) assert del_resp.status_code 200 list_resp http_session.get(/api/v1/orders) assert order_id not in [item[id] for item in list_resp.json()[data]]這個(gè)動(dòng)態(tài)調(diào)用 fixture的能力是 pytest 相對(duì)其他框架特別務(wù)實(shí)的一點(diǎn)。統(tǒng)一入口在 fixture 中收口業(yè)務(wù)組合在用例函數(shù)里自由編排既不會(huì)破壞結(jié)構(gòu)也能處理復(fù)雜依賴。3. 接口請(qǐng)求的封裝與數(shù)據(jù)驅(qū)動(dòng)3.1 requests.Session 與統(tǒng)一請(qǐng)求入口關(guān)于 requests 封裝老生常談但仍有必要畫個(gè)重點(diǎn)。最直接的誤區(qū)是不加封裝每個(gè)用例直接requests.get、requests.post。你想想如果有一天接口統(tǒng)一要求新增一個(gè)跟蹤 ID 的請(qǐng)求頭或者日志要從 stdout 改為打到文件你會(huì)愿意翻遍幾十個(gè)文件改幾十處代碼嗎所以實(shí)際項(xiàng)目中我?guī)缀跻欢ㄓ胷equests.Session作為底層請(qǐng)求對(duì)象。很多人對(duì) Session 的認(rèn)知停留在它自動(dòng)管理 cookie但它的最大價(jià)值在于連接池復(fù)用、默認(rèn)請(qǐng)求頭、默認(rèn)超時(shí)和統(tǒng)一的后置處理邏輯?;诖怂茏屇愕目蚣茏龅揭淮味x處處生效。我這里說(shuō)一個(gè)非常容易踩的坑。requests.Session中headers如果只在一開始設(shè)置之后在某個(gè)用例里直接session.headers.update(...)會(huì)影響所有后續(xù)請(qǐng)求這正是我們需要利用的全局性。但反過(guò)來(lái)如果某個(gè)用例在單個(gè)請(qǐng)求級(jí)修改了 headers這個(gè)修改僅作用于當(dāng)前請(qǐng)求其他用例不受影響。明白了這一點(diǎn)你才能在封裝請(qǐng)求入口時(shí)放心地對(duì)外提供 headers 參數(shù)。下面是兩個(gè)實(shí)際項(xiàng)目通用請(qǐng)求封裝片段import time import logging import requests logger logging.getLogger(__name__) class APIClient: def __init__(self, base_url: str, token_provider): self.session requests.Session() self.base_url base_url.rstrip(/) self.token_provider token_provider def _send(self, method: str, path: str, **kwargs): url f{self.base_url}/{path.lstrip(/)} kwargs.setdefault(timeout, 10) # 每次請(qǐng)求都重新注入最新的 token token self.token_provider() self.session.headers[Authorization] fBearer {token} start time.time() try: response self.session.request(method, url, **kwargs) except requests.exceptions.Timeout: logger.error(請(qǐng)求超時(shí): %s %s, method, url) raise cost_ms (time.time() - start) * 1000 logger.info([接口請(qǐng)求] %s %s 耗時(shí) %.1fms 狀態(tài)碼 %s, method, url, cost_ms, response.status_code) return response這個(gè)APIClient把 base_url、token 和超時(shí)統(tǒng)一管理起來(lái)。我在很多團(tuán)隊(duì)做代碼 review 時(shí)發(fā)現(xiàn)不寫這個(gè)統(tǒng)一入口的項(xiàng)目最終會(huì)在統(tǒng)計(jì)耗時(shí)和定位是哪臺(tái)測(cè)試環(huán)境出了問題上付出極高的定位成本。3.2 用 yaml 管理用例數(shù)據(jù)讓業(yè)務(wù)同學(xué)也看得懂?dāng)?shù)據(jù)驅(qū)動(dòng)是接口測(cè)試減少重復(fù)代碼的核心手段。pytest 里最基礎(chǔ)的參數(shù)化是pytest.mark.parametrize但當(dāng)你面對(duì)幾十上百條業(yè)務(wù)用例時(shí)把數(shù)據(jù)堆在測(cè)試函數(shù)上并不好看。我更推薦的做法把大量接口參數(shù)放在 yaml 或 json 文件里測(cè)試函數(shù)只保留業(yè)務(wù)步驟。以用戶注冊(cè)接口為例我在 data 目錄下的user_register_cases.yaml里這樣寫test_register_success: - case_id: register_001 name: 正常手機(jī)號(hào)注冊(cè) method: post path: /api/v1/register data: phone: 13800138000 password: Abc12345 nickname: 測(cè)試新用戶 expected: code: 0 message: success test_register_invalid_phone: - case_id: register_002 name: 非法手機(jī)號(hào)注冊(cè) method: post path: /api/v1/register data: phone: 12345 password: Abc12345 nickname: 測(cè)試異常用戶 expected: code: 10001 message: 手機(jī)號(hào)格式錯(cuò)誤測(cè)試文件里讀這個(gè)數(shù)據(jù)文件并參數(shù)化import yaml import pytest from common.http_client import api_request def load_cases(file_name, key): with open(fdata/{file_name}, encodingutf-8) as f: content yaml.safe_load(f) return content.get(key, []) class TestUserRegister: pytest.mark.parametrize(case, load_cases(user_register_cases.yaml, test_register_success)) def test_register_success(self, case, http_session): resp api_request(http_session, case) assert resp[code] case[expected][code] assert case[expected][message] in resp[message]這里的好處非常直白新增測(cè)試數(shù)據(jù)不需要改代碼測(cè)試設(shè)計(jì)人員只要看懂 yaml 的字段約定就能寫用例。接口改動(dòng)時(shí)維護(hù)成本也從改代碼降為改數(shù)據(jù)本質(zhì)上降低了測(cè)試團(tuán)隊(duì)的準(zhǔn)入門檻。需要注意的是 yaml 文件里別私自加注釋以外的運(yùn)算語(yǔ)法safe_load即可不要用full_load。3.3 從接口依賴到數(shù)據(jù)池參數(shù)傳遞的三種方式接口測(cè)試?yán)镒罱?jīng)典的問題就是依賴接口如何傳參。比如支付接口要拿到訂單號(hào)而訂單號(hào)來(lái)自創(chuàng)建訂單接口的返回值。很多新人直接用全局變量傳遞跑單條用例沒問題一跑并發(fā)或者多模塊組合用例就亂了。我實(shí)踐下來(lái)有三種方式可以解決各有適用場(chǎng)景。第一種是 fixture 返回。把創(chuàng)建訂單的公共步驟抽成 fixture并把 order_id 通過(guò)返回值提供給調(diào)用方pytest.fixture def create_order(http_session, get_token): def _create_order(goods_id, quantity1): payload { goods_id: goods_id, quantity: quantity } resp http_session.post(/api/v1/orders, jsonpayload) assert resp.status_code 200 return resp.json()[data][order_id] return _create_order第二種是 pytest-dependency 插件控制用例依賴和順序。我先給依賴的用例打一個(gè)標(biāo)號(hào)后面用例聲明依賴它pytest.mark.dependency(namecreate_order) def test_create_order(http_session, create_order): order_id create_order(1001) assert order_id return order_id pytest.mark.dependency(depends[create_order]) def test_pay_order(http_session, order_context): order_id order_context ...第三種是自定義 fixture 維護(hù)數(shù)據(jù)池尤其適合多模塊組合場(chǎng)景。我在接口測(cè)試?yán)锿ǔS靡粋€(gè)data_context字典存儲(chǔ)當(dāng)前測(cè)試會(huì)話的關(guān)鍵數(shù)據(jù)讓用例間可以輕量地共享關(guān)鍵上下文比如訂單號(hào)、用戶 ID、商品 ID。然后通過(guò)_inner方式讓 fixture 既支持讀取也支持寫入。這里需要提一下pytest 默認(rèn)的執(zhí)行順序是文件內(nèi)從上到下如果你跑的是 pytest-randomly 插件隨機(jī)執(zhí)行用例間一旦有隱式依賴就會(huì)掛。所以無(wú)論采用上面哪種方式我都建議顯式標(biāo)記依賴而不是依賴文件執(zhí)行順序。真正用隨機(jī)插件時(shí)更穩(wěn)的方案是讓依賴走 fixture 工廠而不是上面用例產(chǎn)生某個(gè)變量供下面用例使用這種隱性約定。3.4 失敗重試與超時(shí)控制接口測(cè)試經(jīng)常存在不穩(wěn)定因素例如網(wǎng)絡(luò)抖動(dòng)、測(cè)試環(huán)境剛部署完還在重啟、依賴服務(wù)冷啟動(dòng)慢。如果因?yàn)橐淮纬瑫r(shí)就標(biāo)記失敗誤報(bào)率會(huì)非常高。為了避免這種無(wú)效煩擾引入重試機(jī)制是必要的但要設(shè)置合理的重試條件和次數(shù)。pytest-rerunfailures 是最常用的插件。命令行里可以這么用pytest --reruns 2 --reruns-delay 2意思是失敗后自動(dòng)重跑 2 次每次間隔 2 秒。不過(guò)在接口測(cè)試?yán)镂腋ㄗh對(duì)不同的測(cè)試標(biāo)記使用不同策略冒煙測(cè)試必須一次通過(guò)不允許重試否則掩蓋了真實(shí)問題而全量回歸測(cè)試可以開重試減少環(huán)境因素導(dǎo)致的誤報(bào)。更好的一種做法是在代碼執(zhí)行層面對(duì)個(gè)別接口加更精細(xì)的重試比如支付回調(diào)場(chǎng)景第三方回調(diào)可能存在幾秒延遲。requests 的適配器層面可以配置 urllib3 的 Retryfrom urllib3.util.retry import Retry from requests.adapters import HTTPAdapter def add_retry_to_session(session, retries3, backoff_factor1): retry_strategy Retry( totalretries, status_forcelist[500, 502, 503, 504], allowed_methods[GET, POST], backoff_factorbackoff_factor ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(https://, adapter) session.mount(http://, adapter) return session這段代碼背后的邏輯是生產(chǎn)環(huán)境的網(wǎng)絡(luò)故障往往具有瞬時(shí)性退避策略則是避免故障恢復(fù)后所有用例同時(shí)沖刺造成遞歸雪崩。你在設(shè)計(jì)用例時(shí)要區(qū)分業(yè)務(wù)斷言失敗和網(wǎng)絡(luò)層請(qǐng)求失敗前者不能用重試掩蓋后者重試一下反而能拿到更準(zhǔn)確結(jié)果。4. mock 與外部依賴隔離4.1 用 responses 庫(kù)快速 mock 第三方接口很多服務(wù)端接口都調(diào)用了第三方接口比如短信、支付、物流、地圖。測(cè)試環(huán)境為了穩(wěn)定運(yùn)行必須把第三方 mock 掉。在 pytest 生態(tài)里responses庫(kù)是 mock HTTP 請(qǐng)求的一個(gè)好選擇它可以在測(cè)試代碼里把 requests 的調(diào)用攔截下來(lái)模擬返回任何你想看到的結(jié)果。一個(gè)典型場(chǎng)景是用戶下單后觸發(fā)短信通知接口返回成功但我們不想真的發(fā)短信。代碼可以這么寫import responses import requests responses.activate def test_order_sends_sms(): responses.add( responses.POST, https://sms.thirdparty.com/api/send, json{code: 0, message: ok}, status200 ) resp requests.post( https://sms.thirdparty.com/api/send, json{mobile: 13800138000, content: 你的訂單已發(fā)貨} ) assert resp.status_code 200 assert resp.json() {code: 0, message: ok}responses 庫(kù)比較適合在單元層和接口層模擬外部依賴。但有個(gè)使用上的注意事項(xiàng)如果你在測(cè)試代碼里禁用了某些代理環(huán)境變量或被測(cè)代碼走了連接池復(fù)用responses 的攔截仍然生效因?yàn)樗鼣r截的是 requests 的 send 層。這是一個(gè)很可靠的設(shè)計(jì)。我實(shí)際用它時(shí)候最大的體會(huì)是mock 不要只 mock 成功路徑失敗路徑、超時(shí)路徑、斷連路徑都要覆蓋。比如支付回調(diào)返回簽名錯(cuò)誤、短信接口返回余額不足、物流接口超時(shí)——這些恰恰是真實(shí)系統(tǒng)最容易出 bug 的地方。4.2 使用 monkeypatch 替換被測(cè)對(duì)象內(nèi)部的客戶端responses 攔截的是整個(gè) requests 調(diào)用適合外部 URL 明確不可控的場(chǎng)景。但如果被測(cè)代碼內(nèi)部有一個(gè) API client 對(duì)象你想讓它直接返回構(gòu)造好的數(shù)據(jù)monkeypatch 是更直接的方案。假設(shè)被測(cè)服務(wù)的支付模塊里有一個(gè)內(nèi)部類ThirdPartyPayClient.pay()我們想模擬它的成功與異常def test_pay_success(monkeypatch): def fake_pay(self, amount): return {transaction_id: mock_txn_001, status: success} monkeypatch.setattr(payment.service.ThirdPartyPayClient.pay, fake_pay) resp requests.post(http://your-service/api/v1/pay, json{amount: 100}) assert resp.status_code 200 assert resp.json()[transaction_id] mock_txn_001monkeypatch 在 pytest 里屬于 autouse 級(jí)別的能力它會(huì)在測(cè)試結(jié)束后自動(dòng)復(fù)原不會(huì)對(duì)后續(xù)測(cè)試造成污染。接口測(cè)試做 mock 時(shí)用 monkeypatch 替換被測(cè)服務(wù)內(nèi)部代碼里的第三方 client比去攔截一個(gè)具體的 URL 更接近真實(shí)環(huán)境因?yàn)樗@過(guò)了網(wǎng)絡(luò)層直接模擬了業(yè)務(wù)代碼依賴的邊界接口。4.3 mock 數(shù)據(jù)和真實(shí)環(huán)境怎么切換在實(shí)際的接口測(cè)試工程里我不會(huì)硬編碼 mock而是通過(guò)配置開關(guān)決定當(dāng)前環(huán)境要不要走 mock。比如配置文件里加一個(gè)字段mock: enable: true sms: true pay: false一旦enable為 false測(cè)試就直連真實(shí)環(huán)境或真實(shí)第三方沙箱。這么做的好處是開發(fā)環(huán)境可以 mock 掉還沒開發(fā)完的接口測(cè)試環(huán)境則盡量走真實(shí)服務(wù)最大程度還原線上鏈路。然后在 conftest 里根據(jù)配置自動(dòng)啟動(dòng)對(duì)應(yīng) mock 服務(wù)或者選擇在不同的 fixture 中生效。有一個(gè)值得注意的隱蔽問題mock 代碼如果長(zhǎng)時(shí)間不更新第三方接口的真實(shí)字段變了mock 還按照老字段返回會(huì)導(dǎo)致測(cè)試假綠。我的習(xí)慣是每周至少跑一次關(guān)閉 mock 的冒煙用例校準(zhǔn) mock 數(shù)據(jù)和真實(shí)返回的一致性。5. 斷言體系與 hook 擴(kuò)展5.1 別再只用assert resp.status_code 200很多接口測(cè)試用例掛在resp.status_code上斷言 200然后就沒有然后了。HTTP 200 只能說(shuō)明服務(wù)端收到了請(qǐng)求并給出了響應(yīng)業(yè)務(wù)是否成功還要看業(yè)務(wù)碼、業(yè)務(wù)數(shù)據(jù)、耗時(shí)、響應(yīng)頭。如果你斷言不足夠多測(cè)試用例的意義就會(huì)大打折扣。分層斷言是接口測(cè)試最直接有效的思路。第一層狀態(tài)碼斷言。判斷 HTTP 層是否正常常見是 200、201、204。如果出現(xiàn) 500大概率是被測(cè)服務(wù)有 bug或者測(cè)試數(shù)據(jù)有問題。但這里有個(gè)細(xì)節(jié)某些系統(tǒng)設(shè)計(jì)為了統(tǒng)一響應(yīng)即便是業(yè)務(wù)參數(shù)錯(cuò)誤也返回 200這種情況第一層沒法判斷問題。第二層業(yè)務(wù)碼。入?yún)㈠e(cuò)誤返回 10001未登錄返回 10002無(wú)權(quán)限返回 10003。斷言業(yè)務(wù)碼能確認(rèn)接口真的走進(jìn)了業(yè)務(wù)邏輯不是什么都返回 200 的假通。第三層數(shù)據(jù)字段。最細(xì)粒度的是校驗(yàn)關(guān)鍵字段的值比如用戶注冊(cè)后返回的 user_id 必須是大于 0 的整數(shù)token 長(zhǎng)度必須滿足某種規(guī)則。這一層用 jsonpath 或者抽取關(guān)鍵字段來(lái)斷言會(huì)方便很多。下面用一個(gè)斷言工具類的片段說(shuō)明class AssertionUtils: staticmethod def assert_code(response, expect_code): 校驗(yàn) HTTP 狀態(tài)碼和業(yè)務(wù)碼 assert response.status_code 200, fHTTP狀態(tài)碼異常: {response.status_code} body response.json() assert body.get(code) expect_code, ( f業(yè)務(wù)碼不匹配, 期望 {expect_code}, 實(shí)際 {body.get(code)}, 返回內(nèi)容: {body} ) staticmethod def assert_jsonpath(response, json_path, expected_value): 用 jsonpath 提取字段斷言層級(jí)太深時(shí)非常有用 import jsonpath body response.json() result jsonpath.jsonpath(body, json_path) assert result, fjsonpath 未命中任何值: {json_path}, 返回內(nèi)容: {body} assert result[0] expected_value, ( fjsonpath 斷言失敗, 路徑 {json_path}, 期望 {expected_value}, 實(shí)際 {result[0]} )細(xì)心的人會(huì)發(fā)現(xiàn)jsonpath庫(kù)有中文亂碼或者結(jié)果為 False 的坑我在后面常見問題部分會(huì)專門提。5.2 hook 實(shí)現(xiàn)用例順序控制與動(dòng)態(tài)添加 markpytest 的 hook 機(jī)制是可以讓你在用例收集、執(zhí)行、報(bào)告各個(gè)階段插入自定義邏輯的鉤子。其中一個(gè)高頻場(chǎng)景是控制用例執(zhí)行順序。雖然我上面說(shuō)過(guò)隨機(jī)執(zhí)行時(shí)不該依賴隱式順序但大多數(shù)團(tuán)隊(duì)執(zhí)行的仍然是文件內(nèi)從上到下的順序。如果某些用例必須保證先后關(guān)系例如先執(zhí)行登錄清理數(shù)據(jù)、再執(zhí)行核心業(yè)務(wù)可以采用pytest_collection_modifyitems這個(gè) hook在收集階段對(duì)用例排序。另一個(gè)場(chǎng)景是把單個(gè)大型用例拆成幾個(gè)測(cè)試函數(shù)但希望它們?cè)趫?bào)告里顯示為一個(gè)大步驟序列。我參考過(guò)的做法是動(dòng)態(tài)給測(cè)試函數(shù)添加 markerdef pytest_collection_modifyitems(config, items): 收集測(cè)試用例后根據(jù)測(cè)試文件名或函數(shù)名動(dòng)態(tài)添加標(biāo)簽 for item in items: # 如果 node_id 包含 user 模塊且名字帶 smoke自動(dòng)標(biāo)記為冒煙 if user in item.nodeid and smoke in item.name: item.add_marker(pytest.mark.smoke)這個(gè)能力在測(cè)試工程化里相當(dāng)實(shí)用。很多接口測(cè)試框架的用例分類是依賴代碼里手工敲pytest.mark.smoke之類的裝飾器的但通過(guò) hook 可以根據(jù)目錄名、模塊名或者數(shù)據(jù)文件里的字段自動(dòng)分類減少漏標(biāo)和誤標(biāo)。還有一個(gè)我很常用的 hook 是pytest_runtest_makereport可以拿到每個(gè)用例的執(zhí)行結(jié)果在用例失敗時(shí)自動(dòng)截圖或收集服務(wù)端日志。接口測(cè)試雖然沒有 UI 可以截圖但可以自動(dòng)去采集被測(cè)服務(wù)返回的響應(yīng)體、錯(cuò)誤碼、當(dāng)前請(qǐng)求的 trace_id。對(duì)于定位線上問題非常有價(jià)值pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: # 從 request 對(duì)象里取請(qǐng)求參數(shù)和響應(yīng)內(nèi)容打印到日志 if hasattr(item, function): print(f用例失敗節(jié)點(diǎn): {item.nodeid})5.3 自定義命令行參數(shù)擴(kuò)展pytest 支持你自己注冊(cè)命令行參數(shù)然后通過(guò) fixture 或 conftest 訪問它。這個(gè)能力讓一套代碼多環(huán)境跑這種需求變得非常簡(jiǎn)單。你可以在 conftest.py 中用pytest_addoption定義參數(shù)def pytest_addoption(parser): parser.addoption( --env, actionstore, defaulttest, help指定運(yùn)行環(huán)境: dev / test / staging ) parser.addoption( --skip-teardown, actionstore_true, help跳過(guò)用例執(zhí)行完成后的清理動(dòng)作便于問題復(fù)現(xiàn)時(shí)保留現(xiàn)場(chǎng) )然后寫一個(gè)獲取配置的 fixturepytest.fixture(scopesession) def env(request): return request.config.getoption(--env) pytest.fixture(scopesession) def skip_teardown(request): return request.config.getoption(--skip-teardown)使用的時(shí)候執(zhí)行pytest --envstaging測(cè)試就會(huì)跑在 staging 環(huán)境代碼里通過(guò)這些 fixture 加載對(duì)應(yīng)環(huán)境的 base_url 和測(cè)試賬號(hào)。實(shí)踐中這個(gè)參數(shù)接上 CI 后配合環(huán)境變量更容易管理比如在流水線中動(dòng)態(tài)傳入--envtest或--envpre。6. 測(cè)試數(shù)據(jù)準(zhǔn)備與清理6.1 用 yield fixture 實(shí)現(xiàn)自動(dòng)清理接口測(cè)試中最頭疼的問題之一是數(shù)據(jù)污染。測(cè)試跑一次會(huì)在數(shù)據(jù)庫(kù)里留下記錄第二次跑的時(shí)候可能因?yàn)槲ㄒ绘I沖突直接報(bào)錯(cuò)。為了避免這種問題最樸素的做法是測(cè)試結(jié)束后把創(chuàng)建的數(shù)據(jù)刪掉。pytest 的 fixture 支持 yield天然適合做 setup/teardown。pytest.fixture def created_user(http_session): 創(chuàng)建一個(gè)用戶測(cè)試結(jié)束后自動(dòng)刪除該用戶 resp http_session.post(/api/v1/users, json{...}) assert resp.status_code 201 user_id resp.json()[data][user_id] yield user_id # 測(cè)試結(jié)束無(wú)論成功失敗都會(huì)執(zhí)行到這里 http_session.delete(f/api/v1/users/{user_id})這個(gè)模式非常實(shí)用。第一個(gè)好處是清理邏輯和創(chuàng)建邏輯放在一起代碼可讀性高第二個(gè)好處是即使測(cè)試斷言失敗yield之后的內(nèi)容照樣執(zhí)行不必跑到finally塊里去清理。我剛開始寫 pytest 時(shí)一直把清理邏輯寫在一個(gè)finally里后來(lái)發(fā)現(xiàn) yield fixture 才是 pytest 生態(tài)最貼近業(yè)務(wù)場(chǎng)景的寫法。6.2 數(shù)據(jù)庫(kù)直連清理與冪等策略有些場(chǎng)景下接口本身沒有提供刪除能力比如測(cè)試賬號(hào)發(fā)了很多條消息沒有刪除消息接口或者僅靠接口刪除太慢幾千條數(shù)據(jù)逐個(gè)調(diào)接口刪除那就需要直連數(shù)據(jù)庫(kù)做清理。使用 pymysql 或者 SQLAlchemy 可以比較方便地完成。我習(xí)慣在common/db_client.py里封裝一個(gè)清理模塊import pymysql from config.base_config import Config def delete_rows(table: str, condition: str): 直連數(shù)據(jù)庫(kù)刪除數(shù)據(jù)condition 示例: phone 13800138000 conn pymysql.connect( hostConfig.get_env(DB_HOST), portConfig.get_env(DB_PORT), userConfig.get_env(DB_USER), passwordConfig.get_env(DB_PASS), databaseConfig.get_env(DB_NAME), charsetutf8mb4 ) try: with conn.cursor() as cursor: sql fDELETE FROM {table} WHERE {condition} cursor.execute(sql) conn.commit() finally: conn.close()在測(cè)試用例里使用的時(shí)候就可以這樣保證用例開始時(shí)數(shù)據(jù)是干凈的def test_user_register_duplicate_phone(http_session, skip_teardown): # 先清理防止上一次運(yùn)行的臟數(shù)據(jù)導(dǎo)致用例不可重復(fù)執(zhí)行 delete_rows(user, phone13800138000) # 注冊(cè)第一次正常 ... # 注冊(cè)第二次應(yīng)該報(bào)重復(fù) ...關(guān)于數(shù)據(jù)庫(kù)清理有一個(gè)額外的建議清理的時(shí)候千萬(wàn)不要用DELETE FROM table這種不帶條件的操作否則會(huì)把別人正在用的數(shù)據(jù)也刪掉。最好用測(cè)試賬號(hào)前綴或者測(cè)試數(shù)據(jù)標(biāo)識(shí)作為清理?xiàng)l件。為了冪等我還會(huì)把創(chuàng)建數(shù)據(jù)時(shí)用到的主鍵或業(yè)務(wù)唯一鍵帶進(jìn)清理?xiàng)l件這樣每次跑用例開始和結(jié)束都能基于同一個(gè)身份去清理。6.3 用 marker 區(qū)分冒煙、全量、分層回歸用例跑起來(lái)很耗時(shí)接口測(cè)試套件慢慢膨脹到幾千上萬(wàn)條后你不可能每次提交代碼都全量跑。建議一開始就把 marker 設(shè)計(jì)好后續(xù)會(huì)成為活命的關(guān)鍵。pytest 的 marker 體系和-m選擇參數(shù)結(jié)合起來(lái)非常方便# 只跑冒煙用例 pytest -m smoke # 跑支付模塊的所有用例排除掉已知不穩(wěn)定但暫時(shí)不修的 pytest testcases/test_payment.py -m not flaky在項(xiàng)目里需要注冊(cè) marker防止 pytest 強(qiáng)制檢查時(shí)報(bào)警# pytest.ini [pytest] markers smoke: 冒煙用例集 p0: 核心主流程用例 p1: 重要業(yè)務(wù)用例 p2: 一般功能用例 flaky: 已知不穩(wěn)定用例用例和 marker 怎么掛鉤可以直接在測(cè)試類上打類級(jí) marker在所有方法前生效。pytest.mark.smoke class TestOrderCheckout: def test_add_cart(self, ...): pass def test_submit_order(self, ...): pass你還可以在配置文件中加一個(gè)默認(rèn) tag比如這么寫addopts -v -s --strict-markers--strict-markers會(huì)把不認(rèn)識(shí)的 marker 當(dāng)作錯(cuò)誤處理能幫你盡早發(fā)現(xiàn) typo。7. 測(cè)試報(bào)告與 CI 集成7.1 Allure 報(bào)告接入的關(guān)鍵步驟Allure 這幾年已經(jīng)成為 pytest 接口測(cè)試報(bào)告的事實(shí)標(biāo)準(zhǔn)。它不僅能展示用例的通過(guò)率和耗時(shí)還能把請(qǐng)求參數(shù)、響應(yīng)內(nèi)容、附加信息都?xì)w類展示非常適合給管理層和開發(fā)同事看。接入步驟不長(zhǎng)但需要注意版本兼容問題?;玖鞒淌? 安裝 pytest-allure 適配器 pip install allure-pytest # 執(zhí)行測(cè)試并生成 allure 結(jié)果 pytest --alluredir./reports/allure_results # 本地查看報(bào)告 allure serve ./reports/allure_results # 生成靜態(tài) html 報(bào)告適合傳到內(nèi)部文檔平臺(tái) allure generate ./reports/allure_results -o ./reports/allure_html --clean我在實(shí)際使用時(shí)遇到最多的問題是 allure 命令行工具版本和 Java 環(huán)境不匹配。如果你裝了 allure 2.13 以上版本一般需要 JDK 8 以上。Windows 下如果allure命令不是全局的記得把 allure 的 bin 目錄加入 PATH否則會(huì)一直提示找不到命令。為了讓報(bào)告內(nèi)容更豐富我建議在用例中顯式記錄關(guān)鍵上下文import allure allure.title(用戶注冊(cè)——正常手機(jī)號(hào)) allure.description(校驗(yàn)接口正常注冊(cè)時(shí)返回 code0且能查到用戶) def test_user_register_success(http_session, case): with allure.step(準(zhǔn)備注冊(cè)參數(shù)): payload {...} with allure.step(調(diào)用注冊(cè)接口): resp http_session.post(/api/v1/register, jsonpayload) with allure.step(校驗(yàn)業(yè)務(wù)結(jié)果): assert resp.json()[code] 0 allure.attach(resp.text, 響應(yīng)內(nèi)容, allure.attachment_type.TEXT)Allure 的 step 上下文會(huì)在執(zhí)行過(guò)程中形成清晰的調(diào)用樹比平鋪的日志更直觀。尤其是接口測(cè)試中一個(gè)用例有多個(gè)接口調(diào)用時(shí)嵌套 step 能真實(shí)還原業(yè)務(wù)操作鏈路。7.2 在 CI 流水線中執(zhí)行測(cè)試并歸檔報(bào)告如果你的項(xiàng)目做了代碼提交后自動(dòng)觸發(fā)的測(cè)試任務(wù)pytest 只需要在 CI 上執(zhí)行一行命令。這里有一個(gè)關(guān)鍵實(shí)踐是CI 上一旦因環(huán)境不穩(wěn)定掛了要能區(qū)分是用例真有 bug還是環(huán)境沒起來(lái)。所以流水線開始前經(jīng)常會(huì)先準(zhǔn)備一個(gè) health check 腳本連續(xù)探測(cè)被測(cè)服務(wù)端口和健康接口都通過(guò)后才開始跑 pytest。一個(gè)完整的 gitlab CI job 的簡(jiǎn)化配置可能長(zhǎng)這樣test-api: stage: test image: python:3.11-slim script: - pip install -r requirements.txt - python -c import requests; print(ready) - pytest testcases --envtest --alluredir./reports/allure_results -m not flaky -q after_script: - allure generate ./reports/allure_results -o ./reports/allure_html --clean || true artifacts: paths: - reports/allure_html/ expire_in: 7 days注意after_script里即使測(cè)試失敗也要嘗試生成報(bào)告所以加了|| true或者直接讓腳本不因?yàn)閳?bào)告生成失敗覆蓋測(cè)試結(jié)果。另外artifacts和登錄信息有關(guān)系如果是內(nèi)部 Jenkins 環(huán)境一般測(cè)試結(jié)果上傳到 Allure 服務(wù)或者某個(gè)對(duì)象存儲(chǔ)上再在頁(yè)面上內(nèi)嵌鏈接。7.3 并行執(zhí)行與資源競(jìng)爭(zhēng)用例量大了之后單線程跑幾小時(shí)肯定受不了所以 pytest 的并行執(zhí)行也成了必選項(xiàng)。pytest-xdist 是一個(gè)并行執(zhí)行插件最簡(jiǎn)單用法pytest -n 4但有兩點(diǎn)必須在實(shí)際落地前想清楚。第一不同測(cè)試文件之間不能有隱式共享狀態(tài)。假如 test_user.py 創(chuàng)建了 A 用戶test_order.py 默認(rèn)這個(gè)用戶存在那并行執(zhí)行下極易秒掛。所以公共前置必須抽象成 fixturefixture 內(nèi)部讓它自己創(chuàng)建并清理依賴的數(shù)據(jù)絕不能假設(shè)某個(gè)前置文件先跑完了。第二數(shù)據(jù)庫(kù)層面的并發(fā)沖突。如果多個(gè)并行 worker 同時(shí)插入相同手機(jī)號(hào)的用戶數(shù)據(jù)就會(huì)撞唯一鍵。這時(shí)要做的是在用例里給不同 worker 賦予不同的數(shù)據(jù)標(biāo)識(shí)或者保證每個(gè)用例的輸入數(shù)據(jù)在初始化時(shí)就是唯一且?guī)щS機(jī)性的。import time import uuid def gen_unique_phone(): return f139{str(int(time.time()))[-8:]} uuid.uuid4().hex[:4]以上就是一個(gè)初級(jí)的唯一手機(jī)號(hào)生成器。雖然不保證絕對(duì)唯一但實(shí)際沖突概率已經(jīng)非常低。如果你想跑更安全可以結(jié)合數(shù)據(jù)庫(kù)查詢生成后再查一次確認(rèn)不存在。8. 常見問題與避坑手記8.1 fixture 重名與導(dǎo)入范圍問題pytest 的 fixture 查找規(guī)則是從測(cè)試文件所在目錄開始向上查找 conftest.py可以跨層繼承。但正因?yàn)?conftest 是逐層覆蓋的一旦內(nèi)層 conftest 定義了一個(gè)與外層同名的 fixture內(nèi)層會(huì)直接覆蓋外層基本不給你提示。我見過(guò)最坑的場(chǎng)景是項(xiàng)目根目錄的 conftest.py 定義了一個(gè)loginfixture負(fù)責(zé)獲取 admin token后來(lái)業(yè)務(wù)模塊里有人在業(yè)務(wù) conftest.py 里也定義了一個(gè)login心想我這個(gè) login 是普通用戶 token。結(jié)果所有業(yè)務(wù)模塊用例全部拿到了普通用戶 token權(quán)限相關(guān)斷言批量失敗。排查了一整天才發(fā)現(xiàn)是 fixture 同名覆蓋。避免的辦法fixture 命名盡量具體不要用login、session、data這些通用詞改成admin_token、normal_user_session、order_context這種有業(yè)務(wù)含義的名字。項(xiàng)目規(guī)范里明確要求 conftest.py 中 fixture 負(fù)責(zé)范圍公共底層 fixture 放根 conftest業(yè)務(wù)頁(yè)面級(jí)別的放模塊 conftest。開啟 pytest 的嚴(yán)格模式如果發(fā)現(xiàn)有重復(fù) fixture 名盡快重命名而不是在多個(gè) conftest 中兜圈子。8.2 jsonpath 和返回結(jié)構(gòu)斷言的那些坑接口返回結(jié)構(gòu)稍微深一點(diǎn)比如data.user.address.city最簡(jiǎn)單的斷言也得寫一大串字典取值的代碼。如果返回的 key 不存在直接用resp.json()[data][user][address]會(huì)直接拋 KeyError用例報(bào)錯(cuò)不是失敗但在報(bào)告里很難看出具體斷言語(yǔ)義。用 jsonpath 會(huì)更優(yōu)雅但 jsonpath 庫(kù)有一個(gè)容易忽視的問題jsonpath.jsonpath()返回結(jié)果的類型可能需要判斷是否是列表。如果你取出來(lái)的值本身是False、0、None許多庫(kù)會(huì)把匹配成功但沒有值和匹配到False混在一起。還有中文 key 支持性某些 jsonpath 實(shí)現(xiàn)對(duì)中文字段支持不好。我的習(xí)慣是優(yōu)先盡量用英文 key 或者后端返回穩(wěn)定的字段中文 key 場(chǎng)景盡量用jmespath或直接字典逐層取值。8.3 pytest 收集階段執(zhí)行時(shí)長(zhǎng)過(guò)長(zhǎng)有時(shí)候你運(yùn)行 pytest還沒開始執(zhí)行用例就卡了很久。這通常是 conftest.py 或測(cè)試文件導(dǎo)入階段做了大量計(jì)算比如加載 Excel 用例、連接數(shù)據(jù)庫(kù)、初始化 HTTP 客戶端。pytest 在做 collection 時(shí)會(huì)把測(cè)試文件里的模塊導(dǎo)入一遍所有模塊級(jí)代碼都會(huì)執(zhí)行所以你在模塊層寫死連接數(shù)據(jù)庫(kù)或者讀取遠(yuǎn)程配置就會(huì)特別慢。我的建議是把耗時(shí)的初始化操作盡量挪到 fixture 的 setup 階段而不是模塊頂層。如果必須在模塊層加載數(shù)據(jù)優(yōu)先使用惰性加載讓用例真正運(yùn)行到那一層才初始化資源。比如用例參數(shù)化用的 yaml 數(shù)據(jù)讀取可以保留在模塊層但數(shù)據(jù)庫(kù)連接必須放到 fixture 中。8.4 數(shù)據(jù)驅(qū)動(dòng)用例中文 ID 亂碼或無(wú)法定位用pytest.mark.parametrize時(shí)默認(rèn)的參數(shù)化 ID 是參數(shù)值的 repr比如數(shù)據(jù)是 dict就會(huì)變成case0、case1不容易定位失敗的是哪條??梢越o參數(shù)化指定 ids直接讀取 yaml 里的 case 名稱pytest.mark.parametrize(case, load_cases(...), idslambda c: c[name]) def test_register_success(self, case, http_session): ...但你可能會(huì)發(fā)現(xiàn)如果用例名是中文終端和 HTML 報(bào)告里顯示的是轉(zhuǎn)義后的 unicode閱讀起來(lái)依然費(fèi)勁。建議在 ids 函數(shù)里統(tǒng)一做處理def case_ids(case): return case[name].replace( , _)讓 ID 既包含中文字段又能通過(guò)pytest --collect-only看清楚當(dāng)前收集的用例列表排錯(cuò)成本會(huì)低很多。8.5 Allure 報(bào)告里步驟歸屬錯(cuò)亂的問題并發(fā)模式下用 Allure如果不小心用了全局的 step 上下文容易把不同用例的步驟串到同一個(gè)報(bào)告節(jié)點(diǎn)里。Allure 的with allure.step(...)本身沒有線程安全問題但如果你在 fixture 里用with allure.step并且這個(gè) fixture 被多個(gè)并發(fā)用例共享步驟上下文就可能混亂。我的經(jīng)驗(yàn)是Allure 的 step 盡量只在用例函數(shù)體內(nèi)部使用公共 fixture 如果要記錄步驟也通過(guò)allure.attach或allure.dynamic處理不要?jiǎng)硬粍?dòng)在 fixture 里開 step。否則用 xdist 并行跑時(shí)一份報(bào)告里會(huì)出現(xiàn)類似步驟 A 屬于用例 1 又出現(xiàn)在用例 2 下的詭異問題。8.6 requests 請(qǐng)求出現(xiàn)太多重定向或連接過(guò)多接口測(cè)試中如果被測(cè)服務(wù)在前面加了網(wǎng)關(guān)或負(fù)載均衡而測(cè)試機(jī)網(wǎng)絡(luò)代理配置不當(dāng)requests 可能會(huì)走代理導(dǎo)致連接異常。很多團(tuán)隊(duì)在跑本機(jī)用例時(shí)能過(guò)CI 上卻反復(fù)報(bào)連接問題排查到最后往往是環(huán)境變量里的 http_proxy、https_proxy 殘留。解決辦法是在生產(chǎn)測(cè)試腳本里顯式控制代理或者不信任環(huán)境變量session.trust_env False這句能避免 requests 自動(dòng)讀取系統(tǒng)代理。用這一招前確認(rèn)一下你的 CI 網(wǎng)絡(luò)環(huán)境是真的不需要代理訪問被測(cè)服務(wù)否則又會(huì)引入新的不通問題。8.7 遇到一個(gè)偶發(fā)失敗怎么快速定位是不是環(huán)境問題偶發(fā)失敗是接口測(cè)試最頭疼的問題之一。我的實(shí)操經(jīng)驗(yàn)是給所有關(guān)鍵請(qǐng)求加上 trace_id或者至少加上開始和結(jié)束時(shí)間戳這樣一旦偶發(fā)可以從報(bào)告和日志里比對(duì)請(qǐng)求耗時(shí)。比如一個(gè)用例之前跑 200ms這次失敗了但是耗時(shí) 6000ms那大概率是環(huán)境網(wǎng)絡(luò)抖動(dòng)而不是業(yè)務(wù)斷言問題。一個(gè)用例正常 200ms失敗時(shí)只有 20ms且響應(yīng)體是 JSON 解析錯(cuò)誤那就很可能是服務(wù)端直接返回了 502 或者網(wǎng)關(guān)超時(shí)的頁(yè)面。所以框架里最好在請(qǐng)求封裝層就統(tǒng)一記錄“開始時(shí)間-結(jié)束時(shí)間-響應(yīng)碼-響應(yīng)體摘要”。排查偶發(fā)問題時(shí)這四條信息能排除掉 70% 以上的環(huán)境因素干擾。8.8 根據(jù)我的經(jīng)驗(yàn)接口測(cè)試框架維護(hù)的幾個(gè)長(zhǎng)期習(xí)慣最后補(bǔ)幾個(gè)我踩過(guò)許多坑之后形成的習(xí)慣不一定在每本教科書里都寫但長(zhǎng)期實(shí)踐下來(lái)的確省心。一是定期清理報(bào)告目錄。Allure 的 history 目錄如果不清理會(huì)保留很多歷史數(shù)據(jù)導(dǎo)致報(bào)告生成越來(lái)越慢。用 pytest 腳本里的--clean-alluredir參數(shù)或者定期刪除 reports 目錄能保持報(bào)告清爽。二是把日志和報(bào)告分開。pytest 的 console 輸出適合人看但排查問題主要靠結(jié)構(gòu)化日志。我通常會(huì)在請(qǐng)求封裝層用logging寫 request/response 摘要文件名帶日期發(fā)布到 CI 后作為 artifacts 存檔比看控制臺(tái)輸出高效太多。三是優(yōu)先維護(hù)核心冒煙集。哪怕業(yè)務(wù)模塊再多也要保障十幾條冒煙用例能快速跑通核心鏈路。很多團(tuán)隊(duì)接口用例堆到幾千條之后反而把最核心的登錄、下單、支付這些主流程用例淹沒在大量參數(shù)化用例里。每次做框架改造或者環(huán)境升級(jí)后先跑冒煙集確認(rèn)主鏈路沒問題再跑全量回歸。這不僅是時(shí)間管理的問題也是排查效率的核心保證。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
黄色成人网站在线播放| 91色久| 狠狠色五月激情| 99热久久这里只有精品| 五月婷婷开心网| 91九色首页| 99热综合| 激情综合啪啪| 色婷婷成人丁香| 夂夂夂夂夂夂夂夂夂夂夂夂夂夂夂夂夂夂夂亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲色 | 婷婷五月中文字幕| 欧美私人家庭影院| 五月香婷婷| 精品九九视频| 久久刺激网| 精品人妻伦九区久久AAA片| 五月天成人在线播放丁香| 中文字幕成| 久热伊人| 亚洲久艹| 中文成人在线| 五月婷庭丁香在线| 97操碰| av免费在线观看0| 色情五月综合婷婷| 婷婷爱五月| 袁子仪视频观看| 天天综合网色欲香| 久久精品国产AV一区二区三区 | 婷婷成人综合免费视频| 嫩草AV久久伊人妇女超级A| 色婷激情网| 亚洲婷婷五月| 日本免费91| 色婷婷成人做爰A片免费看网站| 亚洲无码99| 久久狠狠色| 黄色一极大片| 夜夜嗨一区二区三区直播内容 | 久久婷五月综合色| 国产精品久久在线观看技巧| 思思久久网| 四季AV综合网| 狠狠人妻色综合| 丁香六月天婷婷色| 99久久婷婷国产综合精品| 九九99久久| 开心婷婷五| 男人的天堂在线婷婷| 免费婷婷| 超碰免费在线| www五月| 天天爽夜夜爽夜夜爽精品视频| 99色在线视频观看| 综合一啪| 五月丁香A片| 熟女人妻一区二区三区免费看| 强伦轩人妻一区二区电影| 亚洲色综合性| 5月婷婷6月六月丁香| 色五月婷婷少妇人妻| 91丨九色丨熟女|新版| 性生生活大片又黄又| 超碰99在线观看| 狠狠干综合网| 久婷婷| 色色com| 亚洲天堂aaa| 久热精品视频| 国产avapp 网| 这里有精品| 91人妻PORNY九色大屁股| 久久精品婷婷| 狠狠色噜噜狠狠狠狠综合| 91婷婷色五月| 夜夜穞天天穞狠狠穞AV美女按摩| 色色欧美。| 天堂无码人妻精品AV一区| 五月丁香激情综合| 五月婷婷亚洲天堂97色婷婷| 大香蕉人人网| 丁香五月天AV在线| 久久婷五月天| 丁香婷婷五月综合影院| 性色天| 精品成人在线观看| 国产成人精品一区二三区熟女在线| 亚洲综合九九| 99啪在线视频| 婷五月丁香俺| 这里只有精品1| 99热精品一区| 91婷婷色| 综合色色五月| 色丁香婷婷| 日韩色色小视频| 五月天a婷婷伊人| 五月天 另类图片| 丁香六月婷婷综合缴| 亚洲天堂色色| 在线观看免费观看在线9久| 色五月丁香五| 色香蕉影院| 五月色婷婷影视在线电影| 开心五月婷婷99| 亭亭玉月丁香| 丰满人妻一区二区三区| 免费在线观看欧美激情xx小视频| 亚洲色情网站| 色五月激情网| 69精品人人人人| 五月天婷婷狂暴白浆| 婷婷97狠狠成人网站| 国产看真人毛片爱做A片| 亚洲午夜在线视频| 无码区婷婷五月花开| 九九人人操| 9热在线观看| 丁香五月大香蕉| 亚洲九九免费| 五月丁香六月婷婷久久肏| 久久九九免费视频| 精品一区二区三区三区| 天天舔天天摸天天射| 开心五月综合| 天天射影院| Aα在线免费观看| 亚洲小视频免费播放| 深夜婷婷五月丁香| 午夜成人AV在线| 嫩BBB槡BBBB搡BBBB| 99碰碰。| WWW.桔色成人.COM入口| 亚洲精品国产成人AV在线| ..真实国产乱子伦对白在线_欧| 99er热精品视频| 五月色婷婷在线观看| 色五月丁香激情| 天天操夜夜操| 黄色av高清| 色婷婷久久| 狠狠另类视频| 国产精品电影| aⅤ79成人片| 五月丁香综合久久| 五月丁香综合网| 99久久人人| 热99国产精品| 丁香六月婷婷色XXXXX| 九热网站| 婷婷中文字幕| 91Chinese在线| 久久免费高| 69热91天堂| 亚洲中文字幕AV| 亚洲欧美另类在线23p| 亚洲色五月天| 五月丁香五月丁香| 婷婷综合五月天| 婷婷va| 热久久成人| 色婷婷狠狠爱| 99久在线观看| 五月亭亭六月天| 婷婷丁香五月天熟女丝袜| 综合久色五月| 中文字幕网伦射乱中文| 99er在线观看| 丁香五月婷婷五月基地| 婷婷五月丁香香蕉| 色播五月丁香综合| 成人九九视频| 色婷婷成人久久| 色青五月天| a久久| 久久五月天色婷婷| 在线播放中文字幕| 天天射综合网天天插| 五月天激情国产综合婷婷婷| 天天爱天天做天天舔| 天天拍天天操| 玖玖福利视频资源| 日本欧美999久久久三级片| 日本在线观看aaa 99| 婷婷综合成人五月天| 永久免费一区二区三区| 综合色色婷婷| 欧美激情VA永久在线播放| 日本三级日本三级99| 成功精品影院| 国产亚洲99| 天天干夜夜谢| 亚洲在线激情婷婷五月| 国产69久久久欧美黑人A片| 激情六月婷婷| 夜夜爽天天日| 亚洲AV网站在线观看| 丁香婷婷激情| 99er精品| 五月丁香婷婷激情爱爱| 久久久大香蕉| 一夜福利不卡| 大香久久综合网| 五月丁香亚洲婷婷| 丁香啪啪| 丁香99| 天天干狠狠| www.久久久久久久久久久| 激情五月天视频| 青青草色在线视频观看| 国产成人精品亚洲线观看| 六月婷婷狠狠做| 成片免费观看大全| 99re8这里只有精品99re8热视频| 久久久久久丁香五月| 狠狠色婷婷色| 中文字幕永久在线| 丁香五月成人网| 伊人玖玖精品| 超碰婷婷五月| 99精品性爱| 婷婷色播综合五月| 综合久久9| 日本高清久久| 99啪99| 美妞av| 久久看九九90| 狠狠色大香蕉| 狠狠操婷婷| 久久五月天视频| 大香蕉av在线| 大香蕉天堂| 丁香五月AV综合| 麻豆忘忧草午夜| 超碰在线人妻| 色色色色网站| 99免费视频精品| 黑人无码一区| 99在线精品免费视频| 婷婷五月色亚洲| 99网址在线看| 成人在线视频一区| 丁香九月激情在线视频| 影音先锋女人AA鲁色资源| 五月婷婷天| 天天爽天天日人人爱 | 亚洲精品色| av婷婷丁香 六月| 99资源在线| 国产真人做爰视频免费| 久草五月| 亚洲AV综合在线观看| 丁香五月天BBw| av在线观看免费| 超碰A V在线| 亚洲AV免费在线| 久久久久久久人妻| 九九热在线观看视频| 亚洲精| 99在线69| 五月婷婷啪啪| 综合网色| 69热在线| 人人草公开操| 思思热久久阴99| 久久久宗合| 色综合久久无码| 99精品国产乱码久久久人妻| 内射爽无广熟女亚洲| 一起草Av| 色婷婷电影网| 五月婷婷深深爱| 五月激情啪啪| 色爱综合网| 亚洲色综合性| 丁香五月天天高清在线| 五月天婷婷爱| 亚洲免费观看高清完整版AV线| 五月天狠狠草| 成人精品一区二区三区四区五区| 五月丁香趴趴| 亚洲精品在线视频| www99精品| 97干免费视频| 99久久免费性爱视频`| 五月色网| 国产va在线视频| 热久久77777| 激情网第九色| 婷婷九月激情| 天天操夜夜爽歪歪| 天天做天天爱天天高潮| www.夜夜.com| 色综合天天综合成人网| 成人丁香色| 五月玖玖| 99网| 欧美啪啪五月天| 色色色地址| 色99色| 久久久天堂国产精品女人| 青柠影视免费高清电视剧| 色婷婷小说| 91久久久久久久久久18| 九九在线视频| 99热99思午夜精品| 天堂A∨在线| 激情婷婷五月亚洲| 99九九精品| 丁香伊人激情| 激情婷婷五月天| 思思热这里只有精品| 99九九精品| 秋霞性爱AV| 99re在线播放| 91一道本| 婷婷丁香水多多视频| 久久亚洲婷婷| 大香蕉九九| 五月天激情国产综合婷婷婷| 99精品无码| 拍色综合| 久久婷婷丁香五月一二三| 激情五月六月丁香| 天天插综合| 综合 激情 婷婷| 婷婷久久伊人| 热99re| 六月撸婷婷| 丁香久久| 久久久91| 国产五月丁香在线| 欧美精品18| 久久综合九九| 欧美五月婷婷| 狠狠色综合五月| 色综合久久99色| 少妇大叫太大太粗太爽了A片| 五月伊人婷婷999| 色之综合网| 婷婷五月在线播放| 国产AV国片偷人妻麻豆| 九九99久久| 色婷婷综合网站| 久婷婷| 97色一二三| 99免费成人网| 人妻精品在线| 亚洲 欧洲 国产 伦综合| 天天看片日日夜夜| 五月丁香六月激情欧美综合| 狠狠色狠狠爱| 五月天成人在线播放丁香| 色狠狠综合网| 久久激情中文| 成人羞羞啪啪 全 视频| 91人人操人人| 成人Av在线大片| 激情五月丁香激情综合网| 婷婷综合色图| 久综合| 天天射影| 伊人五月综合网| 操比激情五月综合| 再次出发二| 秋霞AV淫| 综合一区二区三区| 婷婷色导航| 丁香六月成人| 啄木鸟丝袜美女福利视频| 少妇高潮呻吟A片免费看软件| 激情九月综合| 99视频在线| 久久色五月天| 五月色婷婷影院| 日韩精品电影| 九九热精品视频| 亚洲一二三网| 五月成人网站| 色综合久久88色综合天天看| 99riAV国产精品视频| 操逼国产91| 色婷婷久久综合中文久久一本| 久99视频在线观看| 亚洲AV日韩在线观看| 天天综合网91| 91久久1118| 婷婷五月天激情小说| 色五月大| 色婷丨日丨天丨综合久久| 精品久久久中文字幕大豆网推荐理由| 五月婷婷之综合激情| 婷婷色吧| 九九视频在线观看| 色噜噜狠狠色综合AV兰草影视| 超碰在线91| 97狠狠色| 国产高潮A片羞羞视频涩涩| 久久久妻人人人| 色婷五月天| 色五月丁香一区在线| 亚洲精品久久久久久久久久吃药 | 亚洲婷婷五月天| 婷婷五月天改成什么了| 大香蕉五月婷婷丁香| 五月草影视| 色色综合热| 99国产精品久久久久久久久久久 | 丁香婷婷社区| 五月天婷婷丁香视频| 五月婷A V在线| 安息电影在线观看完整版| 五月婷婷之综合激情| 99爱99操| 色五月婷婷中文字幕| 日韩人妻在线播放| 久久五月激情| 99啪啪网| 国产精品久久久久久久久久久久| 狠狠舔| 爱99干99| 婷婷五月天久久| 五月丁香六月婷婷,婷| 久久艹网| 婷婷视频在线碰| 色婷婷婷婷| 草综合14| 久久人妻www| 色噜噜伊人| 9福利性视频欧美| 亚洲国产精品成人午夜| 激情网五月天| www.99热视频| 国产精品久久久久久白浆色欲| 五月天成人在线播放丁香| 六月婷婷天天操夜夜爽视频| BT综合在线视频观看| 久热精品9999| 另类亚洲电影| 97超级碰人人| 一本到不卡高清DVD| 天天精品视频免费观看| 婷婷丁香97| 婷婷五月激情小说| 夜夜操天天干| 五月婷婷天| www.日韩艹| 5月婷婷综合| 久久综合中文| 日韩成人无码人妻| 99操视频| 九九日本视频| 成人视频九九| 五月天欧美 另类小说| 久久香蕉影院| 天天情天天狠天天透| 97久人人| 久激情网| 婷五月丁香俺| 大波美女VA网站| 天天做天天视天天谢| 日韩久久色| 久热99热| 天天色播| 99热国产这里只有| 亚洲色小说在线综合| 久久久久综合激动五月天| 99成人网站| 五月激情综合激情五月| 人人操人av| 婷婷丁香五月天小说| 五月成人网天天| 天天情色五月天| 久色五月| 99这里只有精品| 26.uuu丁香五月婷婷| 综合激情在线| 婷婷亚洲色| 日本欧美成人片AAAA| 国产乱人偷精品人妻A片| 亚洲精品久久久久久久久久飞鱼| 第四色色六月色综合| 五月色丁香婷婷综合| 人操人人| 丁香六月婷婷综情欧美| 涩涩网五月天| 婷婷五月色| 久草五月| 五月成人丁香av91| 五月天狠狠色| www.99热最新视频8| 五月婷婷久久内射| 人妻久久久| 精品久热| 久久性都花花世界成人免费视频| 色婷婷伊人| 九九九九九无码| 天天天天天天天操| www.久久久久久久| 国内一级片| 亚洲av网址| 99色6爱9热| www.lingjunshare.com| 婷婷五月丁香啪啪| 91精品久久久久久久久久| 久久婷婷五月综合色天| 欧美狠狠草| 天天操天天爱天天玩| 丁香五月天视频| 婷婷色网| 99热主页日本| 天天插天天插天天插| 91色涩| 日本熟妇乱妇熟色A片蜜桃| 亚洲视频在线网| 人人草公开操| 先锋资源91| 中文字幕乱码亚洲精品一区| 色久九| 五月天色视频| 人妻AV在线| 色色日本欧美| 99热这里只有精品1998| 99热这里只有精品在线| 色哟哟性爱av| 丁香五月婷婷手机| 北京熟妇搡BBBB搡BBBB| 日日噜噜夜夜狠狠久久丁香六月| 五月丁香激情婷婷| 欧洲电影在线观看免费版英语版| 一区二区成人电影| 噜一噜在线| 熟女人妻一区二区三区免费看| 99精品偷拍视频| 丁香五月天婷婷久久综合| 五月婷婷与六月丁香图片激情| 高清资源站日A美A欧亚…| 九九色色网| 色偷偷色婷婷| 五月丁香婷婷激情爱爱| 四色女婷婷| 综合网亚洲| www.97干视频| 天天日天天干天天操| 亚洲激情四射色| 日本熟女三区| 99操视频| 91免费在线视频6| 亭亭五月色男人| 99热碰碰| 九九热这里只有精品6| AA片在线观看视频在线播放| 侠女刀之记忆电影在线看免费| 91色婷婷综合久久中文字幕二区| 超碰无码318604| 99亚州综合精品成人网| 日韩三级视频一区二区| 亚洲色无码| 色婷婷香蕉在线| 26uu| 日本操B视频| 综合视频五月| 开心色色五月天综合| 狠狠色丁香99| 成人综合视频网址| 这里只有精品在线免费视频| 日本色色视频| 丁香五月电影| 香蕉大综综综合久久| 毛片色五月| 婷婷碰碰| 丁香六月无码播放| 成人短视频免费观看| 色亚洲欧洲| 久久99激情五月天| 99久久五月婷婷| 激情四射婷婷色色色| 亚洲激情久久| 99色在线观看| WWW.久久.COM| 99re这里| 91爱操| 五月天婷婷小说| 色五月婷婷少妇人妻| 国产在线aaa片一区二区99| 97婷婷丁香五月| 99在线免费视频播放| 深爱激情五月网| 亚韩在线视频| 熟妇内谢69XXXXXA片| 激情综合亚洲色婷婷五月| 婷婷成人av| 激情五月天开心| 色优久久| 欧美日韩成人综合9| 婷婷五月色天| 天天玩夜夜操天天爽| 欧美交换配乱吟粗大25P| 五月婷婷六月奇米网丁香| www.五月婷| 婷婷激情五月综合| 国内精品玖玖| 色色色国产| 99成人| 99热国产这里只有精品| 色情综合网| 婷婷97色| 久久久精品色| 五月天激情www| 九九热a| 人人操超碰| 97人人看| 欧美久久婷婷| 婷婷激情伍月网| 97色综合视频| 五月天激情小说网| 丁香婷婷深情五月亚洲| 五月天婷五月天综合网小说首页-五月天激激婷婷大综合,婷婷亚洲综合五月天小说 | 超碰在线超碰| 深爱激情五月天婷婷网| www.99久| 玖玖九九9999在线观看视频精品| 欧美成人精品A片免费一区99| 亚洲综合色棒| 美妞av| 另类视在线| 青草视频在线播放| 99精品久久| 五月婷婷久草在线视频综合| 色婷婷综合久色AV五色最新| 丁香婷婷五月综合| 亚洲视频一区| 26uuu日韩| 五月性色| 天天舔天天摸天天透| 99玖玖免费视频| 五月婷婷性爱网| 亚洲五月婷| 婷婷午夜综合| 久久婷婷成人综合色怡春院| 黄涩毛片| wWw色五月| www,色婷婷| 9色免费网| 老师的粉嫩小又紧水又多A片视频| 91丁香五月| 婷婷五月花丁香| 丁香五月激情站| 色五月天.con| 亚洲天堂爱爱| 婷婷久久五月天丁香| 91免费试看| 91狠狠色丁香婷婷综合久久狠丁香综合久久精品 | 丁香五月狠狠综合欧美| 99久久99视频只有精品| 久艹大香蕉| 毛片蕉地一二| 91av视频| 丁香花狠狠婷婷亚洲中文字幕| 亚洲国产精品二二三三区| 色色色色网站| 在线只有精品| 色偷偷五月天| 91一起操| 玖玖色综合| 九九热精品| 婷婷五月天色| 天天干天天插| 99热精品在线观看| 欧美日韩中文国产一区发布| 婷婷97狠狠成人网站| www.99色| 五月天综合在线网| 秋霞九九无码| 婷婷伊人五月丁香天堂网| 超碰啪啪网| 牛牛色av| 五月婷婷激情久久| 久久99免费视屏| 五月天婷五月天综合网小说首页-五月天激激婷婷大综合,婷婷亚洲综合五月天小说 | 天天噜天天爱| 99热大片| 丁香五月天AV| 亚洲综合视频网| 超碰人人操在线| 亚洲综合色婷婷| 激情五月久久| 99婷婷精品推荐在线视频| 亚洲激情四谢| 色婷婷色| 在线不卡视频| 99热欧| 日日日影院| 婷婷激情五月综合| 日韩 中文 欧美| 色五月婷婷在线| 韩日另类| 日本精品干| 色噜噜狠狠色综| 激情五月天之六月婷婷| 五月婷婷丁香瑟瑟视频| 黄网免费观看| 欧美69久成人做爰视频| 天色综合网| 欧美性二区| 国产美女最新VA在线免费观看| 婷婷五月天电影区小说区| 亚洲综合另类| 免费做A爰片77777| 99碰在线视频| 精品九九在线观看| 99热网站| 久草婷婷| 久久在线大香蕉| 色婷婷婷av| 亚洲精品午夜国产va久久成人| 99在线观看视频| 7EzOBIhNq85TO| 超碰成人av| 五月激情偷拍婷婷| www.maotanji.com| 日韩AV免费看| www99在线观看视频| 激情综合网五月天天| 婷婷五月六月| 日日鲁鲁鲁夜夜爽爽狠狠视频97| www.99精品视频| 欧美丁香六月激情视频| 97热视频| 亚洲天堂有码| 狠狠干2007| 天天操B| 九九无毛| 六月丁香深深爱| 五月丁香久| 思思热99er| 五月激情丁香六月狠狠干| 99精品综合| 成人综合视频网址| 狠狠香婷婷五月| 色色五月婷婷久久| 九九99热精品| 人人射av| 色婷婷电影网| 超碰二区| 成人丁香五月天Av| YW无码| 97涩涩丁香五月天| 色色色综合色| 色婷| www,色婷婷| HD久久精品视频| 九九色99| 国产精品久久久久9999小说| 婷婷激情小说网| 成人av播放| 色玖玖综合| 色五月婷婷影院| 婷婷丁香五月天激情四射| 五月天婷婷色紫薇阁| 色婷婷狠狠爱| 成人网站免费在线播放| 久re热视频| 蜜乳人妻一区二区三区| 五月香六月婷| 五月激情综合婷婷| 26uuu亚洲欧美| 熟女人妻一区二区三区免费看| 7777激情基地| 五月天激情久久| 九色无码| 日本婷久久| 婷婷射图五月天| 激情丁香九九五月综合网| 91人人操| 欧洲亚洲免费视频9| 99精品一二三四视频| 婷婷色五月开心五月| 久久青青日本视频| 美女五月狠狠| 久人操| 欧美性猛交99久久久99| WWW,五月| 久久综合中文| 九九热色视频| 农村熟妇高潮精品A片| 天天粽合合合合| 91狠狠色丁香婷婷综合久久狠丁香综合久久精品 | 亚洲激情97五月天| 综合激情九月婷婷,激情综合婷婷中文字 | 我要射综合| 丰满少妇乱A片无码| 五月天激情久色| 日韩成人电泉AV| 五月天另类激情在线| 91九色无码日韩 | 91九色中文字幕女在线观看| 色五月婷婷av| 国产在线6| 丁香六月婷婷基地| 丁香五月色| 色v综合网| 五月色综合| 99热九九在线| 九九综合88| 91精品无码| 袁子仪视频观看| 日韩av免费版| 久青操| 综合婷婷六月| 31色区视频免费看| www五月天激情com| 色综合婷婷| 99国产性感视频| 人人性久久| 五月婷婷六月丁香| 99久久综合| 久9热在线视频| 婷婷成人基地| 激情五月天天狠狠久久| 丁香五月婷婷婷婷欧美综合| 四虎99热在线观看网站| 成人五月天丁香| 激情综合网激情五月网| 天天色综| 色婷婷基地 | 丁香婷婷六月男男| 夜夜骑日日操| 婷婷五月色情| 国产69久久久欧美黑人A片 | 激情伊人| 天天开心AV色综合婷婷五月天| 91热网址| 97超碰,人人舔,人人操,人人摸| 五月天婷婷丁香基地在线观看| 狠狠色婷婷7| 国产在线视频1234| 亚洲综合成人网| 日本色爽| 最新亚洲色色网| 久久92| 第四色色六月色综合| 思思精品视频| 十一月婷婷激情四射| tingting五月天亚洲| 天天肏天天肏天天肏| 黑人巨粗进入警花疼哭A片| 久99热在线观看| 丁香五月婷婷俺也要去| 欧美激情 日韩无码 婷婷 五月天| 五月丁香婷婷激情四射迷人| 农村熟妇高潮精品A片| 大香蕉啪啪| 亚洲六月色| 先锋资源996| 色999五月色| 丁香五婷| 欧美色必爱| 欧美内射AAAAAAXXXXX| 9l视频自拍9l九色成人| 九九精品免费| 激情婷婷网| 婷婷丁香午夜综合影视| 亚洲色久| 婷色五月天| 亚洲免费av观看| 97luluse| ..真实国产乱子伦对白在线_欧| 色五月天在线观看| AAA久久久| 婷婷激情五月视频| 大香蕉伊然在亚洲90| 精品人妻午夜一区二区三区四区 | 99干日本| 日韩淑女人妻luan伦激情精品一区二| 五月丁香花开综合网| 337p午夜影院| 婷婷丁香一月| 99热精品网| 五月噜噜噜色综合| 玖玖在线视频| 9久久精品| 成人av播放| 激情综合在线观看| 综合一区二区三区| 狠狠色色综合| 黄网免费看| 青青草tp| 亚洲色五月| 激情五月激情综合俺也去婷婷小说| 五月丁香婷婷综合久久| 色久女| 色婷婷大香蕉| 久久久久这里都是精品| 99免费综合网| 丁香六月 婷婷六月| 26uuu国产色| 日本色婷婷久久99精品91| 人人舔人人色人人高潮| 很操日本7| 九月色婷婷综合| 毛片色五月| 色五月激情五月开心五月| 婷婷五月天亚洲五码| 亚洲成人超碰| 99热.com| 国产亚洲精品AAAAAAA片| 色www久视频| 91婷婷丁香五月亚洲| 97婷婷五月激情六月丁香伊人| 99热高清在线| 人妻人人操| 欧美综合激情五月丁香| 麻豆五月丁香婷婷| 99亚洲精品视频| 婷婷午夜丁香| 高清无码网址| 丁香狠狠色婷婷久久无码视频| 91综合在线观看| 丁香婷婷色九月| 色色色色色热| 婷婷激情丁五月| 99热久| 五月婷婷基地| 97夫妻超碰| 亚洲无码www| 九九99免费视频| 色情五月天首页| 日本五月婷| 婷婷久久网| 综合网五月天123| 五月久视频| 婷婷大香蕉| 精品一区久热| 激情婷婷丁香五月天| 五月天婷五月天综合网在线观| dingxiangtingtingliuyue| 久大香蕉| 男女啪啪做爰高潮无遮挡| 五月婷免费视频| 色婷婷五月天天天天天| 日韩爱操视频| 五月色婷婷综合| 午夜成人av在线| 啪啪综合网| 另类综合网| 香蕉久久国产AV一区二区| 婷婷中合| 超碰免费在线| 99热官网精品在线| 超碰九色| www.9797国产| 九九热区一区二区三区| 亚洲网站999| 在线视频婷婷| 激情婷婷丁香五月天小说| 婷婷草| 99re欧美精品| 六月婷婷综合网2| 激情综合激情五月| 天天激情站| 婷婷五月综合久久中文字幕| 天天久| 性爱五月丁香| 五月婷婷天| 丁香激情五月| 思思精品视频| 九九色插| 日韩一区二区A片免费观看| 五月天婷婷网站888| 九九99精品视频在线观看| 99re最新地址| 成人午夜无码视频| 婷婷大乡焦噜噜| xfplayav在线| 久久久中文| 婷婷五月天综合网| 色播五月婷婷| 99青青草99| 色天天狠狠干| 日本天天色| 色五婷婷| 色五月婷婷五月| 久久激情四射| 思思久久思思| 色色操| 在线婷婷| 五月色婷婷影院| 日韩国产在线免费观看| 天天做天天爱综合| 色呦精品| 99热精品一区| 国产SUV精品一区二区6| 人人澡天天色天天做| 亚洲乱码w在线观看| av操一操| 日本偷拍九九九| 五月丁香狠狠| 91re色综合视频| 婷婷色五月色妇| 日本欧美成人片AAAA| 情婷婷五月天在线| 国产精品人成A片一区二区| 色视频2025| 欧美在线干| WWW夜夜| 色婷婷成人做爰A片免费看网站 | 99国产在线| AV电影在线播放| 婷婷色影院| 欧亚洲在线高清视频| 丁香六月色婷婷| 日本综合久| 久操婷婷| 五月婷色丁香| 五月婷婷 激情按摩| 狠狠干五码| 免费色婷婷| 99爱视频在线观看这里只有精品| 狠狠色噜噜色狠狠狠综合色| 六月丁AV| 亚洲精品在线视频| 99久久99九九九99九他书对| 大香蕉久久伊人婷婷五月丁香| 开心激情五月天网| 人妻乱码久久久| 日本熟妇乱妇熟色A片蜜桃| 久久机热这里只有 | 99热色精品| 午夜爱爱爱成人| ai97re99一本| 性爱视频久久| 丁香五月天网站| 天天色播| 都市激情小说婷婷| 人人爽欧美婷婷久久久五月丁香 | 激情婷婷丁香| 五月丁香人人婷婷在线观看| 日本一区二区三区精品视频| 色玖玖综合| 天天操天天爱天天日| 亚洲中文字幕在线观看| www,av好吊操| 人人操97| 国产乱人偷精品人妻A片| 无月播播激情在线观看视频| 九九久久免费视频44| 色色婷| 亚洲XX日本| 日本久久9| 亚洲五月花| 丁香婷婷色五月天| 超碰三级片| 91久久99久久91熟女精品| av性爱在线| 五月天综合激情网| 99久久人妻精品无码二区| 亚洲天堂aaaa| VA婷婷| 激情淫乱男女| 五月婷婷开心网| 天天色,天天日,天天做| 久热大香蕉| 亚洲AV久久久久久久久久久久久久久久 | 亚洲色热| 婷婷五月综合激情免费| 五月天婷婷色在线视频免费观看 | 另类图片激情五月天| 国产色香蕉精品五夜婷| 天天插天天射| 五月婷婷激情综合网| h在线看免费版在线看| 综合六月激情婷婷| 狠狠爱综合| 五月婷婷久久爱| 久色视频| 婷婷五月六月丁香| 99亚洲综合| 亚洲色图81p| 无码 av电影| www色色色com| 色爱亚洲| 亚洲爱爱无码婷婷色五月| 日本久久婷婷| 无码人妻一区二区一牛影视| 久热伊人| 9精品在线| 久热播这里只有精品| 秋霞三级色戒| 丁香五月AV| 99操不停| http:色情日本com| 色五月激情五月| 色欲婷婷五月天丁香| 五月天色在线| 色激情综合狠狠婷婷| 97人人操| 婷婷99丁香| 亚韩精品视频1区| 亚洲综合在线视频| 亚洲精品电影| 人人视频人人干人人做| 五月丁花色综合网| 六月激情久久婷婷| 大香蕉网站,大香蕉综合| 亚洲热久| 9久9久9久女女女九九九一九| 曰韩少妇内射免费播放| 少妇综合网| 色五月首页| 亚洲av成人在线| 五月丁香六月婷婷成人电影| www,26uuu,c0m,色情| 亚洲一区二区无码蜜乳av| 天天在线久久综合| 国产乱码久久| 日本三级大片| 婷婷色导航| 色5月婷婷色| 五月丁香激| 97色啪| 性色av大香综合| 亚洲婷婷五月| 久久精品夜色噜噜亚洲a∨| 色婷婷无吗| 狠狠干狠狠操狠狠爱| 极品嫩草| 丁香五月天社区婷婷| WWW色色色COM| 涩玖玖免费视频| 9 1 A v久久久| 日本9区视频| 日日噜噜久久婷婷五月天| 激情五月九九九| 91亚洲免费片| 骚五月婷婷| 97视频.干com| 成人日韩欧美| 久鲁鲁色网| 第四色色六月色综合| 色五月激情问网站| 五月丁香啪啪激情| 天天cha成人综合网| 亚洲色综合性| 99亚州综合精品成人网| 激情五月综合| 大香蕉伊人99| 五月激情综合网| 国产乱子轮XXX农村| 一本婷婷丁香久久| 狠干综合| 五月色婷| 九热在线这里有精品6| 啪啪啪五月天| 五月天婷婷在线AN| 五月天婷婷小说| 五月天综合在线| 日日夜夜干| 色99xx| 黄色激情久久| 亚洲日本激情| 精品久久久久久久人妻| 棕合影院色色| 97人人草| 99视频在线9| 大香蕉伊在| 思思99热这里只有精品| 婷婷瑟五月天久久综合| 深爱五月日韩| 伊久久婷婷| 成人精品一区日本无码网| 婷婷五月天综合久久日美女| 91九色 婷婷| 五月天婷婷六月激情网| 激情五月视频| 色婷婷五月天成人网| 丁香五月综合| 99re在线免费视频| 九九热超碰| 天天做天天爱天天玩夜夜爽| xx久久| 国产精产国品一二三在观看| 色婷大香蕉| www.粉嫩av.com| 午夜色丁香| 高清无码视频网址| 97婷婷五月丁香| 99亚洲天堂| 毛片色五月| 五月丁香网站| 五月亭久久无码视频| 婷婷五月成人有| 99久在线精品99re8| 996er热| 蜜桃婷婷狠狠久久综合| 麻豆科斗777| 在线只有精品| 人妻丰满精品一区二区A片| 婷婷五月天AV在| 无码中文一区二区三区| 在线视频99| 久久久久久99日本| 婷婷六月丁香激情| 亚洲日日日| 丰满老熟妇BBBBB搡BBB| 色欲九区| 国产VA亚洲VA96| 噼里啪啦在线观看免费完整版视频| 丁香五月天狠狠操| 中文字幕 久久9999| 天天干天天操天天拍| 日韩黄色AV无码| 亚洲激情色色| 99色综合| 天堂色婷婷| 午夜丁香婷婷| 日本视频不卡123区| 午夜婷婷| 一级二级色大片| 久久婷五月影院| 69五月天视频| 婷婷大香蕉| 9热在线视频| 五月婷综合网| 婷婷五月花| AV中文在线| 婷婷五月大香蕉| 激情五月天福利| 一级A片天天操夜夜操| 五月丁香色婷婷| 色婷婷五月综合| 91久久综合亚洲鲁鲁五月天| 亚洲在线成人| 开心婷婷五月中文字幕组| 亚洲无AV在线中文字幕| 亚洲人成网亚洲欧洲无码久久| 97操碰视频| 久久色五月天| 狠狠操天天干| 狠狠久久婷婷| 直接看的AV| 色五月婷婷91在线| 亚洲色A| 天天草女人| 欧美综合丁香网| 成人国产欧美大片一区| VA日本视频| A久久| 丁香六月婷婷五月天| 久热只有精品| 五月天开心激情综合网| 另类伊人婷婷| 中文字幕在线不卡| 天天操B| 久久曰曰| 99婷婷| 久久五月丁香| 婷婷五月天激情在线观看 | 国产欧美日韩综合精品一区二区| 五月丁香六月激情综合网| 99这里只有| 色五月成人婷婷| 国产精品18久久久| 另类小说婷婷色| 亚洲超碰中文字幕| 欧美五月丁香|