Pytest Fixture 依赖注入:200个测试用例从一团乱麻到井井有条

来源:千锋教育
发布时间:2026-05-04 09:01:18

千锋教育品牌logo

01 Fixture 是什么?为什么它是 Pytest 的灵魂

传统测试框架(比如 unittest)用 setUp 和 tearDown 管理前置和后置操作。简单场景够用,复杂场景就暴露短板:无法组合、无法参数化、无法跨文件共享。

Pytest Fixture 做了一件事:把测试依赖变成参数。

Before:每个测试文件里都写一遍数据库连接、登录逻辑、测试数据准备。

class TestUser:
    def setup_method(self):
        self.db = connect_db()
        self.token = login("admin", "123456")
        self.user_data = create_test_user()
    
    def test_create_user(self):
        # 测试逻辑
        pass
    
    def teardown_method(self):
        self.db.close()
        delete_test_user(self.user_data)

After:Fixture 声明依赖,测试函数只管用。

import pytest

@pytest.fixture
def db_connection():
    conn = connect_db()
    yield conn
    conn.close()

@pytest.fixture
def admin_token(db_connection):
    return login("admin", "123456")

@pytest.fixture
def test_user(db_connection):
    user = create_test_user()
    yield user
    delete_test_user(user)

def test_create_user(test_user, admin_token):
    # 测试逻辑:直接使用 test_user 和 admin_token
    pass

> Fixture 不是 setup 的替代品,是依赖管理的思维方式。把"我要什么"变成"给我什么"。

---

02 依赖注入:Fixture 的核心机制

Pytest Fixture 的依赖注入有多强?看三个关键词:自动发现、链式依赖、作用域控制。

自动发现:只要函数名出现在测试参数里,Pytest 自动调用对应 Fixture,不需要手动 import 或调用。

链式依赖:Fixture 可以依赖其他 Fixture,Pytest 自动构建依赖图。

@pytest.fixture
def api_client(admin_token):
    return APIClient(base_url="https://api.example.com", token=admin_token)

@pytest.fixture
def user_service(api_client):
    return UserService(api_client)

def test_get_user(user_service):
    # Pytest 自动解析链路:
    # test_get_user → user_service → api_client → admin_token
    user = user_service.get_user(1)
    assert user.name == "张三"

作用域控制:5个级别决定 Fixture 的生命周期。

| 作用域 | 说明 | 典型用途 |

|--------|------|---------|

| function | 每个测试函数执行一次(默认) | 创建临时测试数据 |

| class | 每个测试类执行一次 | 初始化浏览器实例 |

| module | 每个模块执行一次 | 数据库连接 |

| package | 每个包执行一次 | 共享配置 |

| session | 整个测试会话执行一次 | 启动测试服务器 |

@pytest.fixture(scope="session")
def test_server():
    server = start_test_server()
    yield server
    server.stop()

@pytest.fixture(scope="function")
def fresh_db(test_server):
    db = test_server.create_database()
    yield db
    db.drop()

> 作用域选错了,不是慢就是脏。函数级保纯净,会话级省资源。

---

03 conftest.py:跨文件共享的枢纽

真实项目里,几十个测试文件需要共享同一套 Fixture。答案就是 conftest.py。

规则:conftest.py 放在哪个目录,它里面的 Fixture 就对该目录下所有测试可见。不需要 import,Pytest 自动发现。

tests/
├── conftest.py          # 全局 Fixture:数据库、登录、配置
├── api/
│   ├── conftest.py      # API 测试专属 Fixture
│   ├── test_user.py
│   └── test_order.py
├── ui/
│   ├── conftest.py      # UI 测试专属 Fixture
│   └── test_login.py
└── conftest.py          # 根级,所有子目录都继承

Before:每个文件 copy 一份登录 Fixture,改密码要改50个文件。

After:只在根 conftest.py 写一次,所有文件自动可用。

# tests/conftest.py(全局)
@pytest.fixture
def base_url():
    return "https://api.test.example.com"

@pytest.fixture
def auth_headers(base_url):
    response = requests.post(f"{base_url}/auth/login", json={
        "username": "test_admin",
        "password": "test_pass"
    })
    token = response.json()["token"]
    return {"Authorization": f"Bearer {token}"}
# tests/api/test_user.py(无需 import,直接用)
def test_list_users(auth_headers, base_url):
    resp = requests.get(f"{base_url}/users", headers=auth_headers)
    assert resp.status_code == 200

> conftest.py 是测试项目的公共基础设施。建好了,后面的路自己走。

---

04 参数化 Fixture:一套逻辑测多组数据

依赖注入最具威力的用法之一:参数化 Fixture。一个 Fixture 定义多组数据,自动生成多个测试用例。

@pytest.fixture(params=[
    {"username": "admin", "password": "admin123", "expected": True},
    {"username": "guest", "password": "wrong", "expected": False},
    {"username": "", "password": "", "expected": False},
])
def login_case(request):
    return request.param

def test_login(login_case):
    result = login(login_case["username"], login_case["password"])
    assert result.success == login_case["expected"]

运行结果:自动生成3个独立测试用例,每个参数组合一个。

test_login[login_case0]  ✅ PASSED
test_login[login_case1]  ✅ PASSED
test_login[login_case2]  ✅ PASSED

更进一步的:Fixture 组合 + 参数化。

@pytest.fixture(params=["chrome", "firefox", "edge"])
def browser(request):
    if request.param == "chrome":
        driver = webdriver.Chrome()
    elif request.param == "firefox":
        driver = webdriver.Firefox()
    else:
        driver = webdriver.Edge()
    yield driver
    driver.quit()

def test_search(browser):
    browser.get("https://www.baidu.com")
    browser.find_element(By.ID, "kw").send_keys("Pytest")
    results = browser.find_elements(By.CSS_SELECTOR, ".result")
    assert len(results) > 0

3个浏览器 × N个测试函数 = 3N 个测试用例,代码只写一份。

> 数据驱动不是多写几组数据,是一份数据跑出所有可能性。

---

05 实战重构:从混乱到清晰

来看一个真实场景的重构过程。

重构前:一个 conftest.py 文件里堆了30个 Fixture,互相依赖,改一个牵连十个。

# 问题代码:隐式依赖,职责不清
@pytest.fixture
def app():
    db = create_db()
    user = create_user(db)
    token = login(db, user)
    client = APIClient(token)
    cache = RedisCache()
    yield {"client": client, "db": db, "user": user, "token": token, "cache": cache}
    cleanup_everything(db, user, cache)

重构后:拆分职责,显式声明依赖。

# 拆分为独立 Fixture,职责单一
@pytest.fixture(scope="session")
def database():
    db = create_db()
    yield db
    db.close()

@pytest.fixture
def test_user(database):
    user = create_user(database)
    yield user
    database.delete_user(user.id)

@pytest.fixture
def auth_token(database, test_user):
    return generate_token(test_user)

@pytest.fixture
def api_client(auth_token):
    return APIClient(token=auth_token)

# 测试用例只声明自己需要的
def test_user_profile(api_client, test_user):
    resp = api_client.get(f"/users/{test_user.id}")
    assert resp.status_code == 200

重构原则:

1. 单一职责:一个 Fixture 只做一件事

2. 显式依赖:需要什么就在参数里写什么

3. 合理作用域:能复用的用大作用域,有副作用的用小作用域

4. yield 清理:资源创建用 yield,后面紧跟清理逻辑

> 重构测试代码和重构产品代码一样重要。测试代码的质量,决定测试结果的可信度。

---

依赖注入不是炫技,是工程思维。Fixture 让你的测试代码从"能用"变成"好用",从"改不动"变成"随便改"。

记住三个关键词:声明依赖、控制作用域、单一职责。做到了,200个测试用例也能管得明明白白。

别让测试代码成为技术债。写得好的测试,是项目最好的文档。

声明:本站部分稿件版权来源于网络,如有侵犯版权,请及时联系我们。

相关推荐

  • Pytest Fixture 依赖注入:200个测试用例从一团乱麻到井井有条
  • Allure 测试报告:3步美化你的测试结果,报告可读性提升200%
  • 你的测试代码还在“裸奔”?一个分支策略让团队效率提升300%
  • 30秒搭建测试环境,效率提升90%:Docker让环境管理不再折磨测试人
  • Appium 移动端自动化测试:一套脚本跑通 iOS 和 Android
  • Jenkins Pipeline 自动化测试调度:代码提交即触发,测试报告自动出