tep关键字驱动框架教程 「tep简介」 tep
是「T」 ry 「E」 asy 「P」 ytest的首字母缩写,关键字驱动框架,专注于接口自动化测试,单个文件即可完成用例编写。
设计理念 ✔️稳定:基于成熟框架pytest,天生强大
✔️规范:RobotFramework风格,井井有条
✔️统一:关键字命名与JMeter组件一致,一知万用
✔️原生:关键字用法保留Python原生定义,轻车熟路
✔️兼容:分层机制保证迭代升级不影响老项目,向下兼容
❌拒绝低代码平台,开发成本太高。
❌拒绝EXCEL/YAML,调试太麻烦。
❌拒绝深度编程,绕来绕去太复杂。
✌️只需要一点点Python基础,就能轻松搞定接口自动化。
「快速入门」 「安装」 pip install tep
验证安装成功:
tep -v Current Version: V2.0.0 ____o__ __o____ o__ __o__/_ o__ __o / \ / \ <| v <| v\ \o/ < > / \ <\ | | \o/ o/ < > o__/_ |__ _<|/ | | | o <o> <o> <| | | / \ / \ _\o__/_ / \
「新建项目」 tep -s demo Created folder: demo Created folder: demo/case Created folder: demo/data Created folder: demo/report Created file: demo/run.py Created file: demo/conftest.py Created file: demo/pytest.ini Created file: demo/.gitignore.py 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) : ro = HTTPRequestKeyword("get" , url="http:///status/200" ) assert ro.response.status_code == 200
执行run.py
后出现以下日志:
URL: http:///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
恭喜您,上手成功!
基础语法 tep框架是编写Python代码的,需要具备一些Python基础。不过无需担心,只要简单入门即可。
「Python文件」
Python文件是以.py
结尾的。可以使用python filename.py
命令执行,也可以在PyCharm中右键点击Run
按钮执行。
「Python语句」
一条语句完成一件事,比如打印日志、发送HTTP请求。
print("Hello, Python!" )
「Python变量」
简单理解,=
符号左边的就是变量,变量用来存储数据。
「Python数据类型」
Number(数字)
x = 123
String(字符串)
s = "cekaigang"
List(列表)
skills = ["测试" , "开发" ]# 索引取值 skills[0 ]
Tuple(元组)
也就是不可变列表。
tup3 = (1 , 2 , 3 )# 索引取值 tup3[0 ]
Set(集合)
# 用于求交集、并集等 sites = {'Google' , 'Taobao' , 'Runoob' , 'Facebook' , 'Zhihu' , 'Baidu' }
Dictionary(字典)
c = {"x" : 1 , "y" : 2 }# 中括号key取值 c["x" ]
「Python缩进」
Python语言特点就是使用4个空格来控制代码块。
def hello () : print("Hello World!" )
「Python函数」
函数定义:
def 函数名(参数列表): 函数体
tep用例就是写在一个test()
函数里面的。
函数调用:
# 使用小括号来调用 UserDefinedVariablesKeyword()# 将函数返回值存入变量 ro = UserDefinedVariablesKeyword()# 给函数传参,参数可以只传值,也可以传键值对key=value ro = HTTPRequestKeyword("get" , url="http:///status/200" )
「Python对象」
对象包含字段和方法,使用.
符合来访问。
# 字段 response.status_code# 方法 response.json()
「Python导入」
从其他文件导入代码到当前文件使用。
from tep.utils.Parewise import pairwise
没错,就是这么简单,掌握这些基础语法就能开始使用tep框架了。
框架语法 tep命令 tep -v
,等同于tep --version
,查看版本
tep -s
,等同于tep --startproject
,新建项目
关键字语法 关键字不需要import就能使用,将关键字传入test()
函数即可。
关键字以Keyword
单词结尾,在输入K
时能获得语法提示:
def test (Keyword) : # 返回结果 = 关键字(参数) ro = Keyword(param)
关键字跟Python函数用法一样,接受传入参数,执行某个动作,返回操作结果。
关键字概览 「框架内置关键字」
HTTPRequestKeyword 发送HTTP请求 UserDefinedVariablesKeyword 用户自定义变量 「用户自定义关键字」
更多关键字内容请阅读“高级用法>关键字详解”章节内容。
代码规范 一条语句写在一行,不换行,超长时建议通过变量拆成多条语句 关键字返回Result对象,可命名为ro
,通过ro.
取值 目录结构 用例管理 用例全部写在一个文件里面,从上往下分成多个段落,每个段落视为一个测试步骤。用例由多个测试步骤组成。 测试步骤分为①前置数据准备②接口请求③后置数据提取三大部分。步骤由关键字驱动。 多条用例按不同模块放在不同目录下,由于用例文件完全独立,可以将稳定用例全部放到某个目录下,命名为“基础用例集”,进行持续维护和定时巡检,执行时指定目录即可。 以上是作者建议,用例管理是很灵活的,框架没有做任何限制,可以自由选择。
用例执行有3种主要方式:
from tep.libraries.Run import Runif __name__ == '__main__' : settings = { "path" : ["test_demo.py" ], # Path to run, relative path to case "report" : False , # Output test report or not "report_type" : "pytest-html" # "pytest-html" "allure" } Run(settings)
测试报告 支持2种测试报告,pytst-html和allure。在run.py
文件中设置。
默认为pytest-html,无需单独安装,开启后会生成HTML报告到report目录下。
allure需要安装Java环境,然后下载文件,解压后将bin目录添加到系统环境变量Path。
https://github.com/allure-framework/allure2/releases
开启allure报告前请确保已完成安装,否则可能报错找不到allure命令。
断言方法 直接使用Python原生断言,assert语句:
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)
变量管理 「全局变量」 在data/UserDefinedVariables.yaml
中填写,通过UserDefinedVariablesKeyword()
关键字直接读取。
「局部变量」 在用例文件中test()
函数内直接定义。
其他变量可以在data
目录下新建不同的YAML/JSON文件,通过DataKeyword
读取。
接口关联 接口关联是指从上个接口响应取值,将值传入下个接口入参,即参数化。
「取值」
TepResponse内置了.jsonpath()
方法:
sku_id = response.jsonpath("$.skuId" )
默认取匹配到的第一个,更复杂取值使用JSONPath原生方法。
「传值」
使用BodyKeyword关键字:
body = r"""{"id":1,"param":"[{\"page\": 1, \"pinList\":[\"cekaigang\"]}]","ext1":{"a":1,"b":1},"ext2":[1,1,1],"ext3":{"name":"pytest"}}""" ro = BodyKeyword(body, {"$.id" : 9 , "$.param[0].page" : 9 , "$.param[0].pinList[0]" : "dongfanger" , "$.ext1.a" : 9 , "$.ext2[0]" : 9 , "$.ext2[2]" : 9 , "$.ext3.name" : "tep" })
第一个参数为JSON字符串,注意使用多行字符串且加上前缀r
。
第二个参数为表达式,key为JSONPath表达式,value为替换值,支持批量替换。
接口复用 接口复用,或者叫做“用例复用”,通过自定义关键字来实现。可以将多个接口,或者公共用例,自定义为关键字,使用关键字在不同用例之间复用。
高级用法 关键字详解 关键字是tep框架核心,语法统一:
ro = Keyword(param)
任何关键字都遵循这种用法。
tep关键字分为内置和自定义两大类。
内置内置关键字命名为单词首字母大写且以Keyword
结尾。
HTTPRequestKeyword学习requests.request即可,HTTPRequestKeyword使用方法完全一样。
ro = HTTPRequestKeyword("get" , url="http:///status/200" )
HTTPRequestKeyword关键字返回Result对象,通过ro.response
获取requests.Reponse对象。
BodyKeyword第一个参数为JSON字符串,注意使用多行字符串且加上前缀r
。
第二个参数为表达式,key为JSONPath表达式,value为替换值,支持批量替换。
ro = BodyKeyword(body, {"$.id" : 9 , "$.param[0].page" : 9 , "$.param[0].pinList[0]" : "dongfanger" , "$.ext1.a" : 9 , "$.ext2[0]" : 9 , "$.ext2[2]" : 9 , "$.ext3.name" : "tep" })
BodyKeyword关键字返回Result对象,通过ro.data
获取替换后JSON。
UserDefinedVariablesKeyword不需要传参,直接使用。
ro = UserDefinedVariablesKeyword()
UserDefinedVariablesKeyword关键字返回Result对象,通过ro.data
获取解析后字典。
DataKeyword入参为文件路径,data目录相对路径。
ro = DataKeyword("data.json" )
DataKeyword关键字返回Result对象,通过ro.data
获取解析后字典。
自定义自定义关键字命名为小写加下划线。需要用户输入数据的关键字为自定义关键字,比如登录信息、数据库连接信息。
自定义关键字需要新建fixture文件夹,文件名以fixture_
开头才能识别:
login使用:
def test (login) : ro = login() print(ro.data)
定义:
import pytestfrom tep.libraries.Result import Result@pytest.fixture(scope="session") def login (HTTPRequestKeyword) : def _function () -> Result: url = "http://127.0.0.1:5000/login" headers = {"Content-Type" : "application/json" } body = {"username" : "dongfanger" , "password" : "123456" } ro = HTTPRequestKeyword("post" , url=url, headers=headers, json=body) response = ro.response assert response.status_code < 400 ro = Result() ro.data = {"Content-Type" : "application/json" , "Cookie" : f"{response.json()['Cookie' ]} " } return ro return _function
mysql_execute使用:
def test (mysql_execute) : sql = "select 1 from dual" ro = mysql_execute(sql) cursor = ro.cursor column_names = [desc[0 ] for desc in cursor.description] rows = cursor.fetchall() for row in rows: print(row) print(row[column_names.index("1" )]) # get by column name
定义:
import pytestfrom tep.libraries.DB import DBfrom tep.libraries.Result import Result@pytest.fixture(scope="session") def mysql_execute (DbcKeyword) : ro = DbcKeyword(host="127.0.0.1" , port=3306 , user="root" , password="12345678" , database="sys" ) conn = ro.conn def _function (sql: str) -> Result: cursor = conn.cursor() DB.pymysql_execute(conn, cursor, sql) ro = Result() ro.cursor = cursor return ro yield _function conn.close() # After test, close connection
自定义关键字 关键字本质上是pytest fixture,使用@pytest.fixture
装饰器即可定义。
为了规范和统一,建议采用以下原则:
自定义关键字使用小写字母加下划线命名,跟tep内置关键字区分 tep框架除了conftest.py定义的fixture,也能识别fixture
目录下以fixture_
开头的文件中,定义的fixture,并自动加载,建议把自定义关键字都放在fixture
目录下。
基本结构:
import pytestfrom tep.libraries.Result import Result@pytest.fixture(scope="session") # 固定 def keyword_name (other_keyword) : # 关键字命名,可以引用其他关键字 def _function (param) -> Result: # 内部函数,定义参数 # 编写逻辑代码 ro = Result() ro.data = "" # 将数据存入Result对象 return ro # 返回Result对象 return _function # 将内部函数返回,使用时就能像函数一样调用
创建虚拟环境 安装时,MAC用户可以创建虚拟环境并激活:
python3 -m venv venv source venv/bin/activate
创建项目时,带上-venv
参数,可创建单个项目的Python虚拟环境,并在该项目的虚拟环境中安装tep:
tep -s demo -venv
三方库 tep用到了很多三方库,可以学习和使用,以更好使用框架:
pytest、requests、jsonpath、pymysql、pytest-xdist、loguru、faker等。
实用案例 场景用例 登录,搜索商家,添加购物车,下单,支付:
def test (HTTPRequestKeyword, BodyKeyword, login) : ro = login() var = {"domain" : "http://127.0.0.1:5000" , "headers" : ro.data} url = var["domain" ] + "/searchSku" + "?skuName=book" ro = HTTPRequestKeyword("get" , url=url, headers=var["headers" ]) assert ro.response.status_code < 400 sku_id = ro.response.jsonpath("$.skuId" ) sku_price = ro.response.jsonpath("$.price" ) url = var["domain" ] + "/addCart" body = r"""{"skuId":1,"skuNum":2}""" ro = BodyKeyword(body, {"$.skuId" : sku_id}) body = ro.data ro = HTTPRequestKeyword("post" , url=url, headers=var["headers" ], json=body) assert ro.response.status_code < 400 sku_num = ro.response.jsonpath("$.skuNum" ) total_price = ro.response.jsonpath("$.totalPrice" ) url = var["domain" ] + "/order" body = r"""{"skuId":1,"price":2,"skuNum":3,"totalPrice":4}""" ro = BodyKeyword(body, {"$.skuId" : sku_id, "$.price" : sku_price, "$.skuNum" : sku_num, "$.totalPrice" : total_price}) body = ro.data ro = HTTPRequestKeyword("post" , url=url, headers=var["headers" ], json=body) assert ro.response.status_code < 400 order_id = ro.response.jsonpath("$.orderId" ) url = var["domain" ] + "/pay" body = r"""{"orderId":1,"payAmount":"0.2"}""" ro = BodyKeyword(body, {"$.orderId" : order_id}) body = ro.data ro = HTTPRequestKeyword("post" , url=url, headers=var["headers" ], json=body) assert ro.response.status_code < 400 assert ro.response.jsonpath("$.success" ) == "true"
该用例很好的展示了一个文件写用例,按段落,分步骤,编写的思想。
启动mock服务,位于源码tests/scripts/mock.py
,可以运行此条用例成功。
仅登录一次 单进程串行:
login自定义关键字的scope="session"
表示整个测试阶段都只执行一次登录。共有这些维度:session、package、module、class、function,如果设置为function则表示每次函数都要登录,其他同理。
多进程并行:
通过pytest-xdist
可以实现多进程并行执行用例,为了保证全局只执行一次登录,可以自定义关键字login_xdist
:
import jsonimport pytestfrom filelock import FileLockfrom tep.libraries.Result import Result@pytest.fixture(scope="session") def login_xdist (HTTPRequestKeyword, tmp_path_factory, worker_id) : """ Xdist is used in a distributed manner, and this login will only be executed globally once throughout the entire runtime Reference: https://pytest-xdist./en/latest/how-to.html#making-session-scoped-fixtures-execute-only-once """ def _login () : url = "http://127.0.0.1:5000/login" headers = {"Content-Type" : "application/json" } body = {"username" : "dongfanger" , "password" : "123456" } ro = HTTPRequestKeyword("post" , url=url, headers=headers, json=body) response = ro.response assert response.status_code < 400 ro = Result() ro.data = {"Content-Type" : "application/json" , "Cookie" : f"{response.json()['Cookie' ]} " } return ro if worker_id == "master" : # not executing in with multiple workers, just produce the data and let # pytest's fixture caching do its job return _login # get the temp directory shared by all workers root_tmp_dir = tmp_path_factory.getbasetemp().parent fn = root_tmp_dir / "data.json" with FileLock(str(fn) + ".lock" ): if fn.is_file(): _function = json.loads(fn.read_text()) else : _function = _login fn.write_text(json.dumps(_function)) return _function
实用技巧 Python代码格式化 快捷键:
PyCharm格式化代码不换行 默认120字符换行,根据显示屏宽度调整:
typing语法提示 给变量通过: Type
指定类型后,在使用时输入.
就能被PyCharm识别从而获得语法提示:
版本升级 pip install -U tep
tep做了向下兼容,请放心升级,如果升级后出现不兼容问题,请联系作者。
更新日志 V2.0.0 tep关键字驱动框架
V1.0.0 tep小工具完整教程
V0.2.3 tep小工具首次开源
源码地址 如果对您有所帮助,请帮忙给开源项目点个Star吧,感谢您的支持!
https://github.com/dongfanger/tep
在线文档 飞书文档,可评论:
https://eqgvpqzl6c./docx/DZVed7YptocKE1xYIgici1DynTe
答疑解惑 怎么向其他人介绍tep框架?
我发现了一个框架,关键字驱动的,只在一个文件里面就能把一条接口自动化用例写完。
不懂代码能使用tep框架吗?
不能。学嘛,简单入门就能用,Python这么流行,学起来。
conftest.py无法识别?
pytest7.4.0版本更新,默认只有在conftest.py相同目录执行pytest命令才能识别,如果是在子目录执行pytest则无法识别,要么显示指定--confcutdir
目录位置到conftest.py所在目录,要么添加空的pytest.ini
配置文件。
向下兼容的分层机制怎么做的?
在tep.keywords.api
做了一个适配层,暴露给用户的入参为*args, **kwargs
,出参为Result
对象,确保后续升级无论怎么变动入参和出参,对老项目是无感知的。
联系作者 公众号《测试开发刚哥》
开源不易,请作者喝杯冰阔乐支持一下~
❝ 参考资料:
《测试开发刚哥电子书》https://dongfanger./blog/
电子书全部由刚哥原创,包含了大量技术文章,包含Python语言、pytest测试框架、teprunner测试平台等。
❞