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

发布时间 2023-10-17 00:21:53作者: dream_of_freedom

一、选取自动化测试用例

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

二、搭建自动化测试环境

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

三、搭建自动化测试框架

1. 接口自动化框架

image

2. 接口自动化框架设计思路

image

3. 搭建基础框架-定义项目目录结构

image
image

四、代码实现自动化

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",上传文件二进制数据}
      详细参考:https://blog.csdn.net/qq_62789540/article/details/123179948
  • Requests查看响应
    image

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

结果:
image

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")
  1. 数据驱动
    以测试数据驱动脚本执行,维护焦点从脚本转向测试数据的一种自动化测试设计模式。
    应对场景:一个接口有多条测试用例,每个测试用例写一个测试方法去断言比较繁琐
    image
  • 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")
  • 结论
    image
  1. config.py配置文件
    存放被测项目基本信息,如URL地址等
    image
  • 配置环境的域名和项目的根路径以及项目中使用的公共数据,代码中获取如下:
import config

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

五、输出Allure测试报告

Allure:支持多种开发语言,如java、python等
帮助文档:https://docs.qameta.io/allure
操作步骤:

  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命令生成在线报告
    image
    https://github.com/allure-framework/allure2/releases
    image
    终端命令行运行:allure serve report
    报错:修改环境变量之后需要重新启动pycharm
allure : 无法将“allure”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
所在位置 行:1 字符: 1
+ allure serve report
+ ~~~~~~
    + CategoryInfo          : ObjectNotFound: (allure:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException