资讯动态

notebooklm-py 测试 Monkeypatch 治理策略(ADR-0007)实战指南:构造函数注入与 `make_fake_core` 工厂

发布时间:2026/9/13 18:39:25 来源:尧图企业网站定制
notebooklm-py 测试 Monkeypatch 治理策略ADR-0007实战指南构造函数注入与make_fake_core工厂【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py导读本文档以 notebooklm-py 仓库的 ADR-0007Test-monkeypatch policy 为骨架系统讲解该开源项目如何通过构造函数注入 测试工厂替代传统monkeypatch.setattr与AsyncMock赋值式的测试打桩从而消除测试猴子补丁引力test-monkeypatch gravity这一架构病害。读完本文你将掌握哪些打桩模式在本仓库被列为禁止项及其底层原因、tests/_fixtures/中make_fake_core工厂的正确用法、双 meta-lint 守卫test_no_forbidden_monkeypatches.py与test_string_patch_ratchet.py如何把策略固化为一票否决的全局不变量以及项目如何分阶段退役三处生产 shim 完成迁移闭环。背景从架构审计到测试猴子补丁引力ADR-0007 的诞生源于一次内部架构审计。审计将test-monkeypatch gravity测试猴子补丁引力列为项目三大架构病害之首测试套件偏好的打桩方式会把模块边界焊死迫使每一次接缝抽取seam extraction都要额外携带一个写穿透门面write-through facade、一组属性桥property bridge或一个并行实现不变量架构演进因此被测试债务绑架。审计在 HEAD22355cf给出的实测计数如下模式数量tests/下monkeypatch.setattr站点总数236字符串目标 patch ——monkeypatch.setattr(notebooklm.X.Y, …)58对象属性 patch ——monkeypatch.setattr(obj, attr, …)152直接属性赋值 ——target.rpc_call AsyncMock(…)等63tests/unit/test_auth_*.py拆分前体量原 4,090 LOC · 70 处 patchtests/unit/cli/test_session.py体量4,431 LOC这些模式的共同根因是生产代码在构造完成之后被外部改写而不是在构造期间接收协作者。正如 ADR-0001 所记录的早期_core.py曾承载 90 方法、约 1,800 行代码为了不一次性打破约 273 处测试耦合站点项目曾以属性桥property bridge作为过渡——monkeypatch.setattr(core, _save_lock, fake)这类负载惯用法必须通过属性读写转发到真正存放状态的接缝。ADR-0007 正是这个过渡策略的终局从策略上禁止新测试继续制造这种引力。三处被退役的生产 shim在 ADR-0007 写就时src/notebooklm/下有三处工件纯粹是为了让旧的打桩风格在架构重构中继续存活它们在迁移完成后已全部被删除见 ADR Status 区块与 ADR-0014_AuthFacadeModuleauth.py:288-339—— 一个types.ModuleType子类其__setattr__会把对notebooklm.auth.name的写入镜像到_auth/storage、_auth/account、_auth/keepalive、_auth/refresh及 header/cookie/policy 等接缝。它的存在是因为约 152 处 patch 直接瞄准notebooklm.auth命名空间如果门面变成被动 re-export这些 patch 会静默失效。该 shim 已由 ADR-0003 提出、ADR-0014 收尾退役。_core.py属性桥动物园约 450-774 行约 324 LOC—— 把Session上遗留的私有属性名读写转发到真正拥有状态的接缝模块用于兼容monkeypatch.setattr(core, _save_lock, fake)这类负载惯用法。已在 session-shrink 阶段ADR-0001 / ADR-0002退役。cli/session_cmd.py代理块约 141-490 行约 350 LOC—— 一层模块级函数镜像服务层符号让monkeypatch.setattr(notebooklm.cli.session_cmd.X, fake)能抵达cli/services/login.py的真实实现。这是最后一个退役的 shimissue #1367移除内容包括_resolve_paths_helper优先级链、其双 fixture 以及纯 re-export 表面。从源码结构看这三处工件的共同模式是为测试而生的生产代码它们不承载业务价值只为让已过时的测试惯用法继续通过。策略的目标正是让这类工件变为有限、可退役的产物而非永久固定装置。核心决策构造函数注入取代事后打桩ADR-0007 的核心决策只有一句话凡是需要在Session或其子客户端NotebooksAPI、SourcesAPI、ArtifactsAPI、ChatAPI、ResearchAPI、NotesAPI、SettingsAPI、SharingAPI上替换协作者的测试必须通过tests/_fixtures/中的工厂基座以构造函数注入的方式获取协作者。ADR 给出的规范示例from tests._fixtures import make_fake_core async def test_notebooks_list_returns_payload() - None: fake make_fake_core(rpc_callAsyncMock(return_value[fake_payload])) api WebNotebooksAPI(fake.rpc_executor) result await api.list() fake.rpc_executor.rpc_call.assert_awaited_once()这与仓库中真实测试的写法完全一致。例如 tests/unit/test_notebook_api.py 中WebNotebooksAPI(core.rpc_executor, supervisorcore)的构造方式以及 tests/unit/test_api_coverage.py 中ChatAPI的组装方式core make_fake_core(rpc_callrpc_call) return WebChatAPI( rpccore.rpc_executor, supervisorcore, transportMagicMock(), reqidMagicMock(), loop_guardMagicMock(specLoopGuard), notebooksMagicMock(), )生产侧与之对应的契约见 src/notebooklm/_web/contracts.py 中的RpcCallerProtocol——rpc_call(method, params, ...)是每个 Web 特性 API 消费的窄 RPC 分发表面而 src/notebooklm/_runtime/contracts.py 中的LoopGuardProtocol 则定义了assert_bound_loop()这一循环亲和断言表面。测试工厂正是围绕这些窄 Protocol 构建的。六类被禁止的测试打桩模式策略自 2026-06 的覆盖更新起把禁止清单扩展为六类全部由 meta-lint tests/_guardrails/test_no_forbidden_monkeypatches.py 强制实施字符串目标 monkeypatch 进入notebooklm命名空间——monkeypatch.setattr(notebooklm.X.Y, ...)。依赖导入字符串解析存储位置迁移时静默失效。经由notebooklm模块的对象属性 monkeypatch——monkeypatch.setattr(notebooklm.X, attr, ...)。与 1 相同的失效模式只是写法不同。对传输/RPC 表面的直接 AsyncMock 属性赋值——target.rpc_call AsyncMock(...)、target._perform_authed_post AsyncMock(...)、target._begin_transport_post AsyncMock(...)、target._finish_transport_post AsyncMock(...)、target.query_post AsyncMock(...)包括self._client._target.rpc_call AsyncMock(...)这类链式变体。这在构造之后改写实例而非在构造时注入假件。unittest.mock字符串目标 patch 进入私有内部issue #1325 新增——mock.patch(notebooklm._private…)/patch(notebooklm._private…)/patch.object(notebooklm._private…, ...)。与 1 相同的导入字符串失效模式只是经由unittest.mock通道。范围限定在首个组件为私有notebooklm._*的路径深层叶私有归模式 5公共门面上的 patch 不在此规则的禁止范围内其数量由字符串 patch 棘轮冻结见后文。深层叶unittest.mock字符串目标 patch 进入私有内部2026-06 覆盖更新新增——patch(notebooklm.public…._private…)例如patch(notebooklm.cli.session_cmd._sync_server_language_to_config)。这是 #1481 CLI 命令迁移事后复盘暴露的盲区命令体一旦迁往兄弟模块这类 patch 就会静默 no-op。模式 4 的正则锚定在notebooklm\._对公有组件之后才是私有组件的深层叶结构结构性失明因此新增模式 5并与模式 4 刻意保持不相交disjoint。经本地别名的私有属性名patch.object2026-06 覆盖更新新增——patch.object(alias, _private_attr, …)。对象引用是真实的但属性名是字符串钉死了内部属性布局重命名后它会在属性袋型假件上继续通过却什么都没 patch 到。只有完整双下划线名如__aenter__豁免——那是 Python 协议表面而非内部布局__private风格的名字仍然被标记。lint 的实现要点每个测试文件被当作单个字符串扫描多行monkeypatch.setattr(\n notebooklm.X, …)也会命中因为\s在 Python 正则引擎中已覆盖换行命中时报告(file, line, matched pattern)三元组以便直接定位。六个正则全部带负向环视(?![\w.])区分patch(与monkeypatch(/dispatch(等形似调用并覆盖target关键字拼写、r…字符串前缀拼写与 f-string 计算目标模式 h。make_fake_core工厂唯一的官方打桩基座srctests/_fixtures/fake_core.py 提供make_fake_core(**overrides) - FakeSession其设计要点显式属性袋而非 spec 型 MagicMockFakeSession是一个普通类构造器只设置传入的属性。访问生产代码实际未使用的属性会得到清晰的AttributeError而不是MagicMock自动凭空生成属性——这保住了窄 Protocol 的类型安全收益也让评审者可以把工厂与子客户端结构上所需的窄 Protocol 逐一对照。异步表面默认AsyncMock同步表面默认MagicMock返回值均为良性默认值测试只覆盖自己真正会用到的那片切片。rpc_executor.rpc_call镜像生产组装NotebookLMClient把composed.executor存为self._web_runtime.executor并传给每个特性 API工厂因此同时暴露fake.rpc_call历史便捷路径和fake.rpc_executor.rpc_call两者指向同一个 mock两条断言路径观察到相同的调用。rpc_call关键字保留为便捷入口传入时会被解包为rpc_executorSimpleNamespace(rpc_callvalue)与生产形状一致。未知关键字立即报错make_fake_core(rpc_cal...)这类笔误会抛出TypeError列出已知属性而不是落成一个永远读不到的属性。工厂内部做了_journalize_rpc_mock封装让测试 RPC mock 模拟生产终端的调度交接bound_operation_journal_entries()的mark_dispatched使调用日志语义与真实链路一致。默认属性槽最小化Phase 7 重构后默认字典从 broad-Session 时代的 25 项缩减为特性实际会消费的最小集合auth、kernel、rpc_call/rpc_executor、assert_bound_loop、is_closing、operation_scope、spawn_child、_drain_hooks/register_drain_hook、record_upload_queue_wait、get_source_ids。新增属性要求存在真实测试站点消费它镜像 ADR-0013 的共享 Protocol 晋升标准。tests/_fixtures/conftest.py 刻意只暴露两个薄 pytest fixturefake_core默认工厂调用与make_fake_core工厂本身供需要逐调用覆盖的测试使用。这个刻意缩小的 fixture 表面避免了完整 fixture 菜单的失败模式——每个覆盖组合配一个无参 fixture 会以 O(测试数 × 覆盖数) 爆炸比它要替代的 monkeypatch 蔓延更糟。双 meta-lint 守卫从文件级豁免到全局不变量ADR-0007 的执法分两把闸全部以 pytest 测试形式常驻无 skip 标记每次本地uv run pytest都会执行闸一test_no_forbidden_monkeypatches.py禁止模式闸主门test_no_forbidden_monkeypatches_outside_allowlist扫描tests/下每个.py文件跳过_guardrails、_fixtures、cassettes、fixtures四个目录命中模式 1-4 即报错。文件级允许列表_ALLOWLIST从 PR 起步时的 49 个文件审计标记的违规者并集被逐波清零issue #1376test_allowlist_stays_empty断言_ALLOWLIST frozenset()——严格钉死空frozenset哨兵而非假值防止未来某次重构把允许列表改回可变set()时守卫被静默削弱。模式 5/6 各带自己的基线允许列表_DEEP_LEAF_ALLOWLIST、_PATCH_OBJECT_PRIVATE_ATTR_ALLOWLIST落地时以实测违规者填充、机会式排空最终也归零test_baselined_allowlists_stay_empty与test_baselined_allowlist_paths_exist分别钉住必须为空与路径必须存在。三个允许列表全部归零后每个 gate 都从文件级豁免变成了全局不变量任何新违规一票否决。闸二test_string_patch_ratchet.py字符串 patch 总量棘轮这个闸管的是禁止规则覆盖不到的公共叶子字符串目标 patch如patch(notebooklm.cli.source_cmd.NotebookLMClient)——#1481 复盘显示约 137 处这类公共叶子 patch 在命令体迁往兄弟模块后同样静默 no-op。棘轮规则零增长每个基线文件钉死在实测站点数超出上限即失败。未入基线的文件预算为 0——新测试文件根本不允许出现字符串目标 patch。只许下调文件降到上限以下必须把上限收紧到 N归零则删除条目回收的空间永不回潮。无陈旧条目基线路径必须仍存在重命名或删除必须同步更新基线。基线曾钉住 52 个文件、共 768 处站点现已全部排空——STRING_PATCH_CEILINGS为空 dict未入基线预算为零适用于每个文件棘轮事实上变成了一刀切的禁令。test_ceilings_stay_empty钉住空 map因为三条棘轮规则都会被按当前计数重新入基线同时满足没有这条守卫回收的地盘会一块一块地重新长回去。两把闸在各自的 remediation 文案中互相引用对方的禁止形状确保违规者被导向合法接缝tests/_fixtures/构造函数注入或对本地导入别名公共属性的patch.object而不是掉进另一把闸的禁区内。文件级而非站点级行号级的允许列表设计是为了在 rebase 与文件重排时保持稳定避免行号漂移产生虚假合并冲突见 ADRAlternatives consideredper-site allowlist entries。迁移路径与后果合法替代接缝别名seam-alias形式并非所有打桩需求都适合构造函数注入。对于不直接构造目标模块的合法unittest.mock.patch需求例如跳过notebooklm._auth.refresh.X中的真实延时官方推荐迁移目标是对象属性形式 本地导入的接缝别名from notebooklm._auth import refresh as refresh_seam monkeypatch.setattr(refresh_seam, public_symbol, fake)lint 接受这种形式因为别名变量提供了真实的 Python 对象引用而不是 lint 无法校验的字符串。若该别名上的属性名本身是私有布局如_render则会落入模式 6 的禁止范围因此必须选公共属性名或直接走构造函数注入。迁移分期与最终状态迁移被显式编排为多阶段而非一刀切D1 PR-1 落地工厂基座与 meta-lintD1 PR-2 迁移 auth 侧测试6 个文件并删除_AuthFacadeModuleD1 PR-3 迁移 CLI 侧测试7 个文件并删除代理块与属性桥残余约 35 个文件在 PR-3 收尾时重新评估。替代方案中明确拒绝了单 PR 大爆炸重写约 273 处站点test_auth.py有 4,090 LOC、cli/test_session.py有 4,431 LOC原子化改造会阻塞期间所有其他测试触碰型 PR且无法增量验证分期迁移让每个 PR 的 pytest 结果都能证明下一次 shim 移除是安全的。截至 ADR 的 Status 区块迁移已全部完成三处 shim 全部删除且无测试残留、meta-lint 的文件级允许列表清零、字符串 patch 棘轮的 52 个基线文件768 处站点全部排空、三个 stays-empty 守卫钉住零值——ADR-0007 从进行中的迁移回到普通的Accepted状态策略与 lint 无限期有效只有迁移本身宣告结束。想要的后果新测试无法重建引力井meta-lint 在 PR 评审阶段就拦截违规模式。子客户端可隔离测试WebNotebooksAPI(fake.rpc_executor)无需启动真实Session、无需导入字符串解析、无需构造后改写。三处 shim 完成了从永久固定装置到已退役工件的身份转变。测试 diff 更小更可读覆盖一个协作者现在是一个关键字参数而不是每个被替换符号一行monkeypatch.setattr。不想要的后果设计上的代价依赖字符串目标 patch 替换非直接构造模块导入的测试需要逐个吸收大多迁移到时钟类协作者的构造函数注入或接缝别名形式。工厂与src/notebooklm/_runtime/contracts.py及特性模块中剩余单消费者接缝存在少量重复保持对齐是人工步骤生成式方案会把测试管道耦合进源码破坏策略想恢复的测试不钉形状属性。最初 49 个文件的允许列表是实打实的迁移工程量需要显式排期。在仓库中验证这套策略想亲手验证 ADR-0007 的执行情况可以阅读规范示例所在文件 tests/_fixtures/fake_core.py 与 tests/_fixtures/conftest.py查看执法闸 tests/_guardrails/test_no_forbidden_monkeypatches.py六个禁止正则 三个归零允许列表 stays-empty 守卫与 tests/_guardrails/test_string_patch_ratchet.py字符串 patch 总量棘轮观察真实使用方式如 tests/unit/test_notebook_api.py、tests/unit/test_api_coverage.py、tests/unit/test_artifact_completeness.py、tests/unit/test_artifact_generation_prompt.py 等大量以make_fake_core(rpc_callAsyncMock(return_value...))开场的测试关联阅读 ADR-0001属性桥策略的历史与退役、ADR-0014特性本地运行时适配器与 ADR-0033测量边界以及 docs/refactor-history.mdPhase 7 迁移计划对工厂默认槽的缩减记录。这套禁止模式清单 工厂基座 双 meta-lint 守卫 归零允许列表的组合拳是大型异步代码库中治理测试打桩债务的完整参考实现它把架构决策从文档落成可执行的一票否决闸并用量化基线236 → 0、768 → 0、49 → 0证明迁移可被逐波验证、回收的地盘可被永久锁定。【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价