持續維護的教程
tep教程會隨著版本更新或經驗積累,持續維護在電子書中,最新的最全的內容請鎖定這篇文章【最新】tep完整教程幫你突破pytest:
https://dongfanger.gitee.io/blog/chapters/tep.html
對教程有任何疑問或建議,可新增微信交流喲:cekaigang。
tep是個小工具
tep
是Try Easy Pytest的首字母縮寫,是一款基於pytest測試框架的測試工具,整合了各種實用的第三方包和優秀的自動化測試設計思想,幫你快速實現自動化專案落地。tep不是測試框架,只是一個小工具。在原理篇就能看出來,它所做的事情,就相當於膠水,把pytest相關的測試技術聚合在一起。假如您的公司想使用或推廣tep,那麼請不要說我們準備引入tep,而是應該說我們準備用pytest直接寫Python程式碼來實現自動化。tep只是幫你做到這一步的小小工具。
快速入門
安裝tep
pip install tep
新建pytest專案
tep startproject demo
啟動自帶FastAPI應用
執行utils/fastapi_mock.py
指令碼。
測試
執行samples資料夾下login_pay
指令碼。
生成報告
pytest samples/login_pay --tep-reports
使用篇
用例集
在tests目錄下將測試用例按功能模組分成多個用例集:
tests
user
user_main_process.py
user_validate.py
teacher
teacher_main_process.py
teacher_validate.py
student
student_main_process.py
student_validate.py
測試用例
用例的基本原則是用例解耦:每個.py
檔案都是單獨的可執行的測試用例。
測試步驟
測試用例由測試步驟組成。步驟由描述、資料、請求、提取、斷言5個部分組成:
# 描述
# 資料
# 請求
response = request(
"{method}",
url="{url}",
headers={headers},
{body_grammar}
)
# 提取
# var = response.jmespath("expression")
# 斷言
assert response.status_code < 400
語法約定
強烈推薦直接編寫Python程式碼。無需額外學習新語法,精通Python語言和Python庫用法,讓你的程式碼能力直線上升。tep沒有做特殊封裝,只做了語法約定。tep編寫自動化指令碼的方法,是一種追求效率的極速寫法。
介面管理
介面寫在用例步驟裡,不用單獨管理,不為了程式碼資料分離而分離。如果想單獨管理,可以參考示例程式碼中的mvc寫法,不推薦這種效率偏低的方式。
介面複用
介面複用的原則是邏輯相對簡單,url+入參+出參,比較固定且重複使用次數很多。符合複用要求的介面可以做成fixture,供測試用例使用。參考fixtures/fixture_login.py
指令碼:
from tep.client import request
from tep.fixture import *
def _jwt_headers(token):
return {"Content-Type": "application/json", "authorization": f"Bearer {token}"}
@pytest.fixture(scope="session")
def login(env_vars):
# 封裝登入介面
logger.info("Administrator login")
response = request(
"post",
url=env_vars.domain + "/login",
headers={"Content-Type": "application/json"},
json={
"username": "dongfanger",
"password": "123456",
}
)
assert response.status_code < 400
response_token = jmespath.search("token", response.json())
class Clazz:
token = response_token
jwt_headers = _jwt_headers(response_token)
return Clazz
返回值使用類包了一層,一是為了在寫程式碼時會有語法智慧補全,二是方便後續擴充套件,直接給類新增新的屬性即可,不影響其他用例。
介面串聯
得益於一個.py
檔案就是一條用例的約定。介面的串聯就能通過變數進行實現,從上個介面響應中取值,存入變數,放到下個介面的入參中,輕鬆完成。
全域性變數
env_vars是全域性變數池,提供了put()和get()方法對變數進行動態存取。在fixtures/fixture_env_vars.py
可以設定預設變數:
#!/usr/bin/python
# encoding=utf-8
from tep.dao import mysql_engine
from tep.fixture import *
@pytest.fixture(scope="session")
def env_vars(config):
class Clazz(TepVars):
env = config["env"]
"""變數定義開始"""
# 環境變數
mapping = {
"qa": { # qa環境
"domain": "http://127.0.0.1:5000", # 變數名:變數值
"mysql_engine": mysql_engine("127.0.0.1", # host
"2306", # port
"root", # username
"123456", # password
"qa"), # dbname
},
"release": { # release環境
"domain": "https://release.com", # 變數名:變數值
"mysql_engine": mysql_engine("127.0.0.1",
"2306",
"root",
"123456",
"release"),
}
# 繼續新增
}
# 定義類屬性,敲程式碼時會自動補全
domain = mapping[env]["domain"]
mysql_engine = mapping[env]["mysql_engine"]
"""變數定義結束"""
return Clazz()
區域性變數
就像正常的Python變數一樣使用,沒有特殊的語法。
環境切換
在conf.yaml
中可以切換執行環境:
env: qa
環境之間的差別體現在環境變數,環境變數也是在fixtures/fixture_env_vars.py
中進行預設的。預設有qa
和release
2個環境。
資料驅動
推薦使用pytest.mark.parametrize
。
//TODO整合對excel、json、yaml檔案讀寫方法。
斷言
採用Python原生的assert斷言。
//TODO整理assert用法。
測試報告
在pytest命令列新增引數--tep-reports
就能一鍵生成Allure測試報告,並且會把請求入參和響應出參,記錄在測試報告中。
pytest --tep-reports
自定義日誌
編輯utils/http_client.py
對日誌進行自定義,用例中引用新版本request:
from utils.http_client import request
參考示例samples/http/test_request_monkey_patch.py
。
Pairwise演算法生成功能用例
Pairwise演算法能針對多條件組合用例,從笛卡爾積中,根據兩兩組合過濾,生成更為精簡的測試用例。
輸入3個條件:
- 'M', 'O', 'P'
- 'W', 'L', 'I'
- 'C', 'E'
from tep.func import pairwise
def test_pairwise():
enum = [['M', 'O', 'P'], ['W', 'L', 'I'], ['C', 'E']]
result = pairwise(enum)
print(f"\npair total:{len(result)}")
for p in result:
print(p)
笛卡爾積有18種組合,經過Pairwise演算法過濾後,只會保留9組用例:
cartesian product total:18
100% [■■■■■■■■■■]
pair total:9
('M', 'W', 'E')
('M', 'L', 'E')
('M', 'I', 'C')
('O', 'W', 'E')
('O', 'L', 'E')
('O', 'I', 'C')
('P', 'W', 'C')
('P', 'L', 'C')
('P', 'I', 'E')
錄製流量生成自動化用例
①手動設定系統代理。
②命令列cd到utils
目錄下,在mitm.py中設定過濾域名。
③mitmdump -s mitm.py
開始錄製。
用例會自動生成到tests/mitm
資料夾下。
原理篇
程式碼是最好的文件:
https://github.com/dongfanger/tep
//TODO完善程式碼註釋
pypi庫
tep可以通過pip直接安裝,這是因為原始碼上傳到了pypi官方庫。上傳藉助了poetry來實現:
poetry install --no-dev
poetry build
poetry publish
執行這3條命令,然後輸入pypi註冊的使用者名稱和密碼即可。
整合第三方包
poetry包管理器可以通過命令安裝包:
poetry install package
poetry remove package
整合以後的包會隨著tep一起安裝。
專案腳手架
tep能從系統命令列來呼叫,也是藉助poetry來實現的:
# pyproject.toml
[tool.poetry.scripts]
tep = "tep.cli:main"
這相當於註冊了系統命令,呼叫後會執行tep.cli:main
函式:
import argparse
import sys
from tep import __description__, __version__
from tep.scaffold import init_parser_scaffold, main_scaffold
def main():
"""Parse command line options and run commands.
"""
parser = argparse.ArgumentParser(description=__description__)
parser.add_argument(
"-V", "--version", dest="version", action="store_true", help="show version"
)
subparsers = parser.add_subparsers(help="sub-command help")
sub_parser_scaffold = init_parser_scaffold(subparsers)
if len(sys.argv) == 1:
# tep
parser.print_help()
sys.exit(0)
elif len(sys.argv) == 2:
# print help for sub-commands
if sys.argv[1] in ["-V", "--version"]:
# tep -V
print(f"{__version__}")
elif sys.argv[1] in ["-h", "--help"]:
# tep -h
parser.print_help()
elif sys.argv[1] == "startproject":
# tep startproject
sub_parser_scaffold.print_help()
sys.exit(0)
args = parser.parse_args()
if args.version:
print(f"{__version__}")
sys.exit(0)
if sys.argv[1] == "startproject":
main_scaffold(args)
startproject會呼叫main_scaffold函式,這裡面的邏輯很簡單,就是建立資料夾和檔案,檔案內容是已經寫好的樣板程式碼。
變數池
變數池是在tep/fixture.py
中實現的:
class TepVars:
def __init__(self):
self.vars_ = {}
def put(self, key, value):
self.vars_[key] = value
def get(self, key):
value = ""
try:
value = self.vars_[key]
except KeyError:
logger.error(f"env_vars doesnt have this key: {key}")
return value
它就是一個具有get和put方法的類,變數存在self.vars_
這個全域性字典中,所有指令碼共享同一個變數池。
環境變數
環境配置是通過config來讀取的:
@pytest.fixture(scope="session")
def config():
config_path = os.path.join(Project.dir, "conf.yaml")
with open(config_path, "r", encoding="utf-8") as f:
conf = yaml.load(f.read(), Loader=yaml.FullLoader)
return conf
它是個fixture,會在fixtures/fixture_env_vars.py
中引用到:
@pytest.fixture(scope="session")
def env_vars(config):
class Clazz(TepVars):
env = config["env"]
這樣就能設定環境變數了。
fixture自動匯入
在conftest.py中,進行了fixture自動匯入:
# 自動匯入fixtures
_fixtures_dir = os.path.join(_project_dir, "fixtures")
_fixtures_paths = []
for root, _, files in os.walk(_fixtures_dir):
for file in files:
if file.startswith("fixture_") and file.endswith(".py"):
full_path = os.path.join(root, file)
import_path = full_path.replace(_fixtures_dir, "").replace("\\", ".").replace("/", ".").replace(".py", "")
_fixtures_paths.append("fixtures" + import_path)
pytest_plugins = _fixtures_paths
它會掃描fixtures目錄下所有以fixture_
開頭和.py
結尾的檔案,然後以pytest_plugins形式新增到執行環境中。
requests猴子補丁
requests藉助於裝飾器打了猴子補丁,tep/client.py
:
def tep_request_monkey_patch(req, *args, **kwargs):
start = time.process_time()
response = req(*args, **kwargs)
end = time.process_time()
elapsed = str(decimal.Decimal("%.3f" % float(end - start))) + "s"
log4a = "{}{} status:{} response:{} elapsed:{}"
try:
kv = ""
for k, v in kwargs.items():
# if not json, str()
try:
v = json.dumps(v, ensure_ascii=False)
except TypeError:
v = str(v)
kv += f" {k}:{v} "
args = list(args)
args += ["", ""]
method, url, *t = args
method_url = ""
if method:
method_url = f'method:"{method}" '
if url:
method_url += f'\nurl:"{url}" '
request_response = log4a.format(method_url, kv, response.status_code, response.text, elapsed)
logger.info(request_response)
allure.attach(request_response, f'request & response', allure.attachment_type.TEXT)
except AttributeError:
logger.error("request failed")
except TypeError:
logger.warning(log4a)
return TepResponse(response)
def request_wrapper(req):
def send(*args, **kwargs):
return tep_request_monkey_patch(req, *args, **kwargs)
return send
@request_wrapper
def request(method, url, **kwargs):
# 這是reqeusts原生方法
沒有對requests做任何改動,只加了日誌和報告內容。
一鍵生成Allure測試報告
--tep-reports
是通過pytest plugin來實現的:
#!/usr/bin/python
# encoding=utf-8
import os
import shutil
import tempfile
import allure_commons
from allure_commons.logger import AllureFileLogger
from allure_pytest.listener import AllureListener
from allure_pytest.plugin import cleanup_factory
from tep.fixture import Project
from tep.func import current_time
allure_temp = tempfile.mkdtemp()
class Plugin:
@staticmethod
def pytest_addoption(parser):
parser.addoption(
"--tep-reports",
action="store_const",
const=True,
help="Create tep allure HTML reports."
)
@staticmethod
def _tep_reports(config):
if config.getoption("--tep-reports") and not config.getoption("allure_report_dir"):
return True
else:
return False
@staticmethod
def pytest_configure(config):
if Plugin._tep_reports(config):
test_listener = AllureListener(config)
config.pluginmanager.register(test_listener)
allure_commons.plugin_manager.register(test_listener)
config.add_cleanup(cleanup_factory(test_listener))
clean = config.option.clean_alluredir
file_logger = AllureFileLogger(allure_temp, clean)
allure_commons.plugin_manager.register(file_logger)
config.add_cleanup(cleanup_factory(file_logger))
@staticmethod
def pytest_sessionfinish(session):
if Plugin._tep_reports(session.config):
reports_dir = os.path.join(Project.dir, "reports")
new_report = os.path.join(reports_dir, "report-" + current_time().replace(":", "-").replace(" ", "-"))
if os.path.exists(reports_dir):
his_reports = os.listdir(reports_dir)
if his_reports:
latest_report_history = os.path.join(reports_dir, his_reports[-1], "history")
shutil.copytree(latest_report_history, os.path.join(allure_temp, "history"))
os.system(f"allure generate {allure_temp} -o {new_report} --clean")
shutil.rmtree(allure_temp)
通過pytest_sessionfinish鉤子函式,在pytest執行結束時,生成測試報告。同時會把歷史資料保留下來,以在Allure報告的趨勢圖中進行展示。
//TODO其他原理慢慢更新,歡迎提出疑問,不斷補充。
附錄
tep相比於pytest優勢
【專案建立】
專案腳手架快速建立自動化專案;
良好的專案結構設計;
【上手簡單】
遵循Python原生語法,沒有額外負擔;
提供豐富的介面自動化實踐示例;
【優雅整合】
保留requests庫用法,採用猴子補丁動態輸出日誌;
pytest命令列引數一鍵生成Allure測試報告;
【平臺支援】
teprunner測試平臺線上管理pytest指令碼;
支援Git一鍵同步至平臺;
tep測試平臺化思路
teprunner是基於tep的測試平臺。
從測試工具轉變到測試平臺,最重要是要想清楚用例的執行流程。從前端錄入用例資訊後,通過後端儲存到資料庫,再把資料組裝出來,變成可執行的檔案。teprunner的做法是,把pytest作為引擎,用例全部轉化為檔案,然後使用pytest命令執行用例。
用例解耦是實現平臺化的關鍵原則。tep是按照一個.py
檔案一條用例的約定來編寫指令碼的,使得每個檔案都是獨立的可執行的。這就能很好的對應到,前端測試用例的增刪改查。假如用例沒有解耦,Python檔案之間存在非常多的依賴,那麼想做成Web平臺是很困難的,在介面上根本無法操作。
專案腳手架為平臺化提供了非常大的便利。在測試平臺建立專案時,就會呼叫tep startproject建立一個專案腳手架,相當於給指令碼執行初始化了一套隔離的執行環境,專案的用例之間互不干擾。
至於fixtures、環境變數等功能,如果做好了分層設計,這些都是水到渠成的事了。在做平臺之前,只是為了多人協作方便,把conftest裡面的fixture抽了出來,但是在平臺化時,抽出來的fixtures正好可以做成一個單獨的功能點。環境變數也是在做平臺之前,只是想用yaml來管理配置,但是在平臺化時,正好可以用來在前端切換環境,結合fixture_env_vars.py做成環境變數的功能。
找準測試平臺定位才能遊刃有餘。測試平臺只是一個殼子,做成什麼樣的平臺,取決於對平臺的定位,以及技術實現的能力。正是因為沒有大牛的技術,無法做成大而全的測試平臺,teprunner測試平臺的才定位於pytest指令碼線上管理平臺。這對於tep來說,恰好是剛剛好的選擇。