函数
函数
函数基础
1、简介
函数时组织好的,可重复使用的代码快,用于实现某个特定的功能
2、优点
提高代码复用性
使程序结构更清晰
降低维护成本
3、函数的基本结构
def 函数名(参数1, 参数2, ...):
"""文档字符串(可选)"""
函数体
return 返回值(可选)
参数类型
1、位置参数
def info(name, age):
print(f"{name}今年{age}岁了")
info("小红", 20)
2、关键字参数(**kwargs)
def show_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
show_info(name="小强", age=22, city="北京")
3、默认参数
def info(name, age=18):
print(f"{name}今年{age}岁了")
info("小蓝") # 使用默认年龄
4、可变参数(*args),也叫不定长参数
def total(*numbers):
print("传入了:", numbers)
print("总和为:", sum(numbers))
total(1, 2, 3, 4)
*args` —— 不定长位置参数
用星号 * 收集额外的位置参数,变成一个元组
常用于参数数量不确定的情况
def add_numbers(*args):
print(args) # args 是一个元组
return sum(args)
print(add_numbers(1, 2, 3)) # 输出 (1, 2, 3) → 6
print(add_numbers(5, 10)) # 输出 (5, 10) → 15
print(add_numbers()) # 输出 () → 0
kwargs` —— 不定长关键字参数**
用两个星号 ** 收集额外的关键字参数,变成一个字典
def print_info(**kwargs):
print(kwargs) # kwargs 是一个字典
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="Shanghai")
# 输出:
# {'name': 'Alice', 'age': 25, 'city': 'Shanghai'}
# name: Alice
# age: 25
# city: Shanghai
混合使用,*args 必须在 **kwargs 前面:
def demo(a, b, *args, **kwargs):
print("固定参数:", a, b)
print("可变位置参数:", args)
print("可变关键字参数:", kwargs)
demo(1, 2, 3, 4, x=5, y=6)
# 固定参数: 1 2
# 可变位置参数: (3, 4)
# 可变关键字参数: {'x': 5, 'y': 6}
简单来说:
*args→ 多个位置参数 → 元组**kwargs→ 多个关键字参数 → 字典
返回值
作用域
1、作用域分类
局部变量:定义在函数内部,只在函数内部有效
全局变量:定义在函数外部,整个程序都能访问
x = 10 # 全局变量
def func():
x = 5 # 局部变量
print("函数内部 x =", x)
func()
print("函数外部 x =", x)
匿名函数lambda
常用于配合map()、filter()、sorted() 等函数
# 普通函数
def square(x):
return x * x
# lambda 表达式
square2 = lambda x: x * x
print(square(5)) # 25
print(square2(5)) # 25
函数进阶
高阶函数
装饰器
def log(func):
def wrapper():
print("开始执行函数...")
func()
print("函数执行完毕")
return wrapper
@log
def say_hello():
print("Hello, world!")
say_hello()
实用技巧
项目实战
用函数结构搭建小项目(学生管理系统,数据分析流程)
小工具开发
需要实现的功能:
1、日志记录,记录函数执行的信息,包括参数,结果等
2、计时统计,记录函数的执行耗时
3、错误处理,捕获函数执行过程中的异常并输出
4、登录接口测试用例
5、加载yaml数据的测试脚本
6、日志输出
7、报告美化,高质量测试报告,支持用例详情,步骤截图,附件
8、测试覆盖率统计
9、mock外部依赖或接口
10、用例失败,自动重试
11、自动获取token,刷新并保存
12、接口依赖执行,控制先后执行接口依赖顺序
13、接口环境切换,支持多环境配置动态切换
14、动态数据生成,生成模拟用户,手机号,地址等数据
15、数据库验证,用于测试后检验数据库状态
16、redis验证,检验缓存更新是否生效
17、excel/csv读写:处理测试数据输入输出,批量执行用例 18、用例参数提取器,从接口响应中提取字段传入下一个接口用 conftest.py + pytest fixtures 实现
19、YAMl或excel转pytest用例生成器:读取测试用例数据自动转为函数代码,用openpyxl/pyyaml实现
20、三合一装饰器(日志、计时、异常)
import time
import traceback
from functools import wraps
def log_timer_exception(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[START] {func.__name__}")
start = time.time()
try:
result = func(*args, **kwargs)
print(f"[SUCCESS] {func.__name__} 返回值:{result}")
return result
except Exception as e:
print(f"[ERROR] {func.__name__} 报错:{e}")
traceback.print_exc()
finally:
duration = time.time() - start
print(f"[END] {func.__name__} 用时:{duration:.2f}s")
return wrapper
用pytest对函数进行自动化测试
方案
requests → 进行接口调用
pytest → 编写测试用例
allure(可选)→ 生成测试报告
yaml/json → 进行数据驱动
logging → 输出日志
项目结构
api_test_project/
├── config/
│ └── config.yaml # 全局配置,如base_url、headers等
├── data/
│ └── login_case.yaml # 测试数据驱动文件
│ └── add_user.yaml # 添加用户,用例数据
├── testcases/
│ └── test_login.py # 用例文件(pytest写法)
├── common/
│ ├── request_handler.py # 封装请求类,含token自动刷新
│ └── assert_handler.py # 封装断言函数(通用)
│ └── token_manager.py # token 读写 + 登录刷新
│ └── db_handler.py # 数据库连接与查询
├── utils/
│ └── logger.py # 日志封装
├── conftest.py # pytest钩子配置
└── pytest.ini # pytest运行配置
├── requirements.txt # 依赖库列表
├── token.txt # 本地缓存token
安装依赖
pytest-allure-adaptor 或 allure-pytest:生成可视化测试报告,接口自动化核心插件
pytest-xdist:多线程并发执行用例 提升执行效率
pytest-html:生成 HTML 报告(简版) 快速出报告
pytest-rerunfailures:用例失败自动重跑 解决网络抖动或临时问题
pytest-ordering:控制测试用例执行顺序 有强依赖的场景
pytest-moc:mock 函数与对象 单元测试与接口依赖隔离
pytest-cov:代码覆盖率统计 结合单元测试使用
pytest-dependency:指定测试用例依赖关系 登录接口依赖场景常见
pytest-random-order:随机打乱用例执行顺序 检测测试间是否存在依赖
pytest-timeout:控制单个测试用例执行时间 防止卡死、超时阻塞
# 常用
pip install pytest
pip install allure-pytest(生成自定义的报告)
pip install pytest-xdist(多线程运行)
pip install pytest-html (生成html报告)
pip install pytest-rerunfailures(失败用例重跑)
pip install pytest-ordering(改变用例的执行顺序)
pip install requests pytest pyyaml allure-pytest
# 然后在虚拟环境执行命令,批量安装依赖
pip install -r requirement.txt
request封装:common/request_handler.py
import requests
import yaml
class RequestHandler:
def __init__(self, base_url=None):
self.base_url = base_url or ""
def send(self, method, url, headers=None, params=None, data=None, json=None):
full_url = self.base_url + url
try:
response = requests.request(
method=method.upper(),
url=full_url,
headers=headers,
params=params,
data=data,
json=json
)
return response
except Exception as e:
print(f"请求出错:{e}")
return None
测试数据驱动:data/login_case.yaml
- case_name: 正确登录
method: post
url: /api/login
json:
username: admin
password: 123456
expected_status: 200
expected_msg: "登录成功"
- case_name: 错误密码
method: post
url: /api/login
json:
username: admin
password: wrongpwd
expected_status: 401
expected_msg: "密码错误"
接口用例编写:testcases/test_login.py
import pytest
import yaml
from common.request_handler import RequestHandler
handler = RequestHandler(base_url="http://your-api.com")
# 加载yaml测试数据
def load_login_data():
with open("data/login_case.yaml", encoding="utf-8") as f:
return yaml.safe_load(f)
@pytest.mark.parametrize("case", load_login_data())
def test_login(case):
print(f"\n【执行用例】:{case['case_name']}")
res = handler.send(
method=case['method'],
url=case['url'],
json=case['json']
)
assert res.status_code == case['expected_status']
assert case['expected_msg'] in res.text
运行方式
# 普通运行
pytest testcases/
# 加上报告(需要先安装 allure)
pytest testcases/ --alluredir=report/
# 打开allure报告(需先安装 allure 命令行工具)
allure serve report/
企业级接口测试:Token提取+过期自动刷新+多接口串联+数据库校验
# 场景设定(真实项目中非常常见)
# Token提取与共享,fixture实现
# 登录并提取token
# common/token_handler.py
import requests
def get_token():
url = "http://your-api.com/api/login"
payload = {"username": "admin", "password": "123456"}
res = requests.post(url, json=payload)
token = res.json().get("token")
return token
# 在pytest中共享token,使用fixture实现
# conftest.py
import pytest
from common.token_handler import get_token
@pytest.fixture(scope="session")
def token():
return get_token()
# token保存和加载工具:common/token_manager.py
import requests
import json
import os
TOKEN_FILE = 'token.txt'
def save_token(token):
with open(TOKEN_FILE, 'w') as f:
f.write(token)
def load_token():
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, 'r') as f:
return f.read().strip()
return None
def refresh_token():
"""重新登录获取 token 并保存"""
url = "http://your-api.com/api/login"
payload = {"username": "admin", "password": "123456"}
res = requests.post(url, json=payload)
token = res.json().get("token")
if token:
save_token(token)
return token
# 自动加token并自动重试:封装请求类common/request_handler.py
from common.token_manager import load_token, refresh_token
import requests
class RequestHandler:
def __init__(self, base_url=None):
self.base_url = base_url or ""
def send(self, method, url, headers=None, **kwargs):
if headers is None:
headers = {}
token = load_token() or refresh_token()
headers['Authorization'] = f'Bearer {token}'
full_url = self.base_url + url
response = requests.request(method, full_url, headers=headers, **kwargs)
# 如果 token 失效,自动刷新后重试一次
if response.status_code in (401, 403):
print("⚠️ Token 失效,正在刷新...")
new_token = refresh_token()
headers['Authorization'] = f'Bearer {new_token}'
response = requests.request(method, full_url, headers=headers, **kwargs)
return response
# 用例中不再需要处理token,只需要调用封装类testcases/test_user_info.py
from common.request_handler import RequestHandler
handler = RequestHandler(base_url="http://your-api.com")
def test_get_user_info():
res = handler.send("get", "/api/user/info")
assert res.status_code == 200
assert "用户信息" in res.text
# 添加用户
# data/add_user.yaml
- case_name: 添加普通用户
url: /api/user/add
method: post
json:
name: "张三"
age: 28
expected_status: 200
expected_msg: "添加成功"
# 测试用例文件(自动加token)
# testcases/test_add_user.py
import pytest
import yaml
from common.request_handler import RequestHandler
handler = RequestHandler(base_url="http://your-api.com")
def load_case():
with open("data/add_user.yaml", encoding="utf-8") as f:
return yaml.safe_load(f)
@pytest.mark.parametrize("case", load_case())
def test_add_user(case, token):
headers = {"Authorization": f"Bearer {token}"}
res = handler.send(
method=case["method"],
url=case["url"],
headers=headers,
json=case["json"]
)
assert res.status_code == case["expected_status"]
assert case["expected_msg"] in res.text
# 数据库连接:common/db_handler.py
import pymysql
def query_user_by_name(name):
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='user_db'
)
cursor = conn.cursor()
sql = f"SELECT * FROM users WHERE name = '{name}'"
cursor.execute(sql)
result = cursor.fetchone()
cursor.close()
conn.close()
return result
# 加入到测试用例中作为断言
from common.db_handler import query_user_by_name
@pytest.mark.parametrize("case", load_case())
def test_add_user(case, token):
headers = {"Authorization": f"Bearer {token}"}
res = handler.send(
method=case["method"],
url=case["url"],
headers=headers,
json=case["json"]
)
assert res.status_code == case["expected_status"]
assert case["expected_msg"] in res.text
# ✅ 数据库断言
db_user = query_user_by_name(case["json"]["name"])
assert db_user is not None
# 完整自动化流程
Step 1:登录 → 获取 token → fixture 共享
Step 2:发送带 token 的接口请求(多接口串联)
Step 3:接口响应断言(状态码、文本)
Step 4:数据库查询 → 数据落库断言
Step 5:统一日志、报告、测试数据管理
# 多环境支持
env: dev
dev:
base_url: http://dev-api.com
test:
base_url: http://test-api.com
stage:
base_url: http://stage-api.com
mysql:
host: localhost
user: root
password: 123456
database: user_db
# 动态加载base_url:
with open("config/config.yaml", encoding="utf-8") as f:
config = yaml.safe_load(f)
env = config["env"]
base_url = config[env]["base_url"]
handler = RequestHandler(base_url=base_url)
# 多接口串联
def test_user_lifecycle(admin_token):
headers = {"Authorization": f"Bearer {admin_token}"}
# 1. 添加用户
res_add = handler.send("post", "/api/user/add", headers=headers, json={"name": "test01", "age": 18})
assert res_add.status_code == 200
# 2. 查询用户
res_get = handler.send("get", "/api/user/detail?name=test01", headers=headers)
assert "test01" in res_get.text
# 3. 删除用户
res_del = handler.send("delete", "/api/user/delete", headers=headers, json={"name": "test01"})
assert res_del.status_code == 200
# 失败重试机制(提高稳定性)pytest.ini增加配置
addopts = --reruns 2 --reruns-delay 2