一、引入pytest用例管理框架
python: pytest unittest
java: testng junit
pytest 可以和 selenium,requests,appium 实现 web,接口,app 自动化

在这里插入图片描述
pytest 作用:

1.发现并找到测试用例 模块名必须以 test_ 开头或者 _test 结尾 类名必须以 Test 开头,不能有 init 方法 用例方法必须以 test 开头
2.按顺序执行测试用例
3.通过断言判断测试结果
4.生成测试报告

pytest 强大的插件

pytest    # 本身
pytest-html         # 简单的html报告
pytest-xdist        # 多线程执行
pytest-ordering     # 控制用例的执行顺序
pytest-rerunfailures    # 失败用例重跑
pytest-base-url         # 设置基础路径(并发,测试,生产,预发布)
allure-pytest           # 生成allure报告

插件安装:
1.在项目的根目录创建文件:requirement.txt
2.在文件中写入需要安装的插件名(上面的插件)
3.在项目根目录打开终端控制台,执行命令:pip install -r requirements.txt
4.执行命令后,等待安装插件,安装之后执行:pip list 查看是否安装成功

运行测试用例:
1.命令行(一般调试用):在pycharm的终端控制台,进入到项目的目录,直接运行:

pytest
pytest -vs

2.主函数:在根目录创建 run.py 文件,通过主函数调用 pytest.main()

import pytest

if __name__ == '__main__':
    pytest.main()
    # pytest.main(["-vs"])  # 输出详细信息

3.结果pytest.ini全局配置文件执行(主流方式):

1.在根目录创建文件:pytest.ini 不管是命令行还是主函数,都会读取 pytest.ini 配置文件来执行 pytest.ini
2.文件一般放在项目的根目录 pytest.ini 文件的文件名和参数名都是固定的
3.需要注意编码格式(如果默认的UTF-8报错就改成GBK,GBK报错就改成UTF-8) 文件内容开头必须带:[pytest] 标志

pytest.ini

[pytest]
# 命令行参数
# 常见的:
;  --html=./reports/report.html  简单的测试用例报告
;  --reruns 2    # 失败用例重跑
;  -m "smoke or user"   # 冒烟测试
; -p no:warnings    # 完全禁用警告捕获

# 配置参数: v 输出详细信息 s 表示调试信息
addopts = -vs -p no:warnings

# 配置执行的用例位置(配置执行哪个用例文件)
testpaths = ./testcases

# 配置修改默认的模块规则(配置执行哪个模块)
python_files = test_*.py

# 配置修改默认的类规则(配置执行哪个类)
python_classes = Test*

# 配置修改默认的用例规则(配置执行哪个用例)
python_functions = test_*

# 配置基础路径
base_url = https://192.168.1.93:8887

# 标记
markers =
    smoke:冒烟测试用例(只运行被标记的用例)
# markes 标记 :在上面的参数 addopts 参数中加入 -m "smoke" 就会只运行被标记的用例
# 例如 addopts = -vs -m "smoke" 只会运行被标记了 smoke 的用例
# 例如 addopts = -vs -m "smoke or user" 同时运行被标记了 smoke 和 user 的用例
#     在用例前面加上:@pytest.mark.smoke
#     在用例前面加上:@pytest.mark.user
#  markes = smoke:冒烟测试
#           user:用户名

二、Pytest 的前后置(不常用,了解就行),固件、夹具(常用)
Jmeter 和 Postman:前置脚本和后置脚本
pytest 默认前置和后置函数

现在这种方法很少用到了(了解就行)
def setup_method(self):
    print("每个用例前的操作")
    
def teardown_method(self):
    print("每个用例后的操作")
    
def setup_class(self):
    print("每个类前的操作")
    
def teardow_class(self):
    print("每个类后的操作")
class TestSendRequest:
    # 全局变量,类变量
    admin_token = ''

    def setup_method(self):
        print("用例请求之前:执行开始语句")

    def teardown_method(self):
        print("用例请求之后:执行结束语句")

    # 1.system admin 登录,获取 token
    @pytest.mark.smoke
    def test_get_systemadmin_token(self):
        url = 'https://192.168.1.93:8887/api/tokens'
        form_data = {
            "username": "admin",
            "password": "admin"
        }
        response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)
class TestSendRequest:
    # 全局变量,类变量
    admin_token = ''

    def setup_class(self):
        print("类请求之前:执行开始语句")
    
    def teardown_class(self):
        print("类请求之后:执行结束语句")

    # 1.system admin 登录,获取 token
    @pytest.mark.smoke
    def test_get_systemadmin_token(self):
        url = 'https://192.168.1.93:8887/api/tokens'
        form_data = {
            "username": "admin",
            "password": "admin"
        }
        response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)

三、使用 fixture 固件结合contest.py文件实现前后置

fixture 是装饰器,语法规则:
@pytest.fixture(scope="作用域",autouse="自动执行",params="参数化",ids="参数别名",name="装饰器别名")
scope="function"    作用于函数
scope="class"        作用于类(自动执行)
scope="module"    作用于模块(自动执行)
scope="session"    作用于会话(自动执行)
autouse=True        表示所有的用例自动执行(都会执行)
autouse=False     表示所有的用例手动执行(需要传参才会执行)

代码示例:

@pytest.fixture(scope="function", autouse=True, name="ps")
def exe_sql():
    print("请求之前:执行开始语句")
    yield   # 固定写法,yield 后面的均会在作用域后执行
    print("请求之后:执行结束语句")

class TestSendRequest:
    # 全局变量,类变量
    admin_token = ''
    
    # 1.system admin 登录,获取 token
    @pytest.mark.smoke
    def test_get_systemadmin_token(self):
        url = 'https://192.168.1.93:8887/api/tokens'
        form_data = {
            "username": "admin",
            "password": "admin"
        }
        response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)

        # 获取 token 的值(正则表达式提取)
        result = response.text
        TestSendRequest.admin_token = re.search('"access_token":"(.*?)"', result).group(1)  # 正则表达式提取token值,通过下标取值

    # 2.list network configurations
    # @pytest.mark.user
    def test_get_network_information(self, ps):    # autouse=False(如果为True,ps 不用传)所以,在使用时,传入的参数名需要改为固件的别名(name="ps"),如果用固件本名(exe_sql),就会报错
        url = 'https://192.168.1.93:8887/api/network'
        headers = {"Authorization": "Bearer " + TestSendRequest.admin_token}
        RequestUtil().all_send_request(method='get', url=url, headers=headers, verify=False)sh

输出结果:
testcases/test_request_systemadmin.py::TestSendRequest::test_get_systemadmin_token 请求之前:执行开始语句
{"access_token":"9c97c7ca-5f98-4c6e-afe8-4f61bb591ba9","role":"SystemAdmin","expires_at":"2024-04-02T09:48:00.657201909Z","expires_in":1800}
PASSED请求之后:执行结束语句

testcases/test_request_systemadmin.py::TestSendRequest::test_get_network_information 请求之前:执行开始语句
{"web_domain":"192.168.1.93","private_ipv4":"192.168.1.93","public_ipv4":"192.168.1.93","enable_ipv6":true,"private_ipv6":"","public_ipv6":"","primary_dns_server":"","secondary_dns_server":""}
PASSED请求之后:执行结束语句

在一个文件中设置的固件 fixture 只能在当前文件中起作用,如果希望对所有py文件起作用,那么要用到 conftest.py 文件,
把 fixture 装饰器单独放在 conftest.py 文件中,就可以对所有文件起作用(通过 conftest.py 文件控制 Fixture 的作用域)

1.conftest.py 名字是固定的,不能变。专门用来存放 fixture 固件的。(testcases 根目录创建 conftest.py)
2.conftest.py 的固件在使用时都不需要导包。默认同级或子级的py文件都可以使用
3.conftest.py 文件可以有多个。作用域都是:同级或子级
4.conftest.py 的固件执行顺序,执行方式为自动时: 外层的优先级高于内层。手动时: 传参时,位置在前的优先级更高

conftest.py
scope=“作用域”,autouse=“自动执行”,params=“参数化”,ids=“参数别名”,name=“装饰器别名”(后面三个参数意义不大,了解就行)
conftest.py

# fixture 固件
import pytest

# scope, autouse 用法
# @pytest.fixture(scope="function", autouse=True)
# def exe_sql():   # 括号内加 request 固定写法
#     print("请求之前:执行开始语句")
#     yield
#     print("请求之后:执行结束语句")

# params, ids, name用法
@pytest.fixture(scope="function", autouse=False, params=[["鲁迅", "周树人"], ["实华", "朱自清"]], ids=["TEST1", "TEST2"], name="Test123")
def exe_sql(request):   # 括号内加 request 固定写法
    print("请求之前:执行开始语句")
    yield request.param   # 通过 request 将参数传出 固定写法
    print("请求之后:执行结束语句")


test_system_admin_login.py
def test_get_systemadmin_token(self, exe_sql):    # 传参 conftest.py:exe_sql
    print(exe_sql)
    url = 'https://192.168.1.93:8887/api/tokens'
    form_data = {
        "username": "admin",
        "password": "admin"
    }
    response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)


输出结果:
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login[TEST1] 请求之前:执行开始语句
['鲁迅', '周树人']
{"access_token":"64b1c57e-e931-44a2-8135-ba08030c4fbd","role":"SystemAdmin","expires_at":"2024-04-07T09:34:52.673783772Z","expires_in":1800}
PASSED请求之后:执行结束语句

testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login[TEST2] 请求之前:执行开始语句
['实华', '朱自清']
{"access_token":"4341a4fe-cfc4-419e-8fc3-6cb049899bad","role":"SystemAdmin","expires_at":"2024-04-07T09:34:52.681468374Z","expires_in":1800}
PASSED请求之后:执行结束语句

分层前后置

testcases 根目录下的 conftest.py
import pytest
@pytest.fixture(scope="function", autouse=True)
def login():
    print("登录")
    

testcases/system_admin/conftest.py 层级目录下的 conftest.py
import pytest
@pytest.fixture(scope="function", autouse=True)
def goto_systemadmin():
    print("在system admin中执行")


输出结果:
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login 登录
在system admin中执行
{"access_token":"1d56b5b6-9698-4c4f-85e9-457859a966b1","role":"SystemAdmin","expires_at":"2024-04-07T09:59:58.893690596Z","expires_in":1800}
PASSED
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_retrieve_network_configurations 登录
在system admin中执行
{"web_domain":"192.168.1.93","private_ipv4":"192.168.1.93","public_ipv4":"192.168.1.93","enable_ipv6":true,"private_ipv6":"23c8:f5c7:4846:8210:50b0:39c0:66e0:8944","public_ipv6":"23c8:f5c7:4846:8210:50b0:39c0:66e0:8944","primary_dns_server":"162.21.10.98","secondary_dns_server":"162.21.10.98"}
PASSED
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_update_network_configurations 登录
在system admin中执行

PASSED
testcases/tenant/test_tenant_admin_login.py::TestTenantAdmin::test_tenant_login 登录
{"access_token":"33c12f2f-df79-4ffc-9de2-9a7557f8d162","role":"Admin","expires_at":"2024-04-07T09:59:58.911056241Z","expires_in":1800}
PASSED
testcases/tenant/test_tenant_admin_login.py::TestTenantAdmin::test_list_user 登录
{"count":0,"items":[{"id":"825642789151703040","enabled":true,"name":"101","email":"Freeda.Schiller19@gmail.com","display_name":"Wendy Jacobi","role":"Admin","extension_number":"101","mobile_phone":"366-786-5189","work_phone":"693-287-8086","home_phone":"520-509-7189","twitter":"438-525-9936","facebook":"428-526-2094","linkedin":"763-492-8441","instagram":"251-845-9758","avatar":"","avatar_file_id":"","avatar_file_name":"","avatar_file_size":0,"avatar_file_url":"","created_at":"2024-03-28T08:09:40.635813Z"}]}
PASSED

四、接口自动化测试框架(通过文件保持中间变量实现接口关联)
原因:
1.类变量不能跨py文件使用
2.统一管理中间变量

项目的根目录新建 yaml 文件(extract.yaml)
项目的根目录 > commons 目录下新建 yaml_util.py 文件(封装)

yaml_util.py
import os
import yaml


# 写入
def write_yaml(data):
    with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="a+") as f:
        yaml.dump(data, stream=f, allow_unicode=True)


# 读取
def read_yaml(key):
    with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="r") as f:
        value = yaml.load(f, yaml.FullLoader)
        return value[key]  # 返回读取到的值


# 清空
def clear_yaml():
    with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="w") as f:
        f.truncate()
        

test_system_admin_login.py
# System Admin 登录
@pytest.mark.smoke
def test_systemadmin_login(self):
    url = 'https://192.168.1.93:8887/api/tokens'
    body = {
        "username": "admin",
        "password": "admin"
    }
    response = RequestUtil().send_request(method='post', url=url, json=body, verify=False)

    # 获取 token(json.path 提取并写入到extract.py文件中)
    result = response.json()
    value = jsonpath.jsonpath(result, '$.access_token')
    datas = {"admin_token": "Bearer " + value[0]}
    write_yaml(datas)   # 写入到extract.py文件中


# 检索网络配置
@pytest.mark.smoke
def test_retrieve_network_configurations(self):
    url = 'https://192.168.1.93:8887/api/network'
    headers = {
        "Authorization": read_yaml('admin_token')   # 读取extract.py文件中admin_token的值
    }
    RequestUtil().send_request(method='get', url=url, headers=headers, verify=False)
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐