pytest + YAML + requests`简单实例化

文章目录

我们直接做一个能跑起来的 pytest + YAML + requests 登录接口小项目

这次你不是只学知识点,而是把这些点串成一套完整流程:

  • requests 负责发接口请求
  • YAML 负责存测试数据
  • pytest 负责执行测试
  • fixture 负责公共前置
  • parametrize 负责数据驱动

我会按"初学者能照着敲出来"的方式带你做。


一、项目目标

我们先做一个最小可运行的登录接口自动化项目。

功能很简单:

  1. 启动一个本地登录接口服务
  2. requests.post() 调用登录接口
  3. YAML 读取多组测试数据
  4. pytest.mark.parametrize 执行多组测试
  5. 用断言校验接口返回结果

二、项目效果图

整个流程你先脑子里有个图:

plantuml 复制代码
@startuml
start
:读取 YAML 测试数据;
:pytest 收集测试用例;
repeat
  :取出一组登录数据;
  :requests 发送 POST 请求;
  :服务端返回响应 JSON;
  :断言 code / msg / token;
repeat while (还有测试数据?)
stop
@enduml

三、项目目录结构

先建这个目录:

text 复制代码
login_test_project/
├─ app.py
├─ pytest.ini
├─ requirements.txt
├─ config/
│  └─ config.yml
├─ common/
│  ├─ yaml_util.py
│  └─ request_util.py
├─ data/
│  └─ login.yml
└─ tests/
   ├─ conftest.py
   └─ test_login.py

四、安装依赖

先进入项目目录,然后安装:

bash 复制代码
pip install pytest requests pyyaml flask

你也可以先写一个 requirements.txt

txt 复制代码
pytest
requests
pyyaml
flask

然后执行:

bash 复制代码
pip install -r requirements.txt

五、先写一个本地登录接口服务

为了让你能完整练习 requests,我们自己写一个本地接口。

文件:app.py

python 复制代码
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/api/login", methods=["POST"])
def login():
    data = request.get_json()

    username = data.get("username", "")
    password = data.get("password", "")

    if not username:
        return jsonify({
            "code": 400,
            "msg": "用户名不能为空",
            "token": None
        })

    if not password:
        return jsonify({
            "code": 400,
            "msg": "密码不能为空",
            "token": None
        })

    if username == "admin" and password == "123456":
        return jsonify({
            "code": 200,
            "msg": "登录成功",
            "token": "token_admin_123"
        })

    return jsonify({
        "code": 401,
        "msg": "用户名或密码错误",
        "token": None
    })

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=True)

六、启动接口服务

打开终端,进入项目目录,运行:

bash 复制代码
python app.py

启动后你会看到本地服务跑在:

text 复制代码
http://127.0.0.1:5000

登录接口地址就是:

text 复制代码
http://127.0.0.1:5000/api/login

七、写配置文件

文件:config/config.yml

yaml 复制代码
base_url: "http://127.0.0.1:5000"
login_path: "/api/login"
timeout: 5

这个配置文件的意义是:

  • 基础地址统一管理
  • 接口路径统一管理
  • 超时时间统一管理

以后做真实项目时,测试环境、预发布环境、生产环境都能这样切换。


八、写 YAML 读取工具

文件:common/yaml_util.py

python 复制代码
import yaml

def read_yaml(path):
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)

这里要记住:
优先用 safe_load(),不要一上来就用 load()


九、写请求工具类

文件:common/request_util.py

python 复制代码
import requests

def send_post(url, json_data=None, timeout=5):
    response = requests.post(url=url, json=json_data, timeout=timeout)
    return response

现在先写最简版。

后面做项目时,我们再往里面加:

  • 日志
  • 异常处理
  • 请求头
  • token 管理
  • 统一断言封装

十、编写登录测试数据

文件:data/login.yml

yaml 复制代码
login_cases:
  - title: "正确登录"
    request:
      username: "admin"
      password: "123456"
    expect:
      code: 200
      msg: "登录成功"
      token_not_null: true

  - title: "密码错误"
    request:
      username: "admin"
      password: "111111"
    expect:
      code: 401
      msg: "用户名或密码错误"
      token_not_null: false

  - title: "用户名为空"
    request:
      username: ""
      password: "123456"
    expect:
      code: 400
      msg: "用户名不能为空"
      token_not_null: false

  - title: "密码为空"
    request:
      username: "admin"
      password: ""
    expect:
      code: 400
      msg: "密码不能为空"
      token_not_null: false

这里你一定要学会这种结构:

  • title:用例标题
  • request:请求参数
  • expect:期望结果

这是接口自动化里非常常见的组织方式。


十一、配置 pytest

文件:pytest.ini

ini 复制代码
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -vs

含义:

  • testpaths = tests:测试目录在 tests
  • python_files = test_*.py:测试文件命名规则
  • addopts = -vs:运行时显示详细输出

十二、写 conftest.py

这里开始把你前面学过的 fixture 用进来。

文件:tests/conftest.py

python 复制代码
import pytest
from common.yaml_util import read_yaml

@pytest.fixture(scope="session")
def config_data():
    return read_yaml("config/config.yml")

@pytest.fixture(scope="session")
def base_url(config_data):
    return config_data["base_url"]

@pytest.fixture(scope="session")
def login_url(config_data, base_url):
    return base_url + config_data["login_path"]

@pytest.fixture(scope="session")
def timeout(config_data):
    return config_data["timeout"]

@pytest.fixture(scope="session")
def login_cases():
    data = read_yaml("data/login.yml")
    return data["login_cases"]

这里你可以复习一下 fixture 的意义:

  • config_data:读取配置
  • base_url:基础地址
  • login_url:完整登录地址
  • timeout:请求超时
  • login_cases:读取 YAML 用例数据

而且这些都是 session 级别,只读一次就够了。


十三、写测试用例

文件:tests/test_login.py

python 复制代码
import pytest
from common.request_util import send_post

def build_ids(cases):
    return [case["title"] for case in cases]

# 先读取一份静态数据用于 ids 展示
from common.yaml_util import read_yaml
_temp_cases = read_yaml("data/login.yml")["login_cases"]

@pytest.mark.parametrize("case", _temp_cases, ids=build_ids(_temp_cases))
def test_login(case, login_url, timeout):
    req_data = case["request"]
    expect_data = case["expect"]

    response = send_post(
        url=login_url,
        json_data=req_data,
        timeout=timeout
    )

    assert response.status_code == 200

    result = response.json()

    assert result["code"] == expect_data["code"]
    assert result["msg"] == expect_data["msg"]

    if expect_data["token_not_null"]:
        assert result["token"] is not None
    else:
        assert result["token"] is None

十四、这个测试文件你要重点看懂什么

这个文件已经把今天的知识都串起来了。

1)参数化

python 复制代码
@pytest.mark.parametrize("case", _temp_cases, ids=build_ids(_temp_cases))

意思是:

  • case 是参数名
  • _temp_cases 是多组 YAML 数据
  • ids=... 用标题作为测试名称

所以它会执行 4 次测试。


2)fixture 注入

python 复制代码
def test_login(case, login_url, timeout):

这里:

  • case 来自参数化
  • login_url 来自 fixture
  • timeout 来自 fixture

这就是"参数化 + fixture"一起用。


3)请求发送

python 复制代码
response = send_post(
    url=login_url,
    json_data=req_data,
    timeout=timeout
)

这里就是 requests.post() 真正出场的地方。


4)断言

python 复制代码
assert response.status_code == 200
assert result["code"] == expect_data["code"]
assert result["msg"] == expect_data["msg"]

这就是接口自动化测试的核心:

  • 先断言 HTTP 层
  • 再断言业务层

十五、整个执行流程图

再看一张更完整的图:


十六、怎么运行项目

先开一个终端,启动接口服务:

bash 复制代码
python app.py

再开第二个终端,执行测试:

bash 复制代码
pytest

你大概会看到类似输出:

text 复制代码
tests/test_login.py::test_login[正确登录] PASSED
tests/test_login.py::test_login[密码错误] PASSED
tests/test_login.py::test_login[用户名为空] PASSED
tests/test_login.py::test_login[密码为空] PASSED

十七、这个项目你学到了什么

这个小项目虽然简单,但它已经有"真实项目雏形"了。

你已经用到了:

  • requests 发 POST 请求
  • YAML 管理测试数据
  • pytest 执行测试
  • parametrize 做数据驱动
  • fixture 管理公共资源
  • conftest.py 管理共享 fixture
  • pytest.ini 管理测试配置

这套组合,就是接口自动化项目最常见的基础骨架。


十八、你现在可以继续升级的方向

下面是这个项目的自然升级路线。

1)加日志

把请求 URL、请求参数、响应结果打印出来。

2)加断言封装

把重复的断言逻辑提到公共方法里。

3)加 token 提取

登录成功后自动提取 token,给后续接口复用。

4)加更多接口

比如用户信息查询、修改密码、退出登录。

5)接入 Allure 报告

让测试报告更像企业项目。


十九、我建议你马上做的练习

你自己先动手改这 3 个点:

  1. 再往 login.yml 里加两条异常数据
  2. request_util.py 里打印请求和响应
  3. 新增一个"获取用户信息"的假接口,并继续用同样方式写测试

这样你就不是"看懂了",而是真正会用了。


相关推荐
竹之却2 小时前
如何使用 SakuraFrp 做内网穿透
运维·服务器·网络·frp·内网穿透·sakurafrp
不一样的故事1262 小时前
抓重点、留弹性、重节奏
大数据·网络·人工智能·安全
爱学习的小囧2 小时前
VMware ESXi V7 无 vCenter 虚拟机磁盘缩减攻略:安全释放存储空间(不丢数据)
服务器·网络·windows·安全·esxi·虚拟化
Sgf2273 小时前
第15章 网络编程
开发语言·网络·php
SPC的存折3 小时前
3、Ansible之playbook模块大全
linux·运维·网络·python
智链RFID4 小时前
当企业运营遇到瓶颈:RFID 为什么越来越被采用?
大数据·网络·人工智能·科技·rfid
源远流长jerry4 小时前
NFV(网络功能虚拟化):重塑未来网络架构的革命性技术
linux·服务器·网络·架构
FreeBuf_4 小时前
Claude浏览器扩展漏洞允许通过任意网站实现零点击XSS提示注入
前端·网络·xss
原来是猿4 小时前
进程间通信(三):命名管道
linux·服务器·网络·git