GitHub - testerfans/interface-testing · GitHub
Skip to content

testerfans/interface-testing

Repository files navigation

自动化测试框架介绍

一、框架介绍

本框架使用python+unittest+requests+httmrunner+excel+log+jenkins(ci部分待接入)实现的接口自动化框架.

目录结构

interfaceTest                                          // 测试框架名称
|-- api/                                               // 根据模块抽象出来的类和方法 方便在其他脚本中进行调用
|   |-- shipment.py                           
|-- cases/                                             // 用例管理文件夹
|   |-- console/                                       // 系统名称 中文英文均可
|   |   |-- 发货管理/                                   // 系统下的一个大功能模块 如华清的发货管理
|   |   |   |-- 包裹查询/                               // 大功能模块下的子功能模块  发货管理-包裹查询 模块需要与 excel 同名 
|   |   |   |   |-- __init__.py
|   |   |   |   |-- test_select.py                     // 具体模块下的测试脚本 脚本的test_xxx.py  xxx 部分与 excel 内sheet同名
|   |   |   |   |-- 包裹查询.xlsx                       // 此模块下测试用例数据 脚本和用例存放到相同的目录下进行管理 方便维护
|   |   |   |-- 发货任务/
|   |   |   |-- 快递查询/
|   |   |   |-- 订单管理/
|   |   |-- __init__.py
|   |   |-- console.xlsx                               // 进入系统时候的一些接口用例 可以放到最外层管理
|   |   |-- test_getUserMenusByParentId.py
|-- common/                                            // 一些业务上的公共方法 如登录获取cookie等
|   |-- __init__.py
|   |-- common.py
|-- configs/                                           // 配置文件
|   |-- email_style.txt                                // 邮件格式配置   
|   |-- pre_config.ini                                 // PRE 环境配置
|   |-- test_config.ini                                // TEST 环境配置
|-- files/                                             // 文件管理文件夹
|   |-- download/                                      // 文件下载目录
|   |-- pictures/                                      // readme.md 文件的图片
|   |-- upload/                                        // 用例使用到的 file 文件存放路径
|-- reports/                                           // 测试报告目录
|   |-- 2024-10-17 16-01-47/                           // 每次执行的测试报告目录 存放 .html 报告和 .log 文件
|-- templates/                                         // 用例模板和脚本模板
|   |-- template.xlsx                                  // excel 维护的用例模板
|   |-- test_template.py                               // 脚本模板
|-- utils/                                             // 公共方法库
|   |-- asserts.py                                     // 断言方法
|   |-- config_db.py                                   // 数据库链接以及配置
|   |-- config_dingding.py                             // 钉钉通知
|   |-- config_email.py                                // 邮件发送
|   |-- data_decryptor.py                              // 加密数据解密
|   |-- excel_parser.py                                // excel 数据处理
|   |-- log.py                                         // 日志处理
|   |-- report.py                                      // 测试报告处理
|   |-- request_control.py                             // http 请求控制 根据 excel 用例数据封装请求
|   |-- wrappers.py                                    // 各种装饰器方法 减少代码量
|-- read_config.py                                     // 配置文件操作
|-- readme.md                                          // readme 文件
|-- requirements.txt                                   // requirements.txt 安装的包列表
|-- run.ini                                            // 运行配置 是否开启 log
|-- run.py                                             // 测试用例执行入口
|-- run_caselist.ini                                   // 运行的用例列表

依赖库

beautifulsoup4==4.12.3
certifi==2024.8.30
charset-normalizer==3.3.2
et-xmlfile==1.1.0
Faker==30.3.0
HTMLTestRunner-rv==1.1.2
idna==3.10
Jinja2==3.1.4
jsonpath==0.82.2
jsonpath-ng==1.6.1
logzero==1.7.0
MarkupSafe==2.1.5
openpyxl==3.1.5
parameterized==0.9.0
ply==3.11
PyMySQL==1.1.1
pyparsing==3.1.4
python-dateutil==2.9.0.post0
requests==2.32.3
six==1.16.0
soupsieve==2.6
typing_extensions==4.12.2
urllib3==2.2.3

安装教程

  • 安装python 3.12 版本.
  • 安装pycharm 开发工具.
  • 从 gitlab 上通过git pull git@git.edianzuno.cn:qa1/interface-testing.git获取代码
  • 安装依赖包 pip3 install requirements.txt

分支开发

为了避免大家使用同一个主干master分支编写脚本,导致彼此之间相互影响,也方便公共库在维护后大家能及时更新和拉取,各个团队的自动化脚本采用分支开发的模式.一个系统有多个人共同维护的时候,代码都对相同一个自建分支去push和pull代码即可.

master
|-- dev_console_branch      // 华清分支
|-- dev_hr_branch           // 人力分支
|-- dev_crm_branch          // CRM 分支
|-- dev_system_branch       // XX 系统分支

创建自己系统分支的步骤

  • 创建自己分支 git checkout -b dev_console_branch
  • 关联创建远端分支git push -u origin dev_console_branch 这时在 Gitlab 上可以看到自己创建的分支

gitlab.png

  • 使用 git branch 查看自己目前所在分支,如果dev_console_branch是绿色,就代表我们处于当前分支了

image.png

  • 在自己的分支进行脚本开发,开发过程中尽量不要动utils/templates/run.py/read_config.py等公共使用的内容,有需要新增的功能或者发现框架BUG反馈给 @吴越 进行修改

从master合并代码的步骤

当一些公共方法或者框架上有一些 BUG 修复后需要更新的时候,可以从master上来拉取最新的代码

  • 从远端的master拉取代码 git merge origin/master

二、Excel模板

case_name priority method uri content_type request_type request_content assertions cleanup_needed
用例名称 用例优先级 请求方法类型 请求接口的uri 请求的content_type 请求的类型 请求的具体数据 断言 是否需要进行teardown处理
  • case_name:测试用例的名称,例如:TC-01-正常登录/TC-02-用户名错误
  • priority:用例的优先级,选择项:P0~P4 用例执行会根据配置的优先级判断这条 case 是否执行
  • method:请求方法,选择项:GET/POST 等
  • uri:请求的uri,接口路径
  • content_type:接口实际的请求content_type,当请求是上传文件的时候不需要指定content_type

image.png

  • request_type:请求的参数类型.
request_type request_content 说明
params id=1765983 这种请求大多用于GET请求,会讲参数拼到请求路径上
data userName=wuyue&password=11223344aA 这种请求大多用于POST请求,以表单形式进行请求
json {"userName":"吴越","age":18} 用于POST请求,请求参数是JSON
file filename 将要使用的文件放置到cases.upload文件夹下,将文件名字填写到request_content内.
注意:当上传文件的时候content_type是不需要填写的.
path_vars 路径参数 当参数是拼接到路径上的时候,需要对路径参数进行参数化.比如:/get/user/info/{userId}
request_content内使用字典形式组织参数{"userId":123},框架会自动进行替换.
  • request_content:请求的实际数据,如果在F12中我们直接将[载荷—查看源代码]的数据拷贝过来就可以

image.png

针对 request_content 内的内容需要进行变化的,比如一个创建 SKU 的接口要求 SKU 的名称是变化,所以我们就需要确保 skuname 随机性和唯一性,这种情况如何来做?

正常请求:
request_content = {
"sku_name": "联想蓝色笔记本",
"sku_remark":"这个笔记本比较贵",
}

调整后:将sku_name 
request_content = {
"sku_name": "{sku_name}",
"sku_remark":"这个笔记本比较贵",
}

在测试脚本中对{}中的内容进行参数化替换:

image.png

  • assertions:

断言请求采取了使用与assert类相同的方式,虽然有些难以记忆,但框架针对断言部分会相对好实现一些.框架支持了几乎所有的断言方式,包括:assertEqual assertNotEqual assertIn 等常用断言.

断言在 Excel 内编写的时候需要特别注意:断言的书写不区分大小写,但字母顺序不能错,否则无法匹配.我用华清接口验证了一下,大部分接口都是可以使用assertEqual和assertIn完成断言的.

下面给出几个示例:

断言示例 1:返回JSON数据

response_data = {
            "status": 200,
            "message": "operation successful",
            "data": {
                "user": {
                    "id": 12345,
                    "name": "Alice",
                    "roles": ["admin", "user"],
                    "profile": {
                        "age": 30,
                        "hobbies": ["reading", "cycling"],
                        "location": {"country": "USA", "state": "California"}
                    },
                    "tags": {"tag1": "A", "tag2": "B", "tag3": "C"},
                    "balance": 2000.00000001,
                    "threshold": 1000.0,
                    "list_value": [5, 10, 15],
                    "tuple_value": (1, 2, 3),
                    "set_value": {1, 2, 3},
                    "history": None,
                },
                "success": True,
            }
        }

        asserts = """
                assertEqual:$.status=200
                assertEqual:$.data.user.roles[0]=admin  
                assertNotEqual:$.message=failed  
                assertSequenceEqual:$.data.user.roles=['admin', 'user']
                assertListEqual:$.data.user.roles=['admin', 'user']  
                assertTupleEqual:$.data.user.tuple_value=(1, 2, 3)  
                assertSetEqual:$.data.user.set_value={1, 2, 3}  
                assertIn:operation successful  
                assertNotIn:error  
                assertDictEqual:$.data.user.tags={'tag1': 'A', 'tag2': 'B', 'tag3': 'C'}  
                assertCountEqual:$.data.user.tags={'tag1': 'A', 'tag2': 'B', 'tag3': 'C'}  
                assertMultiLineEqual:$.data.user.name=Alice  
                assertLess:$.data.user.profile.age=40 
                assertLessEqual:$.data.user.profile.age=30  
                assertGreater:$.data.user.profile.age=20
                assertGreaterEqual:$.data.user.profile.age=30  
                assertIsNone:$.data.user.history  
                assertIsNotNone:$.data.user.id  
                assertIsInstance:$.data.user.id=int  
                assertNotIsInstance:$.data.user.id=str
                assertIs:$.data.success=True
                assertIsNot:$.data.success=False
                assertAlmostEqual:$.data.user.balance=2000.0
                assertNotAlmostEqual:$.data.user.balance=2000.0
                """

断言示例 2:返回文本数据

        response_data = """
            <link rel="canonical" href="https://blog.csdn.net/care5752/article/details/104476355">
        """

        asserts = """\
        assertIn:104476355
        """

断言示例 3:从返回list中断言多条数据

# 应用场景:如果一个查询接口是通过状态或者类型来查询结果的,需要断言所有查询结果只能包括这个状态或者类型,即可通过这种方式进行断言.
response_data = {
        "code":0
        "data":
            "items":[
                     {"status":"已完成"},
                     {"status":"已完成"},
               ]
				}
asserts = """
assertEqual:$.data.items[*]=已完成 #上述案例会断言成功
"""
response_data = {
        "code":0
        "data":
            "items":[
                     {"status":"已完成"},
                     {"status":"未完成"},
               ]
				}
asserts = """
assertEqual:$.data.items[*]=已完成 #上述断言会失败
"""
  • cleanup_needed:是否需要进行teardown数据清理,但为True的时候需要进行清理,完成自己的清理代码image.png

三、接口用例的管理

创建用例模板

  • 在 PyCharm 中,点击 File > Settings (或 Preferences on macOS)
  • 在左侧导航栏中,选择 Editor > File and Code Templates
  • File and Code Templates 页面,点击 + 按钮以创建一个新的模板
  • 为你的模板取一个名字,比如 TestTemplate
  • 在右侧的模板编辑框中,将 /templates/test_templates.txt 文件内容拷贝到模板内:
    • 将class Template 替换成 class ${ClassName}
    • 将test_template 替换成 class test_${MethodName}
  • 应用并保存,后续在创建文件的时候可以使用这个模板,会询问替换${}内的值
import unittest
import os
import inspect
from parameterized import parameterized
import read_config as readConfig
from utils import excel_parser, request_control as rc
from common import common as bizCom
from utils.log import MyLog
from utils.wrappers import *
from utils.asserts import AssertionHandler
from faker import Faker


# 获取当前脚本的文件名(不带扩展名),脚本名称和Excel sheet页名称保持一致,否则找不到.
sheet_name = os.path.splitext(os.path.basename(inspect.getfile(inspect.currentframe())))[0].split('_', 1)[1]

# 获取当前脚本所在的目录名
script_dir = os.path.basename(os.path.dirname(inspect.getfile(inspect.currentframe())))
# 获取当前脚本所在文件夹下的测试用例数据文件,文件要与所在模块同名
file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"{script_dir}.xlsx")
# 获取测试用例数据
case_xls = excel_parser.get_xls(file_path, sheet_name)

config = readConfig.ReadConfig()

# 获取优先级过滤器并转换为大写
priority_filter = config.get_priority_filter()


class ${ClassName}(unittest.TestCase):

    def setUp(self):
        """
        测试前的准备工作
        """
        self.log = MyLog.get_log()
        self.logger = self.log.get_logger()
        self.cookies = bizCom.get_cookie()
        self.info = None  # 初始化信息
        self.response = None  # 初始化响应
        self.fake = Faker(locale='zh_CN')
        self.case_name = self.__str__().split('(')[1].split(')')[0]
        self.params = {}
        self.handler = AssertionHandler(self)

    @parameterized.expand(case_xls)
    @filter(priority_filter)
    @retry()
    @delay()
    def test_${MethodName}(self, case_name, priority, method, uri,
                                  content_type, request_type, request_content, assertions, cleanup_needed):
        """
        case描述
        """
        self.start_time = time.time()
        self.assertions = assertions
        self.cleanup_needed = cleanup_needed

        # 第一步:设置请求头.因为请求头各个系统存在特殊的情况,比如token、cookie,所以需要单独在case内设置.
        headers = {"Content-Type": content_type, "Cookie": self.cookies}

        # 第二步:对请求内容进行替换,比如:时间、姓名、手机号等,确保请求是不重复的
        self.params = {"sku_name": self.fake.word()}
        request_content = rc.replace_placeholder(request_content, **self.params)

        # 第三步:发送请求
        self.response = rc.RequestControl(method, uri, headers, request_type, request_content).send_request()

        # 第四步:检查结果
        self.checkResult()

    @skip_teardown_if_skipped
    def tearDown(self):
        """
        测试后的清理工作
        """
        if self.cleanup_needed:
            pass
        # 第五步:记录执行结果
        self.end_time = time.time()
        self.log.build_case_line(self.case_name, self.handler.assert_status, self.start_time - self.end_time)

    @decrypt_response
    def checkResult(self):
        """
        检查测试结果
        self.info 在装饰器decrypt_response内已经进行了赋值并且进行了解密动作,后面self.info可以直接进行使用.
        """
        print(self.info)
        self.handler.fun_assert(self.info, self.assertions)


if __name__ == "__main__":
    unittest.main()



使用用例模板

  • 创建文件的时候选择 new-TestTemplates
  • 选择模板后会弹出如下页面

.png

  • 点击 OK,生成脚本文件

场景用例的编写

测试框架不支持直接使用 Excel 组织单接口进行接口串联组成自动化测试场景,主要考虑到使用 Excel 管理场景用例复杂度高并且灵活度比较差,不利于脚本维护.场景用例的组织和实现如下案例.

  • 在 api 文件夹内按照功能模块对接口进行管理,如华清的[发货管理]模块
deliver_management # 发货管理
	deliver_express.py # 快递查询
	saas_order.py # 订单管理
	saas_work_order_job.py # 发货任务
  • 将页面接口抽象成类 集成base_class并将接口封装成方法,按需进行数据处理和返回

场景串联-接口封装

  • 然后在cases目录下按照unittest的形式去组织场景用例 实现自己的场景串联

场景串联

数据构造平台的使用

在执行自动化测试中有很多场景需要进行前置的数据准备,可以将数据构造脚本封装到数据构造平台,来简化自动化场景.数据构造平台的调用方式如下:

用例的组织

测试用例的执行管理是在 run_caselist.ini 文件

  • [admin] 为模块名
  • case 名称 为 test_xxx 或者 test_xxx.py 均可
  • 当 case 前被标记成 ; 的时候代表不执行这条 case
  • 如果文件为空,默认执行所有用例
[admin]
;test_password.py
[procurement]
test_queryProcureApplyList_list
[recycle]
test_doExpressNoticeList
test_express_list
test_modifyNoticeExpressRemark
test_toIndex
[shipment]
test_edit
test_express_query
test_getSystemRemarkByOrderId
test_getUserMenusByParentId
test_import
test_modifyDetailReceiver
test_modifySystemRemark
test_orderDetailIndex
test_query
test_queryAll
test_queryProvinceAll
test_saasFailureDevice_adjust
test_saasFailureDevice_getList
test_saasFailureDevice_recover
test_select
test_view
[system]
test_importFromFile
test_select

四、用例的执行

用例的执行入口在run.py,在执行 case 之前需要配置好环境配置和 run.ini 配置

环境配置

x[EMAIL]
mail_host = smtp.exmail.qq.com
mail_user = wuyue1@edianyun.com
mail_pass = xxxxxxxx # 替换成自己的密码
mail_port = 465
sender = wuyue1@edianyun.com
receiver = wuyue1@edianyun.com  # 邮件接收方,多人用 email1,email2 方式隔开
subject = 测试邮件
content = 测试邮件内容
on_off = off  # 是否发送邮件开关 on 或者 off

[DINGDING]
on_off = off  # 是否发送钉钉消息开关  on 或者 off
callback_url = https://oapi.dingtalk.com/robot/send?access_token=9635733cefcc9133a22c59f1d9ca4f33dd1ca52af13ca261d44c89bcd96126dd   # 钉钉机器人回调地址 安全认证写【测试】

[HTTP]
scheme = https
;baseurl = test-hr.edianzu.cn
baseurl = test-console.edianzu.cn    # 系统域名
port = 443
timeout = 10.0

[DATABASE]
host = test-database.edianzu.cn
username = db_public_w
password = Mysql_123
port = 3306

[ADMIN]
scheme = https
baseurl = test-admin.edianzu.cn   # admin 系统域名
username = wuyue    # admin 系统用户名
password = 11223344aA   # admin 系统密码


[DECORATOR]
decrypt_url = https://test-data-security.edianzu.cn/data/decry    # 加解密请求域名

运行配置

[ENV]
env = test    # 读取哪个环境配置文件

# env = pre
# env = prod
[PRIORITY]
priority =    # 要执行用例的优先级 不写默认全部执行.P0,P1,P2,P3,P4 逗号分割

[LOG]
on_off = on    # 是否打印 log 文件

五、测试报告

测试报告会在 reports 目路下根据日期生成一个 测试报告目录.如果打开邮件发送测试报告,聚合测试报告会发送到 receiver

image.png

image.png

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors