前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【原创】爆肝23页教程,自研关键字驱动框架

【原创】爆肝23页教程,自研关键字驱动框架

作者头像
dongfanger
发布2023-11-11 09:24:56
2450
发布2023-11-11 09:24:56
举报
文章被收录于专栏:dongfangerdongfanger

tep关键字驱动框架教程

tep简介

tepTry Easy Pytest的首字母缩写,关键字驱动框架,专注于接口自动化测试,单个文件即可完成用例编写。

设计理念

✔️稳定:基于成熟框架pytest,天生强大

✔️规范:RobotFramework风格,井井有条

✔️统一:关键字命名与JMeter组件一致,一知万用

✔️原生:关键字用法保留Python原生定义,轻车熟路

✔️兼容:分层机制保证迭代升级不影响老项目,向下兼容

❌拒绝低代码平台,开发成本太高。

❌拒绝EXCEL/YAML,调试太麻烦。

❌拒绝深度编程,绕来绕去太复杂。

✌️只需要一点点Python基础,就能轻松搞定接口自动化。

快速入门

安装

代码语言:javascript
复制
pip install tep

验证安装成功:

代码语言:javascript
复制
tep -v
Current Version: V2.0.0

 ____o__ __o____   o__ __o__/_   o__ __o
  /   \   /   \   <|    v       <|     v\
       \o/        < >           / \     <\
        |          |            \o/     o/
       < >         o__/_         |__  _<|/
        |          |             |
        o         <o>           <o>
       <|          |             |
       / \        / \  _\o__/_  / \

新建项目

代码语言:javascript
复制
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编写用例,脚手架已自动生成:

代码语言:javascript
复制
def test(HTTPRequestKeyword):
    ro = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")
    assert ro.response.status_code == 200

执行run.py后出现以下日志:

代码语言:javascript
复制
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

恭喜您,上手成功!

基础语法

tep框架是编写Python代码的,需要具备一些Python基础。不过无需担心,只要简单入门即可。

Python文件

Python文件是以.py结尾的。可以使用python filename.py命令执行,也可以在PyCharm中右键点击Run按钮执行。

Python语句

一条语句完成一件事,比如打印日志、发送HTTP请求。

代码语言:javascript
复制
print("Hello, Python!")

Python变量

简单理解,= 符号左边的就是变量,变量用来存储数据。

Python数据类型

Number(数字)

代码语言:javascript
复制
x = 123

String(字符串)

代码语言:javascript
复制
s = "cekaigang"

List(列表)

代码语言:javascript
复制
skills = ["测试", "开发"]
# 索引取值
skills[0]

Tuple(元组)

也就是不可变列表。

代码语言:javascript
复制
tup3 = (1, 2, 3)
# 索引取值
tup3[0]

Set(集合)

代码语言:javascript
复制
# 用于求交集、并集等
sites = {'Google', 'Taobao', 'Runoob', 'Facebook', 'Zhihu', 'Baidu'}

Dictionary(字典)

代码语言:javascript
复制
c = {"x": 1, "y": 2}
# 中括号key取值
c["x"]

Python缩进

Python语言特点就是使用4个空格来控制代码块。

代码语言:javascript
复制
def hello() :
    print("Hello World!")

Python函数

函数定义:

代码语言:javascript
复制
def 函数名(参数列表):
    函数体

tep用例就是写在一个test()函数里面的。

函数调用:

代码语言:javascript
复制
# 使用小括号来调用
UserDefinedVariablesKeyword()
# 将函数返回值存入变量
ro = UserDefinedVariablesKeyword()
# 给函数传参,参数可以只传值,也可以传键值对key=value
ro = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")

Python对象

对象包含字段和方法,使用.符合来访问。

代码语言:javascript
复制
# 字段
response.status_code
# 方法
response.json()

Python导入

从其他文件导入代码到当前文件使用。

代码语言:javascript
复制
from tep.utils.Parewise import pairwise

没错,就是这么简单,掌握这些基础语法就能开始使用tep框架了。

框架语法

tep命令

tep -v,等同于tep --version,查看版本

tep -s,等同于tep --startproject,新建项目

关键字语法

关键字不需要import就能使用,将关键字传入test()函数即可。

关键字以Keyword单词结尾,在输入K时能获得语法提示:

代码语言:javascript
复制
def test(Keyword):
    # 返回结果 = 关键字(参数)
    ro = Keyword(param)

关键字跟Python函数用法一样,接受传入参数,执行某个动作,返回操作结果。

关键字概览

框架内置关键字

  • HTTPRequestKeyword 发送HTTP请求
  • BodyKeyword 对接口参数进行参数化
  • UserDefinedVariablesKeyword 用户自定义变量
  • DataKeyword 读取data目录下文件
  • DbcKeyword 数据库连接

用户自定义关键字

  • login 登录
  • mysql_execute 执行sql

更多关键字内容请阅读“高级用法>关键字详解”章节内容。

代码规范

  1. 一条语句写在一行,不换行,超长时建议通过变量拆成多条语句
  2. 关键字返回Result对象,可命名为ro,通过ro.取值
  3. 遵守PEP8,祝您写出漂亮代码

目录结构

  • case 存放用例文件
  • data 存放数据文件
  • report 存放报告文件
  • run.py 执行用例入口

用例管理

  1. 用例全部写在一个文件里面,从上往下分成多个段落,每个段落视为一个测试步骤。用例由多个测试步骤组成。
  2. 测试步骤分为①前置数据准备②接口请求③后置数据提取三大部分。步骤由关键字驱动。
  3. 多条用例按不同模块放在不同目录下,由于用例文件完全独立,可以将稳定用例全部放到某个目录下,命名为“基础用例集”,进行持续维护和定时巡检,执行时指定目录即可。

以上是作者建议,用例管理是很灵活的,框架没有做任何限制,可以自由选择。

用例执行有3种主要方式:

  1. run.py执行
代码语言:javascript
复制
from tep.libraries.Run import Run

if __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)
  1. PyCharm执行
  2. pytest命令执行

测试报告

支持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语句:

代码语言:javascript
复制
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()方法:

代码语言:javascript
复制
sku_id = response.jsonpath("$.skuId")

默认取匹配到的第一个,更复杂取值使用JSONPath原生方法。

传值

使用BodyKeyword关键字:

代码语言:javascript
复制
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框架核心,语法统一:

代码语言:javascript
复制
ro = Keyword(param)

任何关键字都遵循这种用法。

tep关键字分为内置和自定义两大类。

内置

内置关键字命名为单词首字母大写且以Keyword结尾。

HTTPRequestKeyword

学习requests.request即可,HTTPRequestKeyword使用方法完全一样。

代码语言:javascript
复制
ro = HTTPRequestKeyword("get", url="http://httpbin.org/status/200")

HTTPRequestKeyword关键字返回Result对象,通过ro.response获取requests.Reponse对象。

BodyKeyword

第一个参数为JSON字符串,注意使用多行字符串且加上前缀r

第二个参数为表达式,key为JSONPath表达式,value为替换值,支持批量替换。

代码语言:javascript
复制
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

不需要传参,直接使用。

代码语言:javascript
复制
ro = UserDefinedVariablesKeyword()

UserDefinedVariablesKeyword关键字返回Result对象,通过ro.data获取解析后字典。

DataKeyword

入参为文件路径,data目录相对路径。

代码语言:javascript
复制
ro = DataKeyword("data.json")

DataKeyword关键字返回Result对象,通过ro.data获取解析后字典。

自定义

自定义关键字命名为小写加下划线。需要用户输入数据的关键字为自定义关键字,比如登录信息、数据库连接信息。

自定义关键字需要新建fixture文件夹,文件名以fixture_开头才能识别:

login

使用:

代码语言:javascript
复制
def test(login):
    ro = login()
    print(ro.data)

定义:

代码语言:javascript
复制
import pytest

from 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

使用:

代码语言:javascript
复制
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

定义:

代码语言:javascript
复制
import pytest

from tep.libraries.DB import DB
from 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装饰器即可定义。

为了规范和统一,建议采用以下原则:

  1. 自定义关键字使用小写字母加下划线命名,跟tep内置关键字区分
  2. 定义一个内部函数,返回函数名
  3. 内部函数返回Result对象

tep框架除了conftest.py定义的fixture,也能识别fixture目录下以fixture_开头的文件中,定义的fixture,并自动加载,建议把自定义关键字都放在fixture目录下。

基本结构:

代码语言:javascript
复制
import pytest

from 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用户可以创建虚拟环境并激活:

代码语言:javascript
复制
python3 -m venv venv
source venv/bin/activate

创建项目时,带上-venv参数,可创建单个项目的Python虚拟环境,并在该项目的虚拟环境中安装tep:

代码语言:javascript
复制
tep -s demo -venv

三方库

tep用到了很多三方库,可以学习和使用,以更好使用框架:

pytest、requests、jsonpath、pymysql、pytest-xdist、loguru、faker等。

实用案例

场景用例

登录,搜索商家,添加购物车,下单,支付:

代码语言:javascript
复制
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

代码语言:javascript
复制
import json

import pytest
from filelock import FileLock

from 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.readthedocs.io/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识别从而获得语法提示:

版本升级

代码语言:javascript
复制
pip install -U tep

tep做了向下兼容,请放心升级,如果升级后出现不兼容问题,请联系作者。

更新日志

V2.0.0 tep关键字驱动框架

V1.0.0 tep小工具完整教程

V0.2.3 tep小工具首次开源

源码地址

如果对您有所帮助,请帮忙给开源项目点个Star吧,感谢您的支持!

https://github.com/dongfanger/tep

答疑解惑

  • 怎么向其他人介绍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.gitee.io/blog/ 电子书全部由刚哥原创,包含了大量技术文章,包含Python语言、pytest测试框架、teprunner测试平台等。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • tep关键字驱动框架教程
  • tep简介
  • 设计理念
  • 快速入门
    • 安装
      • 新建项目
        • 编写用例
        • 基础语法
        • 框架语法
          • tep命令
            • 关键字语法
              • 关键字概览
                • 代码规范
                • 目录结构
                • 用例管理
                • 测试报告
                • 断言方法
                • 变量管理
                • 接口关联
                • 接口复用
                • 高级用法
                  • 关键字详解
                    • 内置
                    • 自定义
                  • 自定义关键字
                    • 创建虚拟环境
                      • 三方库
                      • 实用案例
                        • 场景用例
                          • 仅登录一次
                          • 实用技巧
                            • Python代码格式化
                              • PyCharm格式化代码不换行
                                • typing语法提示
                                • 版本升级
                                • 更新日志
                                • 源码地址
                                • 答疑解惑
                                相关产品与服务
                                腾讯云微搭低代码
                                微搭低代码是一个高性能的低代码开发平台,用户可通过拖拽式开发,可视化配置构建 PC Web、H5 和小程序应用。 支持打通企业内部数据,轻松实现企业微信管理、工作流、消息推送、用户权限等能力,实现企业内部系统管理。 连接微信生态,和微信支付、腾讯会议,腾讯文档等腾讯 SaaS 产品深度打通,支持原生小程序,助力企业内外部运营协同和营销管理。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档