Pytest自动化测试框架

一、什么是测试框架

根据大量的测试实践,抽象出来的常用工具集合,包含大量组件或功能,以及经过验证的方法论。

二、安装Pytest

bash 复制代码
pip install pytest #安装
pip uninstall pytest #卸载
pip install pytest -U #升级最新版本
pip install pytest==7.2 #升级到指定版本

三、Pytest基础概念

1、创建测试用例

创建用例的规则:

  • 创建test_开头文件
  • 创建test开头函数
  • 创建assert断言
  • 不可自定义参数和返回值(pytest中参数有另外的含义)
python 复制代码
def test_tom():
    a=1
    b=2
    assert a >= b

2、用例发现规则

pytest识别、加载用例的过程,称之为用例发现,具休规则如下:

  • 遍历所有的目录(venv和.开头的除外)
  • 加载符合要求的py文件(test_开头或者_test结尾)
  • 遍历符合要求的类(Test开头且没有_init_)
  • 收集符合要求的函数或者方法(test开头)

3、执行测试用例

执行用例核心逻辑是启动pytest框架(pytest会自动收集和执行用例),具体方法有多种:

  • 命令行
bash 复制代码
pytest

1).运行所有

pytest

2).指定模块

pytest -vs tests/server/test_api.py #-vs表示输出详细信息和调试信息,包括print打印的信息

3).指定目录

pytest -vs ./tests/server

4).通过nodeid指定用例运行

pytest -vs ./tests/server/test_api.py::test_api_articles #nodeid由模块名,分隔符(::),类名,方法名,函数名组成

  • 代码
python 复制代码
import pytest

pytest.main() # 启动pytest 测试框架

1).运行所有

pytest.main()

2).指定模块

pytest.main('-vs','tests/server/test_api.py') #-vs表示输出详细信息和调试信息,包括print打印的信息

3).指定目录

pytest.main('-vs','./tests/server')

4).通过nodeid指定用例运行

pytest.main('-vs','tests/server/test_api.py::test_api_articles') #nodeid由模块名,分隔符(::),类名,方法名,函数名组成

4、执行参数详解

-s :表示输出调试信息,包括print打印的信息

-v:显示更详细的信息

-vs :这两个参数可以一起用

-n:支持多线程或者分布式运行测试用例,如果目录下有2个模块,当n=2时会产生2个线程同时执行两个测试模块内容 如: pytest.main ( '一vs', '.tests/server' , '-n=2 ') ; pytest -vs .tests/server -n 2

--reruns num:失败用例重跑,num为重跑次数,如果某个用例失败再跑num次,其他用例正常执行 如:pytest.main ( '一vs', 'tests./server' , '--reruns=2 ') ; pytest -vs .tests/server --reruns 2

-x:表示只要有一个用例报错,那么测试停止

--maxfail=2 表示如果出现两个用例报错,测试就停止

-k:根据测试用例的部分字符串指定测试用例 如:如: pytest.main('-vs','.tests/server','-k=articles') ; pytest -vs .tests/server -k "articles" #只执行测试用例名称中包含"articles"字符串的测试用例

5、理解执行结果

bash 复制代码
================================================================= test session starts =================================================================
platform win32 -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0
rootdir: C:\Users\vorn\Documents\trae_projects\AutoTest
collected 1 item                                                                                                                                       

test_01.py F                                                                                                                                     [100%]

====================================================================== FAILURES =======================================================================
______________________________________________________________________ test_tom _______________________________________________________________________

    def test_tom():
        a=1
        b=2
>       assert a>=b
E       assert 1 >= 2

test_01.py:4: AssertionError
=============================================================== short test summary info =============================================================== 
FAILED test_01.py::test_tom - assert 1 >= 2
================================================================== 1 failed in 0.19s ================================================================== 

pytest执行结果分为几个部分:

  • 执行环境:操作系统、python版本、pytest版本
  • 执行过程:根目录、用例收集情况(例如上面collected 1 item代表收集到一个pytest用例)、用例名称、用例执行结果、执行进度
  • 失败详情:用例名称、用例内容、变量内容、断言提示
  • 整体摘要:结果数量、花费时间、失败的文件和用例

pytest场景结果缩写:

. passed 通过
F failed 失败
E error 出错
s skipped 跳过
X xpassed 意外通过
x xfailed 预期失败

三、夹具fixture

1、作用

在用例执行之前、执行之后,自动运行代码(准备、请求动作)

场景:

  • 之前:启动浏览器;之后:关闭浏览器
  • 之前:连接数据库;之后:关闭数据库连接
  • 之前:注册账号;之后:删除账号

2、创建fixture

  1. 创建一个函数(不要test开头)
  2. 添加装饰器(@pytest.fixture)
  3. 添加关键字(yield)
python 复制代码
@pytest.fixture
def func():
    #前置操作
    yield
    #后置操作

3、conftest:跨文件共享fixture

conftest.py:自动发现和加载fixture

conftest支持五级作用域,可以对作用域中所有测试用例执行fixture中的前后置操作:

  • function:函数默认值
  • class:类
  • module:模块(文件)
  • package:包(目录)
  • session:全局(所有的用例)

4、请求fixture

请求框架调用fixture,并且返回结果:

  • 把fixture名字写在用例参数列表(可以接收结果)(推荐的方式)
  • 使用@pytest.mark.usefixtures 标记(不可以接收结果)(不推荐)
  • 不允许直接调用fixture

5、fixture的作用域

同一个作用域中,fixture不会重复执行;不在同一个作用域中,fixture会重复执行:

  • package作用域的fixture,在同一个目录中,不会重复执行
  • session作用域的fixture,整个框架运行过程中,只执行1次

示例:

问题1:10个用例,希望使用同一个浏览器?

解答:将fixture的作用域定义为package,10个用例放到同一个目录下。例如打开浏览器执行10个用例,每个用例都依赖前一个用例在浏览器中的数据,全部跑完后关闭浏览器,能够做到跑10个用例只打开一次浏览器就是利用了"同一个作用域中,fixture不会重复执行"的规则。

问题2:希望所有用例执行完毕之后,做某事?

解答:将fixture的作用域定义为session即可。例如执行完所有用例后需要删除注册过的账号。

四、配置文件pytest.ini

pytest框架通过读取pytest.ini配置文件运行

(1)位置:一般放在项目的根目录。

(2)编码:必须是ANSI,可以使用notpad++修改编码格式,并且文件中不可以有中文。

(3)作用:改变pytest默认的行为。

(4)运行的规则:不管是主函数的模式运行,命令行模式运行,都会去读取这个配置文件。

python 复制代码
[pytest]
addopts = -vs  #命令行的参数,用空格分隔
testpaths = ./tests  #测试用例的路径
python_files = test_*.py  #模块名的规则
python_classes = Test*  #类名的规则
python_functions = test  #方法名的规则

五、一个Pytest项目示例

项目结构:

python 复制代码
│  conftest.py
│  main.py
│  pytest.ini
│
└─tests
    ├─server
    │      test_api.py
    │
    └─web
            test_web.py
            test_websearch.py

#conftest.py

python 复制代码
import pytest
from selenium import webdriver

@pytest.fixture(scope="package") #scope代表作用域,默认是function(函数)还可以是class(类)、module(包|文件)、package(模块|目录)、session(全局)
def driver():
    #启动浏览器
    driver=webdriver.Chrome()
    driver.get("https://www.sogou.com/")
    print("[ok] 打开 sogou.com")
    
    yield driver #把启动好并且随后会关闭的浏览器对象driver传递给测试用例
    
    #关闭浏览器
    driver.quit()
    print("[ok] 关闭浏览器")

#main.py

python 复制代码
import pytest

pytest.main(['-vs'])

#pytest.ini

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

#test_api.py

python 复制代码
import requests

def test_api_articles():
    
    #准备请求要素
    method = "get"
    url = "https://api.apiopen.top/api/articles/random"
    headers = {
        "accept": "application/json",
    }
    
    #向接口发送请求
    response = requests.request(method, url, headers=headers)
    print(response.text)
    
    #断言响应结果
    assert response.status_code == 200
    assert '成功' in response.text
    print("[Pass]获取随机文章成功")

def test_api_sentence():
    
    #准备请求要素
    method = "get"
    url = "https://api.apiopen.top/api/tools/famous-sentence"
    headers = {
        "accept": "application/json",
    }
    
    #向接口发送请求
    response = requests.request(method, url, headers=headers)
    print(response.text)
    
    #断言响应结果
    assert response.status_code == 200
    assert '成功' in response.text
    print("[Pass]获取随机名言成功")

#test_web.py

python 复制代码
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

def test_web(driver): #driver是conftest.py中定义的fixture,这里直接使用即可。从python的角度讲就是函数传参;从pytest的角度讲就是请求fixture

    #输入搜索内容
    search_box=driver.find_element(By.XPATH,'//*[@id="query"]')
    search_box.clear()
    search_box.send_keys("pytest")
    search_box.send_keys(Keys.ENTER) #模拟按下回车键
    print("[ok] 输入搜索内容 pytest")
    time.sleep(3)

    #断言:验证搜索结果包含 pytest
    assert "pytest" in driver.page_source
    print("[ok] 搜索结果包含 pytest")
    time.sleep(3)

#test_websearch.py

python 复制代码
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

def test_websearch(driver): #driver是conftest.py中定义的fixture,这里直接使用即可。从python的角度讲就是函数传参;从pytest的角度讲就是请求fixture

    #点击图片搜索结果
    search_box=driver.find_element(By.XPATH,'//*[@id="sogou_pic"]')
    search_box.click()

    print("[ok] 切换到图片搜索结果")
    time.sleep(3)

执行结果:

python 复制代码
================================ test session starts =================================
platform win32 -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0 -- C:\Users\vorn\AppData\Local\Programs\Python\Python314\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\vorn\Documents\trae_projects\AutoTest
configfile: pytest.ini
testpaths: ./tests
collected 4 items                                                                     

tests/server/test_api.py::test_api_articles {"code":200,"message":"成功","data":{"id":399,"title":"Go 语言入门指南","summary":"详细介绍 Go 语言的基础知识","author":{"account_id":2,"email":"test@example.com","nickname":"testuser","avatar":"https://example.com/avatar.jpg","bio":"这是一段个人简介","gender":"male","age":25,"location":"北京","created_at":"2025-12-01 16:49:51","updated_at":"2026-07-28 10:39:23"},"category":"技术","tags":"Go,编程,后端","cover_image":"https://picsum.photos/800/600?random=1","view_count":0,"like_count":0,"is_published":true,"is_pinned":false,"created_at":"2026-07-06 20:29:16","updated_at":"2026-07-06 20:29:16"}}
[Pass]获取随机文章成功
PASSED
tests/server/test_api.py::test_api_sentence {"code":200,"message":"成功","data":{"name":"早知恁么。悔当初、不把雕鞍锁。","from":"柳永《定风波·自春来》"}}
[Pass]获取随机名言成功
PASSED
tests/web/test_web.py::test_web [ok] 打开 sogou.com
[ok] 输入搜索内容 pytest
[ok] 搜索结果包含 pytest
PASSED
tests/web/test_websearch.py::test_websearch [ok] 切换到图片搜索结果
PASSED[ok] 关闭浏览器


================================= 4 passed in 27.56s ================================= 

六、数据驱动测试

数据驱动测试 = 参数化测试 + 数据文件

根据数据文件的内容,动态决定用例的数量、内容

数据驱动需要用到装饰器**@pytest.mark.parametrize()**

python 复制代码
语法:@pytest.mark.parametrize(args_name,args_value)
#args_name 参数名,字符串,可自定义名称
#args_value 参数值(list,tuple,字典列表,字典元组),有多少个值那么测试用例就会执行多少次(以"-"为分隔符计算有多少个值)

#YAML介绍:
yaml是一种数据格式,主要用于配置文件或者编写用例
yaml只有两种数据:
  1.键值对
    key:(空格)value
  2.list
    用一个"-"表示一个列表
操作yaml的第三方模块是pyyaml,安装方法:pip install pyyaml

增加数据驱动后的Pytest项目示例:

目录结构:

python 复制代码
│  conftest.py  #保持不变
│  main.py  #为了方便演示,只运行test_api.py中的一个测试用例
│  pytest.ini  #保持不变
│
├─common  #新增公共方法目录
│      yaml_util.py  #新增读取数据驱动方法
│
└─tests
    ├─server
    │      test_api.py  #method、url配合yaml_util模块做成变量形式
    │      test_api_parametrize.yaml  #新增数据驱动文件
    │
    └─web
            test_web.py  #不使用
            test_websearch.py  #不使用

#main.py

python 复制代码
import pytest

pytest.main(['-vs','tests/server/test_api.py::test_api_articles'])  #只运行test_api.py中的一个测试用例test_api_articles

#yaml_util.py

python 复制代码
import yaml  #需要安装模块:pip install pyyaml
import os

#读取数据驱动
def read_testcases(yaml_name):
    with open(os.getcwd()+'/tests/server'+'/'+yaml_name,mode='r',encoding='utf-8') as f:
        value=yaml.load(stream=f,Loader=yaml.FullLoader)
        return value

#test_api.py

python 复制代码
import pytest
import requests
from common.yaml_util import read_testcases  #导入公共函数中"读取数据驱动"方法

@pytest.mark.parametrize("args_name",read_testcases("test_api_parametrize.yaml"))  #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
def test_api_articles(args_name):
    
    #准备请求要素
    method = args_name["request"]["method"]
    url = args_name["request"]["url"]
    headers = {
        "accept": args_name["request"]["headers"]["accept"],
    }
    
    #向接口发送请求
    response = requests.request(method, url, headers=headers)
    print(response.text)
    
    #断言响应结果
    assert response.status_code == args_name["validate"]["status_code"]
    assert args_name["validate"]["status_text"] in response.text
    print("[Pass]获取随机文章成功")

def test_api_sentence():
    
    #准备请求要素
    method = "get"
    url = "https://api.apiopen.top/api/tools/famous-sentence"
    headers = {
        "accept": "application/json",
    }
    
    #向接口发送请求
    response = requests.request(method, url, headers=headers)
    print(response.text)
    
    #断言响应结果
    assert response.status_code == 200
    assert '成功' in response.text
    print("[Pass]获取随机名言成功")

#test_api_parametrize.yaml

python 复制代码
-  #这个"-"是每组数据的分隔符,@pytest.mark.parametrize可以根据有几组测试数据就把这个测试用例跑几遍
  name: 获取随机文章  #本组数据名称
  request:  #请求内容
    method: get  #请求方法
    url: https://api.apiopen.top/api/articles/random  #请求url
    headers:  #请求头
      accept: application/json
    body:  #请求体
      account: None
      password: None
  validate:  #断言
      status_code: 200
      status_text: 成功
#name、request、validate是数据驱动最基本的内容,当然这三个基本内容的名称也可自定义
-  #第二组数据
  name: 获取随机文章  #本组数据名称
  request:  #请求内容
    method: get  #请求方法
    url: https://api.apiopen.top/api/articles/random  #请求url
    headers:  #请求头
      accept: application/json
    body:  #请求体
      account: None
      password: None
  validate:  #断言
      status_code: 200
      status_text: 成功

执行结果:可以看到,main.py中只执行了一个用例,由于数据驱动中有2组数据,这个用例被跑了2遍,实际项目中可以给出2组不同的数据做不同场景测试。

python 复制代码
================================ test session starts =================================
platform win32 -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0 -- C:\Users\vorn\AppData\Local\Programs\Python\Python314\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\vorn\Documents\trae_projects\AutoTest
configfile: pytest.ini
collected 2 items                                                                     

tests/server/test_api.py::test_api_articles[args_name0] {"code":200,"message":"成功","data":{"id":410,"title":"Go 语言入门指南","summary":"详细介绍 Go 语言的基础知识","author":{"account_id":2,"email":"test@example.com","nickname":"testuser","avatar":"https://example.com/avatar.jpg","bio":"这是一段个人简介","gender":"male","age":25,"location":"北 京","created_at":"2025-12-01 16:49:51","updated_at":"2026-07-28 10:39:23"},"category":"技术","tags":"Go,编程,后端","cover_image":"https://picsum.photos/800/600?random=1","view_count":0,"like_count":0,"is_published":true,"is_pinned":false,"created_at":"2026-07-06 20:45:41","updated_at":"2026-07-06 20:45:41"}}
[Pass]获取随机文章成功
PASSED
tests/server/test_api.py::test_api_articles[args_name1] {"code":200,"message":"成功","data":{"id":425,"title":"Go 语言入门指南","summary":"详细介绍 Go 语言的基础知识","author":{"account_id":2,"email":"test@example.com","nickname":"testuser","avatar":"https://example.com/avatar.jpg","bio":"这是一段个人简介","gender":"male","age":25,"location":"北 京","created_at":"2025-12-01 16:49:51","updated_at":"2026-07-28 10:39:23"},"category":"技术","tags":"Go,编程,后端","cover_image":"https://picsum.photos/800/600?random=1","view_count":0,"like_count":0,"is_published":true,"is_pinned":false,"created_at":"2026-07-06 20:54:12","updated_at":"2026-07-06 20:54:12"}}
[Pass]获取随机文章成功
PASSED

================================= 2 passed in 0.73s ================================== 
相关推荐
何宝荣1435 小时前
Postman 接口测试入门:从发请求到写断言
测试
2601_962382431 天前
3种python自动化测试框架推荐,看看哪个适合你?_robotframework和pytest
测试·自学·
围炉聊科技2 天前
Playwright Test Agents 三件套实测 ——智能体基建系列
浏览器·ai编程·测试
月読h3 天前
# Agent 的自主执行与工程决策记录:读 Anthropic 和 AWS ADR
agent·测试
囤囤囤3 天前
基于 WebUSB 与 CDP:在浏览器端实现 Android 设备通信与无证书抓包实践
测试
stillstream_ink4 天前
OpenCart性能压测复盘|JMeter\+Locust双工具实操,附5个踩坑记录
jmeter·测试
2501_928996224 天前
信创备份一体机性能焦虑根源与中科热备国产CPU平台实测拆解
后端·数据安全·测试