Write clean, maintainable pytest tests using Fake-based testing, contract testing, and dependency injection patterns. Use when setting up test suites for Python/MCP projects, creating Fakes for external dependencies, writing contract tests, or implementing test patterns with fixtures and parametrization.
使用基于Fake的测试、契约测试和依赖注入,实现干净、可维护的pytest测试模式。通过明确的AAA模式和结构良好的fixture,专注于测试隔离、可重用性和清晰度。
使用Fake类代替unittest.mock进行模拟。Fake是内存中的实现,模拟真实依赖关系,无需外部调用。
为什么使用Fake?
将每个测试结构化为三个清晰的阶段,并添加注释:
python
在fixture之间注入依赖,以维护关系并避免重复。
验证组件是否正确注册工具/函数,并传递预期的参数。
控制器(MCP工具)
↓
服务(业务逻辑)
↓
仓库(数据访问)
↓
Fake(测试实现)
创建一个实现与真实依赖相同接口的Fake类:
python
class FakeAuth:
AuthProvider的Fake实现,用于测试。
def init(self) -> None:
self.created: List[Dict[str, Any]] = []
self.deleted: List[str] = []
self._seq = 0
self.failoncreate: bool = False
def createuser(self, email: str, password: str, displayname: str) -> str:
if self.failoncreate:
raise RuntimeError(create_user失败(fake))
self._seq += 1
uid = fuid-{self._seq}
rec = {uid: uid, email: email, displayname: displayname}
self.created.append(rec)
return uid
def delete_user(self, uid: str) -> None:
self.deleted.append(uid)
python
class FakeUsersRepo:
UsersRepository的Fake实现。
def init(self) -> None:
self.users: Dict[str, Dict[str, Any]] = {}
self.failonupsert: bool = False
def upsertuserdoc(self, uid: str, data: Dict[str, Any]) -> None:
if self.failonupsert:
raise RuntimeError(upsertuserdoc失败(fake))
self.users[uid] = dict(data)
def list_users(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
items = list(self.users.values())
if limit and limit > 0:
items = items[:limit]
return [dict(it) for it in items]
python
class FakeAuth:
def init(self) -> None:
self.failoncreate: bool = False # 在测试中控制失败
def createuser(self, email: str, password: str, displayname: str) -> str:
if self.failoncreate:
raise RuntimeError(create_user失败(fake))
# ... 其余实现
python
class FakeSectorsRepo:
def init(self, institutions: FakeInstitutionsRepo | None = None) -> None:
self.institutions = institutions # 注入依赖
self.data: Dict[str, Dict[str, Dict[str, Any]]] = {}
def institutionexists(self, institutionid: str) -> bool:
return bool(self.institutions and institution_id in self.institutions.data)
def upsertsector(self, institutionid: str, sector_id: str, data: Dict[str, Any]) -> None:
self.data.setdefault(institutionid, {})[sectorid] = dict(data)
python
@pytest.fixture()
def fake_auth():
为每个测试提供一个新的FakeAuth。
return FakeAuth()
@pytest.fixture()
def fakeusersrepo():
为每个测试提供一个新的FakeUsersRepo。
return FakeUsersRepo()
python
@pytest.fixture()
def fakesectorsrepo(fakeinstitutionsrepo):
FakeSectorsRepo依赖于FakeInstitutionsRepo。
return FakeSectorsRepo(institutions=fakeinstitutionsrepo)
@pytest.fixture()
def fakeroomsrepo(fakesectorsrepo):
FakeRoomsRepo依赖于FakeSectorsRepo。
return FakeRoomsRepo(sectors=fakesectorsrepo)
python
@pytest.fixture()
def userenv(fakeauth, fakeusersrepo):
为用户操作提供服务及所有依赖。
from myapp.services.user_service import UserService
svc = UserService(fakeauth, fakeusers_repo)
return svc, fakeauth, fakeusers_repo
python
@pytest.fixture()
def userenvseeded(user_env):
预填充数据的环境。
svc, auth, repo = user_env
svc.add_user(email=test@example.com, password=secret, name=Test User)
return svc
python
@pytest.fixture()
def temp_file():
提供临时文件并在测试后清理。
import tempfile
import os
fd, path = tempfile.mkstemp()
os.close(fd)
yield path
os.unlink(path)
python
def testaddusersuccess(fakeauth, fakeusersrepo):
# 准备
svc = UserService(fakeauth, fakeusers_repo)
email = test@example.com
password = secret
name = Test User
# 执行
result = svc.add_user(email=email, password=password, name=name)
# 断言
assert result[status] == ok
assert result[user][email] == email
assert result[user][name] == name
assert result[uid] in fakeusersrepo.users
python
@pytest.mark.parametrize(
email,password,name,role,
[
(a@example.com, secret, Alice, admin),
(b@example.com, p@ss, Bob, user),
],
)
def testadduserparametrized(userenv, email, password, name, role):
svc, auth, repo = user_env
# 执行
res = svc.adduser(email=email, password=password, name=name, globalrole=role)
# 断言
assert res[status] == ok
assert res[user][email] == email
assert res[user][name] == name
assert res[user][globalRole] == role
python
@pytest.mark.parametrize(email, [c@example.com, d@example.com])
def testadduserrollbackonfirestorefailure(fakeauth, fakeusers_repo, email):
# 准备
fakeusersrepo.failonupsert = True
svc = UserService(fakeauth, fakeusers_repo)
# 执行 & 断言
with pytest.raises(RuntimeError):
svc.add_user(email=email, password=secret, name=Bob)
# 断言回滚
assert fake_auth.deleted, 预期Firestore失败时auth用户被删除
python
def testlistusersnormalizestimestampstoiso(user_env):
# 准备
svc, auth, repo = userenv
from datetime import datetime
repo.users[u1] = {
id: u1,
email: x@y.z,
name:
该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 clean-pytest-1776419977 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 clean-pytest-1776419977 技能
skillhub install clean-pytest-1776419977
文件大小: 5.74 KB | 发布时间: 2026-4-17 18:56