接口自动化测试(python+pytest+requests)

一、选取自动化测试用例

  1. 优先级高:先实现业务流程用例、后实现单接口用例
  2. 功能较稳定的接口优先开展测试用例脚本的实现

二、搭建自动化测试环境

  1. 核心技术:编程语言:python;测试框架:pytest;接口请求:requests
  2. 安装/验证requests:命令行终端分别输入 pip install requests / pip show requests

三、搭建自动化测试框架

1. 接口自动化框架
2. 接口自动化框架设计思路
3. 搭建基础框架-定义项目目录结构

四、代码实现自动化

1、业务接口测试
1. Requests库
  • Requests库是python中的"浏览器",基于urllib的HTTP库

  • 安装/验证requests:命令行终端分别输入 pip install requests / pip show requests

  • 操作步骤:导包、发送接口请求、查看响应结果

  • Requests发送请求

    requests.请求方法(url, params=None, data=None, json=None, headers=None, files=None)

    说明:

    • 常见的请求方法:get/post/put/delete
    • url:请求的url地址 (字符串)
    • params:请求的查询参数 (字典)
    • data:请求体为form表单的参数 (字典)
    • json:请求体为json的参数 (字典)
    • headers:请求头参数 (字典)
    • filse:上传文件,类型为multipart/form-data (字典) filse={"参数/file",上传文件二进制数据}
      详细参考:Python中requests库_python requests-CSDN博客
  • Requests查看响应

    import requests
    url = 'http://kdtx-test.itheima.net/api/captchaImage'
    response = requests.get(url)
    print(response.json())

2. 接口对象封装的核心思想 (代码分层思想)
- 接口对象层(重点关注接口封装调用)

登录接口:

# 接口封装层,重点是依据接口文档封装接口信息,目录:api/login.py
# 需要使用的测试数据是从测试用例传递的,接口方法被调用是需要返回对应的响应结果

import requests


class LoginAPI:

    url_verify = 'http://kdtx-test.itheima.net/api/captchaImage'

    url_login = "http://kdtx-test.itheima.net/api/login"

    # 初始化
    def __init__(self):
        pass

    # 定义获取验证码方法
    def get_verify_code(self):
        return requests.get(url=self.url_verify)

    # 定义登录方法
    def login(self, login_param: dict):
        return requests.post(url=self.url_login, json=login_param)

课程接口:

# 接口封装层,重点是依据接口文档封装接口信息,目录:api/course.py
# 需要使用的测试数据是从测试用例传递的,接口方法被调用是需要返回对应的响应结果

import requests


class CourseAPI:
    # 初始化
    def __init__(self, url_add_course):
        self.url_add_course = url_add_course

    # 定义添加课程方法  token需登录后才拿到值
    def add_course(self, request_param, token):
        # 请求头携带鉴权信息 "Content-Type": "application/json"因为参数已经传的是json 会默认识别格式,所以不用另外传
        headers = {
            "Authorization": token
        }
        return requests.post(url=self.url_add_course, json=request_param, headers=headers)

合同接口(文件上传)

# 接口封装层,重点是依据接口文档封装接口信息,目录:api/contract.py
# 需要使用的测试数据是从测试用例传递的,接口方法被调用是需要返回对应的响应结果
import requests


class ContractAPI:
    # 初始化
    def __init__(self, url_upload):
        self.url_upload = url_upload

    # 合同上传接口
    def upload_contract(self, contract_data, token):
        headers = {
            "Authorization": token
        }
        files = {
            "file": contract_data
        }
        return requests.post(url=self.url_upload, files=files, headers=headers)
- 测试脚本层(重点关注测试数据准备、断言及业务处理等)

模拟业务流程(验证码-登录-新增课程-上传合同)涉及接口的自动化测试脚本

注意:需安装pytest且不能有__init__方法 才能识别是测试类

测试类规则:

  • 1.模块名必须以test_开头或者_test结尾

  • 2.测试类必须以Test开头,并且不能有init方法

  • 3.测试用例必须以test开头
    测试类可以看方法的详细执行情况

    测试脚本层 目录 script/test03_contract_business.py

    验证码-登录-上传合同-新增合同 合同新增业务流程涉及接口的自动化测试脚本

    导包 自定义的先from到文件下 在import对应的类

    from api.login import LoginAPI
    from api.course import CourseAPI
    from api.contract import ContractAPI

    创建测试类

    class TestContractBusiness:
    # 链接来自b站黑马的视频
    __url_add_course = "http://kdtx-test.itheima.net/api/clues/course"

      __url_upload = "http://kdtx-test.itheima.net/api/common/upload"
    
      __url_add_contract = "http://kdtx-test.itheima.net/api/contract"
    
      token = None
    
      # # 初始化 测试类不能有该方法 初始的逻辑放在前置处理方法里
      # def __init__(self):
      #     self.login_api = LoginAPI()
      #     self.course_api = CourseAPI(self.__url_add_course)
      #     self.contract_api = ContractAPI(self.__url_upload)
    
      # 前置处理
      def setup(self):
          # 实例化接口对象
          self.login_api = LoginAPI(self.__url_verify, self.__url_login)
          self.course_api = CourseAPI(self.__url_add_course)
          self.contract_api = ContractAPI(self.__url_upload, self.__url_add_contract)
    
      # 后置处理
      def teardown(self):
          pass
    
      # 登录成功
      def test01_login_success(self):
          # 获取验证码
          res_v = self.login_api.get_verify_code()
          print(res_v.json())
          print(res_v.json().get("uuid"))
    
          # 登录
          login_param = {
              "username": "admin",
              "password": "HM_2023_test",
              "code": 2,
              "uuid": res_v.json().get("uuid")
          }
          res_l = self.login_api.login(login_param=login_param)
          # 提取登录成功之后的token数据并保存在类的属性中
          # 相当于java中类的静态属性赋值,直接通过类去给属性赋值,通过类去获取属性值
          TestContractBusiness.token = res_l.json().get("token")
          print(res_l.json())
          # return res_l
    
      # 添加课程
      def test02_add_course(self):
          # # 先登录拿token
          # res_login_success = self.test01_login_success()
          # print(res_login_success.json())
          # token = res_login_success.json().get("token")
    
          # ====== token 直接取类的属性  调登录方法时赋了值
          add_course_param = {
              "name": "好的课",
              "subject": "5",
              "price": 899,
              "applicablePerson": "2",
              "info": "测试测试"
          }
          res_c = self.course_api.add_course(request_param=add_course_param, token=TestContractBusiness.token)
          print(res_c.json())
          # return res_c
    
      # 上传合同
      def test03_upload_contract(self):
          # 读取合同文件二进制数据 第二个参数 rb
          contract_data = open("../data/test.pdf", "rb")
          res = self.contract_api.upload_contract(contract_data=contract_data, token=TestContractBusiness.token)
          print(res.json())
          # return res
    
      # 新增合同
      def test04_add_contract(self):
          # contractNo 合同编号 数据唯一
          add_contract_param = {
              "name": "test",
              "phone": "12345678901",
              "contractNo": "HT2023597",
              "subject": "6",
              "courseId": "468",
              "channel": "0",
              "activityId": 77,
              "fileName": "xxx"
          }
          res = self.contract_api.add_contract(request_param=add_contract_param, token=TestContractBusiness.token)
          print(res.json())
          # return res
    

结果:

2、单接口测试
  1. 登录接口单接口测试
    断言:按照测试用例预期结果进行断言
    相等断言:assert 预期结果 == 实际结果
    包含断言:assert 预期结果 in 实际结果

    登录单接口测试:测试登录接口的测试用例(每个测试用例一个测试方法,使用断言的方式验证预期结果)

    from api.login import LoginAPI

    class TestLoginAPI:
    # 前置处理
    # uuid = None

     uuid = None
    
     def setup(self):
         # 实例化接口类
         self.login_api = LoginAPI()
         # 获取验证码
         res = self.login_api.get_verify_code()
         # 提取验证接口返回的uuid参数值  类的属性保存
         print("uuid:", res.json().get("uuid"))
         TestLoginAPI.uuid = res.json().get("uuid")
    
     # 后置处理
     def teardown(self):
         pass
    
     # 用例一:登录成功
     def test01_success(self):
         login_param = {
             "username": "admin",
             "password": "HM_2023_test",
             "code": 2,
             "uuid": TestLoginAPI.uuid
         }
         response = self.login_api.login(login_param)
         print(response.json())
         # 断言测试用例的预期结果
         # 断言响应状态码
         assert 200 == response.status_code
         # 断言响应数据包含'成功'
         assert '成功' in response.text
         # 断言响应jsons数据中的code值
         assert 200 == response.json().get("code")
    
     # 用例二:登录失败(用户名为空)
     def test02_without_username(self):
         login_param = {
             "username": "",
             "password": "HM_2023_test",
             "code": 2,
             "uuid": TestLoginAPI.uuid
         }
         response = self.login_api.login(login_param)
         print(response.json())
         # 断言测试用例的预期结果
         # 断言响应状态码
         assert 200 == response.status_code
         # 断言响应数据包含'成功'
         assert '错误' in response.text
         # 断言响应jsons数据中的code值
         assert 500 == response.json().get("code")
    
     # 用例三:登录失败(用户不存在)
     def test03_username_not_exist(self):
         login_param = {
             "username": "admin123",
             "password": "HM_2023_test",
             "code": 2,
             "uuid": TestLoginAPI.uuid
         }
         response = self.login_api.login(login_param)
         print(response.json())
         # 断言测试用例的预期结果
         # 断言响应状态码
         assert 200 == response.status_code
         # 断言响应数据包含'成功'
         assert '错误' in response.text
         # 断言响应jsons数据中的code值
         assert 500 == response.json().get("code")
    
  2. 数据驱动
    以测试数据驱动脚本执行,维护焦点从脚本转向测试数据的一种自动化测试设计模式。
    应对场景:一个接口有多条测试用例,每个测试用例写一个测试方法去断言比较繁琐

  • json文件测试数据

    [
    {
    "username": "admin",
    "password": "HM_2023_test",
    "status": 200,
    "msg": "成功",
    "code": 200
    },
    {
    "username": "",
    "password": "HM_2023_test",
    "status": 200,
    "msg": "错误",
    "code": 500
    },
    {
    "username": "admin123",
    "password": "HM_2023_test",
    "status": 200,
    "msg": "错误",
    "code": 500
    }
    ]

  • 代码实现

    数据驱动单接口测试

    测试数据 含入参以及预期响应 注解最终入参的数据类型为:列表[字典]

    import json

    import pytest

    from api.login import LoginAPI

    测试数据 后改成直接从json文件读取 最终格式保持为 列表[字典]

    login_data_test = [

    ("admin", "HM_2023_test", 200, "成功", 200),

    ("", "HM_2023_test", 200, "错误", 500),

    ("admin123", "HM_2023_test", 200, "错误", 500),

    ]

    json文件读取测试数据

    def build_login_data(filename):
    # 数据格式[(),()]
    result = []
    with open(filename, "r", encoding='UTF-8') as data:
    login_params = json.loads(data.read())
    for param in login_params:
    # 转换数据格式 [{},{}] -> [(),()]
    dict_data = (
    param.get("username"),
    param.get("password"),
    param.get("status"),
    param.get("msg"),
    param.get("code"),
    )
    result.append(dict_data)
    print("result", result)
    return result

    class TestLoginAPI:
    # 前置处理
    uuid = None

      def setup(self):
          # 实例化接口类
          self.login_api = LoginAPI()
          # 获取验证码
          res = self.login_api.get_verify_code()
          # 提取验证接口返回的uuid参数值  类的属性保存
          print("uuid:", res.json().get("uuid"))
          TestLoginAPI.uuid = res.json().get("uuid")
    
      # 后置处理
      def teardown(self):
          pass
    
      # 数据驱动登录接口测试
      @pytest.mark.parametrize("username, password, status, msg, code", build_login_data("../data/login.json"))
      def test_login(self, username, password, status, msg, code):
          login_param = {
              "username": username,
              "password": password,
              "code": 2,
              "uuid": TestLoginAPI.uuid
          }
          response = self.login_api.login(login_param)
          print(response.json())
          # 断言测试用例的预期结果
          # 断言响应状态码
          assert status == response.status_code
          # 断言响应数据包含'成功'
          assert msg in response.text
          # 断言响应jsons数据中的code值
          assert code == response.json().get("code")
    
  • 结论

  1. config.py配置文件
    存放被测项目基本信息,如URL地址等

  • 配置环境的域名和项目的根路径以及项目中使用的公共数据,代码中获取如下:

    import config

    print(config.BASE_URL)
    print(config.BASE_PATH)

五、输出Allure测试报告

Allure:支持多种开发语言,如java、python等

帮助文档:Allure Report Docs --- Introduction

操作步骤:

  1. 生成测试结果文件(json文件)
  • 安装 pip install allure-pytest

  • 在pytest.ini件中的命令行参数加上如下代码设定:

    pytest配置文件 按照实际目录调整通配符 pytest.ini

    [pytest]

    结果文件在哪个目录下

    addopts=-s --alluredir report

    测试文件所在位置

    testpaths=./script

    在测试文件目录下哪些文件/类/方法需要被执行 通配符

    python_files=test*.py
    python_classes=Test*
    python_functions =test*

  • 编写好测试脚本后,在命令行行中运行pytest(直接在终端输入pytest即可)

  • 程序运行结束后,会在项目的report目录中生成一些json文件

  1. 使用allure命令生成在线报告

    Releases · allure-framework/allure2 · GitHub

    终端命令行运行:allure serve report
    报错:修改环境变量之后需要重新启动pycharm

    allure : 无法将"allure"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
    所在位置 行:1 字符: 1

    • allure serve report
    • 复制代码
        + CategoryInfo          : ObjectNotFound: (allure:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

这是我整理的**《2024最新Python自动化测试全套教程》** ,以及配套的接口文档/项目实战**【网盘资源】** ,需要的朋友可以下方视频的置顶评论获取。肯定会给你带来帮助和方向。

【已更新】B站讲的最详细的Python接口自动化测试实战教程全集(实战最新版)

相关推荐
艾派森2 分钟前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
远望清一色10 分钟前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself20 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
小码的头发丝、28 分钟前
Django中ListView 和 DetailView类的区别
数据库·python·django
XiaoLeisj31 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man35 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*36 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家37 分钟前
go语言中package详解
开发语言·golang·xcode
llllinuuu37 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s38 分钟前
Golang--协程和管道
开发语言·后端·golang