Httprunner V3.x接口自动化测试

介绍

HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。

特点

  • 继承了Requests的全部特性,可轻松实现 HTTP(S) 的各种测试需求
  • 使用YAML或JSON格式定义测试用例,并使用Pytest以简洁优雅的方式运行用例
  • 支持使用HAR实现接口录制并生成测试用例
  • 支持variables/ extract/ validate/hooks机制,以应对非常复杂的测试场景
  • 使用debugtalk.py插件自定义函数,可以在测试用例的任何部分调用
  • 使用Jmespath,更加方便对返回的json进行校验
  • 通过Pytest的强大插件生态补充了httprunner的功能
  • 使用Allure,让测试报告更加美观,可读性更强
  • 通过与locust的结合,可以很方便利用httprunner进行接口性能测试
  • 支持CLI命令,更可与持续集成工具(CI/CD)完美结合,如Jenkins

安装

本文使用的环境为python3.7+httprunner3.1.4

pip install  httprunner==3.1.4 

查看版本

httprunner -V # hrun -V 
3.1.4

可能出现的错误和解决办法

pip install  markupsafe==2.0.1 pydantic==1.8.2 click==8.0.2
# 1.ImportError: cannot import name 'soft_unicode' from 'markupsafe'
pip install markupsafe==2.0.1
# 2. parameters的参数化数据 每次只运行第一组的参数化数据
pip install pydantic==1.8.2
# 3.ImportError: cannot import name ‘_unicodefun’ from ‘click’
pip install  click==8.0.2

创建项目

httprunner startproject httprunner_demo

用pycharm打开项目,创建目录api,data,testsuites,创建文件main.py.

文件夹及作用

  • api 存放接口定义yml文件
  • data 存放测试数据
  • har 存放har文件,使用抓包工具导出
  • reports 存放测试报告
  • testcases 存放测试用例yml文件
  • testsuites 存放测试套件yml文件
  • .env 存放环境变量,如base_url
  • debugtalk.py 写python脚本,在yml文件中调用

api接口

本文使用flask定义测试接口,用flasgger生成swagger接口文档,创建api_server.py文件。

安装环境:

pip install flask Flask-JWT-Extended
pip install flasgger
# 5000端口被占用时
netstat -ano | find "5000"
taskkill /f /im port

运行api_server,访问 http://localhost:5000/apidocs/

import os

from flask import Flask, request, jsonify
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from flasgger import Swagger, swag_from
from werkzeug.utils import secure_filename

app = Flask(__name__)
swagger = Swagger(app)

app.config['JWT_SECRET_KEY'] = '123456'  # 在实际应用中请更改为安全的密钥
jwt = JWTManager(app)

UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# 用户信息存储,模拟数据库
users = {
    'test@qq.com': {'password': '123456', 'name': 'test'}
}


def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/login', methods=['POST'])
def login():
    """
    User Login
    ---
    parameters:
      - name: email
        in: formData
        type: string
        required: true
        description: The user's email address
      - name: password
        in: formData
        type: string
        required: true
        description: The user's password
    responses:
      200:
        description: Login successful
      401:
        description: Invalid credentials
    """
    data = request.form
    email = data.get('email')
    password = data.get('password')

    if email in users and users[email]['password'] == password:
        # Create an access token
        access_token = create_access_token(identity=email)
        return jsonify(
            {'code': 200, 'data': {'access_token': access_token, 'message': 'Login successful'}, 'message': 'success'})
    else:
        return jsonify({'code': 401, 'data': {}, 'message': 'Invalid credentials'})


@app.route('/change_name', methods=['POST'])
@jwt_required()
@swag_from({
    'security': [{'JWT': []}],
    'parameters': [
        {
            'name': 'Authorization',
            'in': 'header',
            'type': 'string',
            'required': True,
            'default': 'Bearer ',
            'description': 'JWT Authorization header',
        },
        {
            'name': 'new_name',
            'in': 'formData',
            'type': 'string',
            'required': True,
            'description': 'The new name for the user',
        },
    ],
    'responses': {
        200: {'description': 'Name changed successfully'},
        401: {'description': 'Unauthorized'},
        404: {'description': 'User not found'},
    }
})
def change_name():
    current_user = get_jwt_identity()
    data = request.form
    new_name = data.get('new_name')

    if current_user in users:
        users[current_user]['name'] = new_name
        return jsonify({'code': 200, 'data': {'message': 'Name changed successfully'}, 'message': 'success'})
    else:
        return jsonify({'code': 404, 'data': {}, 'message': 'User not found'})


@app.route('/upload_file', methods=['POST'])
@jwt_required()
def upload_file():
    """
    Upload User File
    ---
    security:
      - JWT: []
    parameters:
      - name: Authorization
        in: header
        type: string
        required: true
        default: 'Bearer '
        description: JWT Authorization header
      - name: file
        in: formData
        type: file
        required: true
        description: The file to upload
    responses:
      200:
        description: File uploaded successfully
      400:
        description: No file provided or invalid file format
      401:
        description: Unauthorized
      404:
        description: User not found
    """
    current_user = get_jwt_identity()

    if 'file' not in request.files:
        return jsonify({'code': 400, 'data': {}, 'message': 'No file provided'})

    file = request.files['file']

    if file.filename == '':
        return jsonify({'code': 400, 'data': {}, 'message': 'No file provided'})

    if file and allowed_file(file.filename):
        if current_user in users:
            # Save the uploaded file
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

            # You can perform additional processing here (e.g., save file path to user profile)

            return jsonify({'code': 200, 'data': {'message': 'File uploaded successfully', 'filename': filename},
                            'message': 'success'})
        else:
            return jsonify({'code': 404, 'data': {}, 'message': 'User not found'})
    else:
        return jsonify({'code': 400, 'data': {}, 'message': 'No file provided or invalid file format'})


if __name__ == '__main__':
    app.run(debug=True, port=5000, load_dotenv=False)

接口定义yml文件

  1. 使用网页或postman发送登录请求
  2. 使用抓包工具fiddler或charles导出har文件(v1.1)存放har目录
  3. 使用命令har2case -2y har/login.har 转化成yml文件,存放api目录
  4. 修改login.yml,将teststeps的所有的变量存放config
  5. 在.env文件中添加base_url=http://127.0.0.1:5000
config:
  name: testcase description
  variables:
    email: test@qq.com
    password: '123456'
  verify: false
  base_url: ${ENV(base_url)}
teststeps:
  - name: /login
    request:
      data:
        email: $email
        password: $password
      headers:
        Accept-Encoding: gzip, deflate, br
        Accept-Language: zh-CN,zh;q=0.9
        Connection: keep-alive
        Content-Length: '35'
        Content-Type: application/x-www-form-urlencoded
        Host: 127.0.0.1:5000
        Origin: http://127.0.0.1:5000
        Referer: http://127.0.0.1:5000/apidocs/
        Sec-Fetch-Dest: empty
        Sec-Fetch-Mode: cors
        Sec-Fetch-Site: same-origin
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
          (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
        accept: application/json
        sec-ch-ua: '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"'
        sec-ch-ua-mobile: ?0
        sec-ch-ua-platform: '"Windows"'
      method: POST
      url: /login
    extract:
      - access_token: body.data.access_token
    validate:
      - eq:
          - status_code
          - 200
      - eq:
          - headers.Content-Type
          - application/json
      - eq:
          - body.code
          - 200
      - eq:
          - body.message
          - success
  1. 使用extract关键字提取access_token,用来接口关联
  2. 使用$变量名调用config下的全局变量,${ENV(变量名)}调用环境变量,${函数名()}调用debugtalk.py中定义的函数。

测试用例yml文件

login_case.yml,测试步骤里面调用api

config:
  name: testcase description
teststeps:
  - name: /login
    api: api/login.yml

在main.py文件添加,运行测试用例,生成测试报告

import os

if __name__ == '__main__':
    os.system('hrun testcases/login_case.yml --html=reports/report.html')
接口关联

通过extract关键字提取,export关键字导出

  1. 调用change_name接口,按照同样的方式生成change_name.yml
config:
  name: testcase description
  variables:
    authorization: ${get_authorization($access_token)}
    new_name: 'test123'
  verify: false
  base_url: ${ENV(base_url)}
teststeps:
  - name: /change_name
    request:
      data:
        new_name: $new_name
      headers:
        Accept-Encoding: gzip, deflate, br
        Accept-Language: zh-CN,zh;q=0.9
        Authorization: $authorization
        Connection: keep-alive
        Content-Length: '16'
        Content-Type: application/x-www-form-urlencoded
        Host: 127.0.0.1:5000
        Origin: http://127.0.0.1:5000
        Referer: http://127.0.0.1:5000/apidocs/
        Sec-Fetch-Dest: empty
        Sec-Fetch-Mode: cors
        Sec-Fetch-Site: same-origin
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
          (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
        accept: application/json
        sec-ch-ua: '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"'
        sec-ch-ua-mobile: ?0
        sec-ch-ua-platform: '"Windows"'
      method: POST
      url: /change_name
    validate:
      - eq:
          - status_code
          - 200
      - eq:
          - headers.Content-Type
          - application/json
      - eq:
          - body.code
          - 200
      - eq:
          - body.message
          - success

在debugtalk.py中添加get_authorization

def get_authorization(access_token):
    return 'Bearer ' + access_token

测试用例chage_name_case.yml

config:
  name: testcase description
teststeps:
  - name: /login
    api: api/login.yml
    export:
      - access_token
  - name: /change_name
    api: api/change_name.yml

提取参数需要使用export导出,下一个步骤才能使用

数据驱动

使用parameters关键字,写在测试用例的config下

方式一

直接写到yaml文件,参数名用"-"连接

config:
  name: testcase description
  parameters:
    email-password-body_code:
      - [ "test@qq.com","123456",200 ]
      - [ "","123456",401 ]
      - [ "test@qq.com","",401 ]
teststeps:
  - name: /login
    api: api/login.yml

使用数据驱动需要修改对应api的断言,常用断言:eq,str_eq,contains

    validate:
      - eq:
          - status_code
          - 200
      - str_eq:
          - body.code
          - $body_code
方式二

使用csv文件

config:
  name: testcase description
  parameters:
    email-password-body_code: ${P(data/login_data.csv)}
teststeps:
  - name: /login
    api: api/login.yml

login_data.csv

email,password,body_code
"test@qq.com","123456",200
"","123456",401
"test@qq.com","",401
方式三

使用debugtalk.py文件,需要返回列表字典格式数据:

config:
  name: testcase description
  parameters:
    email-password-body_code: ${login_data()}
teststeps:
  - name: /login
    api: api/login.yml

login_data

def login_data():
    data = [
        {"email": "test@qq.com", "password": "123456", "body_code": 200},
        {"email": "", "password": "123456", "body_code": 401},
        {"email": "test@qq.com", "password": "", "body_code": 401}
    ]
    return data
文件上传

文件上传需要安装插件

pip install requests_toolbelt filetype
pip install "httprunner[upload]"

api,使用upload关键字上传,key名称为file,变量名尽量不要和key名一样,没有特殊符号等

config:
  name: testcase description
  variables:
    file_path: data/test.jpg
    authorization: ${get_authorization($access_token)}
  verify: false
teststeps:
  - name: /upload_file
    request:
      upload:
        file: $file_path
      headers:
        Authorization: $authorization
      method: POST
      url: http://127.0.0.1:5000/upload_file
    validate:
      - eq:
          - status_code
          - 200

case

config:
  name: testcase description
teststeps:
  - name: /login
    api: api/login.yml
    export:
      - access_token
  - name: /upload_file
    api: api/upload_file.yml
测试套件

运行指定测试用例集合,注意testcases

test_suite.yml

config:
  name: 测试套件
testcases:
  - name: /login
    testcase: testcases/login_case.yml
  - name: /change_name
    testcase: testcases/change_name_case.yml
  - name: /upload_file
    testcase: testcases/upload_file_case.yml

执行测试套件

os.system('hrun testsuites/test_suite.yml --html=reports/report.html')

Allure测试报告

  1. 下载allure压缩包,解压后将bin目录添加到系统环境变量path

    官网下载:https://github.com/allure-framework/allure2/releases

    验证 allure --version,装完需重启pycharm

  2. pip install allure-pytest

  3. 执行测试,生成allue报告

     os.system('hrun testsuites/test_suite.yml --alluredir=reports/temp --clean-alluredir')
     os.system('allure generate reports/temp -o reports/allure --clean')
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值