關鍵字驅動框架
tep簡介
tep
是一款幫你輕鬆編寫pytest的測試框架。Try Easy Pytest!
快速入門
安裝
pip install tep
驗證安裝成功:
tep -V
Current Version: V2.2.0
____o__ __o____ o__ __o__/_ o__ __o
/ \ / \ <| v <| v\
\o/ < > / \ <\
| | \o/ o/
< > o__/_ |__ _<|/
| | |
o <o> <o>
<| | |
/ \ / \ _\o__/_ / \
新建項目
tep new demo
Created folder: demo
Created folder: demo/case
Created folder: demo/data
Created folder: demo/data/har
Created folder: demo/report
Created file: demo/replay.py
Created file: demo/run.py
Created file: demo/conftest.py
Created file: demo/pytest.ini
Created file: demo/.gitignore
Created file: demo/case/__init__.py
Created file: demo/case/test_demo.py
Created file: demo/data/UserDefinedVariables.yaml
編寫用例
先看個簡單示例,在case/test_demo.py
編寫用例:
def test(HTTPRequestKeyword):
response = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")
assert response.status_code == 200
執行run.py
後出現以下日誌:
URL: http://httpbin.org/status/200
Method: GET
Headers: {"User-Agent": "python-requests/2.31.0", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "keep-alive"}
Request Body: None
Status Code: 200
Response Body:
Elapsed: 0.61046s
恭喜您,上手成功!
抓包自動生成用例
更簡單點,抓包,自動生成用例。
編輯relay.py
,指定HAR目錄和用例目錄:
import os
from tep.libraries.Config import Config
from tep.libraries.Har import Har
if __name__ == '__main__':
profile = {
"harDir": os.path.join(Config.BASE_DIR, "data", "har"),
"desDir": os.path.join(Config.BASE_DIR, "case", "replay")
}
Har(profile).har2case()
默認HAR包放在data/har
,生成用例放在case/replay
。
通過Proxyman、Charles等工具抓包後,導出HAR包到data/har
目錄中。如果還沒來得及抓,可以下載現成的:
https://github.com/dongfanger/tep/blob/master/tests/demo/case/replay_demo.har
執行relay.py
後就會自動生成replay_demo_test.py
用例:
def test(HTTPRequestKeyword, JSONKeyword, VarKeyword):
var = VarKeyword({})
url = "http://httpbin.org/status/200"
headers = JSONKeyword(r"""
{
"Host":"httpbin.org",
"Connection":"keep-alive",
"accept":"text/plain",
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Referer":"http://httpbin.org/",
"Accept-Encoding":"gzip, deflate",
"Accept-Language":"zh-CN,zh;q=0.9"
}
""")
response = HTTPRequestKeyword("get", url=url, headers=headers)
# user_defined_var = response.jsonpath("$.jsonpath")
assert response.status_code < 400
讓複雜的也變得簡單,從登陸到下單示例:
def test(HTTPRequestKeyword, JSONKeyword, VarKeyword, login):
headers = login()
var = VarKeyword({
"domain": "http://127.0.0.1:5000",
"headers": headers
})
url = var["domain"] + "/searchSku" + "?skuName=book"
response = HTTPRequestKeyword("get", url=url, headers=var["headers"])
assert response.status_code < 400
var["skuId"] = response.jsonpath("$.skuId")
var["skuPrice"] = response.jsonpath("$.price")
url = var["domain"] + "/addCart"
body = JSONKeyword(r"""
{
"skuId":"${skuId}",
"skuNum":2
}
""")
response = HTTPRequestKeyword("post", url=url, headers=var["headers"], json=body)
assert response.status_code < 400
var["skuNum"] = response.jsonpath("$.skuNum")
var["totalPrice"] = response.jsonpath("$.totalPrice")
url = var["domain"] + "/order"
body = JSONKeyword(r"""
{
"skuId":"${skuId}",
"price":${skuPrice},
"skuNum":${skuNum},
"totalPrice":${totalPrice}
}
""")
response = HTTPRequestKeyword("post", url=url, headers=var["headers"], json=body)
assert response.status_code < 400
var["orderId"] = response.jsonpath("$.orderId")
url = var["domain"] + "/pay"
body = JSONKeyword(r"""
{
"orderId":"${orderId}",
"payAmount":"0.2"
}
""")
response = HTTPRequestKeyword("post", url=url, headers=var["headers"], json=body)
assert response.status_code < 400
assert response.jsonpath("$.success") == "true"
段落式組織代碼,以關鍵字爲驅動,pytest也可以寫的很輕鬆。
目錄結構
- case 存放用例文件
- data 存放數據文件
- report 存放報告文件
- replay.py 抓包自動生成用例
- run.py 執行用例入口
用例管理
- 用例所在目錄稱之爲用例集。
- 用例全部寫在一個文件裏面,從上往下分成多個段落,每個段落視爲一個測試步驟。
- 測試步驟分爲①前置②接口請求③後置三大部分。
前置示例:
headers = login()
var = VarKeyword({
"domain": "http://127.0.0.1:5000",
"headers": headers
})
接口請求示例:
url = var["domain"] + "/addCart"
body = JSONKeyword(r"""
{
"skuId":"${skuId}",
"skuNum":2
}
""")
response = HTTPRequestKeyword("post", url=url, headers=var["headers"], json=body)
assert response.status_code < 400
後置示例:
var["skuNum"] = response.jsonpath("$.skuNum")
var["totalPrice"] = response.jsonpath("$.totalPrice")
編寫用例
手動編寫
Python是解釋性語言,py文件和yaml文件一樣,順序執行。簡單寫,簡單執行。
①新建test_
開頭或_test
結尾的py文件。
②定義函數:
def test():
③在函數參數中輸入關鍵字:
def test(HTTPRequestKeyword):
PyCharm輸入大寫的K
會出現語法提示,方便快速鍵入:
④編寫請求:
def test(HTTPRequestKeyword):
response = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")
⑤添加斷言:
def test(HTTPRequestKeyword):
response = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")
assert response.status_code == 200
headers和body,使用多行字符串表示,通過JSONKeyword
轉爲dict:
url = "https://postman-echo.com/post"
headers = JSONKeyword(r"""
{
"Host":"postman-echo.com",
"User-Agent":"Go-http-client/1.1",
"Content-Length":"28",
"Content-Type":"application/json; charset=UTF-8",
"Cookie":"sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk",
"Accept-Encoding":"gzip",
"sails.sid":"s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk"
}
""")
body = JSONKeyword(r"""
{
"foo1":"HDnY8",
"foo2":12.3
}
""")
response = HTTPRequestKeyword("post", url=url, headers=headers, json=body)
HAR包轉換
單個文件
import os
from tep.libraries.Config import Config
from tep.libraries.Har import Har
def test():
profile = {
"harFile": os.path.join(Config.BASE_DIR, "case", "replay_demo.har")
}
Har(profile).har2case()
指定目錄
import os
from tep.libraries.Config import Config
from tep.libraries.Har import Har
if __name__ == '__main__':
profile = {
"harDir": os.path.join(Config.BASE_DIR, "data", "har"),
"desDir": os.path.join(Config.BASE_DIR, "case", "replay")
}
Har(profile).har2case()
如果HAR包已經生成了用例,第二次執行會跳過。通過配置overwrite: True
可重新覆蓋:
import os
from tep.libraries.Config import Config
from tep.libraries.Har import Har
if __name__ == '__main__':
profile = {
"harDir": os.path.join(Config.BASE_DIR, "data", "har"),
"desDir": os.path.join(Config.BASE_DIR, "case", "replay"),
"overwrite": True
}
Har(profile).har2case()
回放對比
profile配置"replay": True
,開啓回放對比:
import os
from loguru import logger
from tep.libraries.Config import Config
from tep.libraries.Har import Har
def test():
har_file = os.path.join(Config.BASE_DIR, "case", "har", "demo.har")
profile = {"replay": True}
Har(har_file, profile).har2case()
等待HAR包轉換爲pytest用例後,執行pytest用例,即會對比:
變量管理
全局變量在data/UserDefinedVariables.yaml
中定義,通過UserDefinedVariablesKeyword
關鍵字讀取。
局部變量在test()
函數內定義,通過VarKeyword
關鍵字設值和取值。
其他維度變量可在data
目錄下新建YAML/JSON文件,通過DataKeyword
讀取。
接口關聯
初始化
在test()
函數中通過VarKeyword
關鍵字初始化一個變量池:
var = VarKeyword({})
取值
在步驟後置中通過jsonpath取值:
var["skuId"] = response.jsonpath("$.skuId")
存入變量池中。
傳值
通過JSONKeyword
關鍵字,在字符串中使用${}
標記變量:
body = JSONKeyword(r"""
{
"skuId":"${skuId}",
"skuNum":2
}
""")
框架會使用變量池中同名變量進行替換。
數據管理
PyCharm就是“測試平臺”,單個用例文件就是平臺界面,數據和代碼都寫在裏面。
若要分離,可從文件讀取。
接口管理
以空間換時間,接口不單獨管理,跟隨用例複製多份,用例維護自己的接口參數。
若要複用,自定義關鍵字。
原生斷言
def test_assert_equal():
assert 1 == 1
def test_assert_not_equal():
assert 1 != 2
def test_assert_greater_than():
assert 2 > 1
def test_assert_less_than():
assert 1 < 2
def test_assert_less_or_equals():
assert 2 >= 1
assert 2 >= 2
def test_assert_greater_or_equals():
assert 1 <= 2
assert 1 <= 1
def test_assert_length_equal():
assert len("abc") == len("123")
def test_assert_length_greater_than():
assert len("hello") > len("123")
def test_assert_length_less_than():
assert len("hi") < len("123")
def test_assert_length_greater_or_equals():
assert len("hello") >= len("123")
assert len("123") >= len("123")
def test_assert_length_less_or_equals():
assert len("123") <= len("hello")
assert len("123") <= len("123")
def test_assert_string_equals():
assert "dongfanger" == "dongfanger"
def test_assert_startswith():
assert "dongfanger".startswith("don")
def test_assert_regex_match():
import re
assert re.findall(r"don.*er", "dongfanger")
def test_assert_contains():
assert "fang" in "dongfanger"
assert 2 in [2, 3]
assert "x" in {"x": "y"}.keys()
def test_assert_type_match():
assert isinstance(1, int)
assert isinstance(0.2, float)
assert isinstance(True, bool)
assert isinstance(3e+26j, complex)
assert isinstance("hi", str)
assert isinstance([1, 2], list)
assert isinstance((1, 2), tuple)
assert isinstance({"a", "b", "c"}, set)
assert isinstance({"x": 1}, dict)
測試報告
支持pytst-html報告和Allure報告。
默認爲pytest-html,無需單獨安裝。
Allure需要安裝Java,下載Allure安裝包,解壓後將bin目錄添加到系統環境變量Path:
https://github.com/allure-framework/allure2/releases
協議支持
默認協議爲HTTP1,添加http2=True
參數後,使用HTTP2協議:
def test(HTTPRequestKeyword):
ro = HTTPRequestKeyword("get", url="http://httpbin.org/status/200", http2=True)
assert ro.response.status_code == 200
HAR包轉換用例時,在profile配置"http2": True
也能使用HTTP2協議。
接口重試
給用例添加@retry
,自定義重試策略,比如重試3次,間隔2秒。
根據CODE碼進行重試:
from loguru import logger
from tenacity import retry, stop_after_attempt, wait_fixed
@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(2),
before=lambda _: logger.info("Retrying..."),
after=lambda _: logger.info("Retry completed.")
)
def test(HTTPRequestKeyword):
ro = HTTPRequestKeyword("get", url="http://127.0.0.1:5000/retry/code")
ro.response.raise_for_status() # Add this statement
根據Exception類型進行重試:
from loguru import logger
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(2),
retry=retry_if_exception_type(TypeError),
before=lambda _: logger.info("Retrying..."),
after=lambda _: logger.info("Retry completed.")
)
def test_exception(HTTPRequestKeyword):
ro = HTTPRequestKeyword("get", url="http://127.0.0.1:5000/retry/200")
raise TypeError
關鍵字
關鍵字的本質是pytest fixture函數。
內置關鍵字
命名方式:大駝峯命名,Keyword
結尾
HTTPRequestKeyword
與requests.request用法完全一致
JSONKeyword
將字符串轉爲dict,支持${}
替換變量
UserDefinedVariablesKeyword
讀取data/UserDefinedVariables.yaml
作爲全局變量
DataKeyword
讀取data目錄下YAML/JSON文件作爲自定義變量
自定義關鍵字
命名方式:下劃線命名。
自定義關鍵字需新建fixture包,文件名以fixture_
開頭:
login
登錄接口,可定義爲全局只登陸一次
mysql_execute
連接MySQL數據庫,執行SQL語句
關鍵字使用示例請查看https://github.com/dongfanger/tep/tree/master/tests/demo/case: