pytest使用前置fixture运行后自定义Frozen描述符失效问题排查
这个问题的核心原因是你使用的ORM(从start_mappers/clear_mappers逻辑判断为SQLAlchemy这类数据映射器ORM)在启动映射时,会直接覆盖Patient类上你预定义的first_name、email类属性,替换为ORM自带的属性描述符(如SQLAlchemy的InstrumentedAttribute),你自己实现的Frozen描述符被直接替换失效,赋值时自然不会触发你写的只读校验逻辑。
手动实例化时没有启动ORM映射,描述符正常生效,所以可以正常抛出错误。只要session fixture先执行触发了start_mappers,后续所有使用Patient类的测试都会继承被ORM修改后的类属性,导致只读校验失效。
额外提示:你原有Frozen描述符的错误提示存在逻辑问题,显示的是赋值内容的类型名而非属性名,用户无法从报错判断是哪个属性触发了只读限制,后续优化时可以改为返回属性名self.storage_name。
方案1:改用__setattr__实现只读校验,避免被ORM覆盖
这种方案在实例层面拦截所有属性赋值,优先级高于类属性的描述符,不会被ORM的映射操作影响,兼容性最好:
class Patient: # 定义只读属性列表 _READONLY_ATTRS = {"first_name", "email"} def __init__(self, first_name: str, email: str, phone: str, age: int, gender: str, last_name="", weekly_meals: WeeklyMeals = None, nutrition_plan: NutritionPlan = None) -> None: self.first_name = first_name self.last_name = last_name self.email = email self.phone = phone self.age = age self.gender = gender self.weekly_meals = weekly_meals self.nutrition_plan = nutrition_plan self.receipt_line_mapping = dict() self.meals = list() def __setattr__(self, key, value): # 已存在的只读属性禁止修改,初始化阶段允许正常赋值 if key in self._READONLY_ATTRS and key in self.__dict__: raise AttributeError(f'readonly attribute {key}') super().__setattr__(key, value)
方案2:在ORM映射配置中保留自定义描述符
如果你使用的是SQLAlchemy,可以在列定义时指定descriptor参数,明确使用你自己的Frozen描述符,避免被ORM默认逻辑覆盖:
# ORM映射定义示例 from sqlalchemy.orm import mapper, Column, String from your_module import Patient first_name_col = Column(String) email_col = Column(String) mapper( Patient, patient_table_def, properties={ "first_name": first_name_col.with_descriptor(Frozen()), "email": email_col.with_descriptor(Frozen()) } )
方案3:测试隔离,拆分单元测试与集成测试
不需要ORM参与的纯领域模型单元测试不要依赖session fixture,也不要触发start_mappers逻辑,避免ORM修改你的原始模型类。可以用pytest的标记把测试分成单元测试、集成测试两类,分开执行避免互相影响。
内容的提问来源于stack exchange,提问作者Joaquim

