
Frappe Testing 测试框架深度指南TestConfig、TestRunner 与测试发现机制解析【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe导读Frappe 作为面向真实业务场景的 Low-code Web 框架其测试体系直接关系到每个应用的交付质量。本文以 frappe/testing/README.md 为核心骨架系统讲解frappe.testing模块中TestConfig、TestRunner、TestResult三大核心类以及discover_all_tests、discover_doctype_tests、discover_module_tests三个测试发现函数的设计与用法。读完本文你将掌握如何以编程方式驱动 Frappe 测试、测试分类与执行优先级的工作机制、测试环境准备/清理的完整流程并能通过bench run-tests命令的实际参数理解整个框架的运行脉络。模块总览一个完整的测试框架frappe.testing是一个为 Frappe 应用提供完整测试能力的模块覆盖测试发现discovery、执行execution、结果上报reporting与环境准备environment setup全流程。其核心能力可以从模块入口 frappe/testing/init.py 的文档字符串中确认主要包括TestConfig用于定制测试执行的配置类TestRunner继承unittest.TextTestRunner的主执行类附带 Frappe 特有的功能TestResult自定义测试结果类提供改进的输出格式与日志记录discover_all_tests / discover_doctype_tests / discover_module_tests分别用于发现指定 Frappe 应用中全部测试、特定 DocType 的测试、特定模块的测试。该模块通常由 Frappe 的 CLI 命令bench run-tests间接调用但同时也完全支持在代码中以编程方式驱动自定义测试执行场景。编程式用法三行代码跑通测试原文档给出的最小可用示例完整如下这也是理解整个框架的最佳切入点from frappe.testing import TestConfig, TestRunner, discover_all_tests config TestConfig(failfastTrue, verbose2) runner TestRunner(cfgconfig) discover_all_tests([my_app], runner) runner.run()这段代码的执行链路是构造配置TestConfig(failfastTrue, verbose2)创建配置对象开启失败即停创建运行器TestRunner(cfgconfig)初始化执行器注意TestRunner的构造签名中cfg: TestConfig是关键字参数runner.py 中将其传入unittest.TextTestRunner.__init__并把failfast等配置自动透传给底层 unittest 执行器发现测试discover_all_tests([my_app], runner)扫描my_app应用的所有测试模块并装载进 runner 内部的per_app_categories数据结构执行测试runner.run()实际运行已发现的测试套件。需要说明原示例中的verbose2在当前的TestConfig数据类定义config.py中并不存在verbosity 实际由TestRunner(verbosity...)或 CLI 层控制见下文main()中按日志级别动态设定 verbosity 的逻辑。在实际编程调用时建议显式传入verbosity参数。TestConfig 与 TestParameters两层配置模型TestConfig运行器级配置config.py 中定义了TestConfig数据类其字段与含义如下字段类型默认值作用profileboolFalse是否启用 cProfile 性能剖析failfastboolFalse遇到首个错误/失败即停止teststuple()需要执行的特定测试方法名集合用于过滤casestr | NoneNone指定测试模块中的特定 TestCase 类pdb_on_exceptionstuple | NoneNone指定需要在异常时进入 pdb 调试的异常类型元组selected_categorieslist[str][]仅运行选定的测试分类如 unit/integrationskip_before_testsboolFalse是否跳过 before_tests hooks 的执行test_serviceTestService | NoneNone只运行声明了所需外部服务的测试如 web-server其中pdb_on_exceptions会驱动 runner.py 的_apply_debug_decorators方法当配置了该字段时运行器会遍历套件中的每个测试方法用debug_on上下文管理器装饰器定义于 frappe/tests/classes/context_managers.py包裹指定异常类型从而在异常处自动进入调试器。TestParameters轻量模式的参数模型同一文件中还定义了TestParameters数据类服务于--lightmode轻量测试路径见 commands/testing.py字段包括site、app、module、doctype、module_def、verbose、tests、force、profile、junit_xml_output、doctype_list_path、failfast、case、test_service。它记录了用户从命令行传入的原始参数由FrappeTestLoaderloader.py消费按tests doctype module app的优先级生成测试套件。TestRunner分类执行与剖析测试分类与执行优先级TestRunner继承自unittest.TextTestRunner在 runner.py 中定义了分类优先级字典CATEGORY_PRIORITIES { unit: 1, integration: 2, functional: 3, }测试发现阶段会将每个测试按类别归入runner.per_app_categories[app][category]一个双层defaultdict值为unittest.TestSuite。执行阶段iterRun()会对每个应用按CATEGORY_PRIORITIES对分类排序未登记的分类按float(inf)排在最后跳过没有实际测试用例的分类_has_tests通过迭代套件判断调用_prepare_category为分类准备环境在_profile()上下文中逐分类 yield 套件交给上层执行。分类环境准备_prepare_categoryrunner.py使用分发器模式integration分类交给IntegrationTestPreparation实例old-frappe-test-class-category交给get_compat_frappe_test_case_preparation(cfg)兼容旧的FrappeTestCase写法相关 deprecation 警告在 discovery 阶段给出未知分类仅记录 debug 日志不做额外准备。性能剖析_profile()上下文管理器在cfg.profileTrue时启用cProfile.Profile()结束后用pstats按cumulative排序输出统计帮助定位耗时测试。测试发现三大发现函数discovery.py 提供三个发现函数均以runner作为装载目标并返回 runner便于链式调用。三者都用debug_timer装饰器utils.py记录耗时日志。discover_all_tests全应用扫描def discover_all_tests(apps: list[str], runner) - TestRunner:实现要点接受字符串或列表内部统一转为列表通过os.walk(frappe.get_app_path(app))遍历应用目录跳过隐藏目录及node_modules、locals、public、__pycache__跳过 doctype boilerplate 目录收集所有以test_开头、以.py结尾且文件名不是test_runner.py的文件将文件路径转换为模块名后调用_add_module_tests装载。discover_doctype_tests按 DocType 精确发现def discover_doctype_tests(doctypes: list[str], runner, app: str, force: bool False) - TestRunner:实现要点通过frappe.db.get_value(DocType, doctype, module)查询 DocType 所属模块查不到则抛出TestRunnerError(Invalid doctype ...)通过Module Def校验 DocType 归属应用若未指定app则自动采用 DocType 所属应用若指定了不匹配的应用则抛出TestRunnerError防止跨应用误跑使用frappe.modules.utils.get_module_name(doctype, module, test_)拼出测试模块名例如test_doctypeforce为真时删除 DocType 记录用于重新生成测试数据。discover_module_tests按模块精确发现def discover_module_tests(modules: list[str], runner, app: str) - TestRunner:实现要点从模块名如my_app.tests.test_something中取首段作为模块所属应用并做与 doctype 路径一致的应用归属校验然后直接importlib.import_module装载该模块。_add_module_tests分类的最终裁决者这是三大发现函数的公共落点discovery.py它完成导入模块并加载测试若配置了case用loadTestsFromTestCase(getattr(module, case))只加载指定测试类否则loadTestsFromModule加载全模块方法级过滤cfg.tests中指定的测试方法名才保留服务过滤requires_selected_test_service与apply_test_service_skips来自 frappe/tests/utils/test_capabilities.py处理测试的外部服务依赖分类判定IntegrationTestCase→integrationUnitTestCase→unit其余归入unspecified-category若类直接继承自FrappeTestCase则归入old-frappe-test-class-category并触发 v17 弃用警告分类过滤selected_categories非空时仅保留匹配分类的测试最终写入runner.per_app_categories[app][category]。注意IntegrationTestCase与UnitTestCase从 frappe/tests/init.py 导出该文件同时定义了global_test_dependencies [User]这是环境准备阶段创建全局测试记录的依据。TestResult面向可读性的结果输出result.py 中自定义的TestResult继承unittest.TextTestResult核心特性着色输出通过click.style为不同结果上色——成功✔绿色、错误/失败✖红色、跳过白色慢测试标记SLOW_TEST_THRESHOLD 2秒超过阈值的方法在输出后追加红色耗时如(2.314s)计时基于time.monotonic()按测试类分组展示startTest中检测到测试类切换时打印类名并输出该类 setUpClass/tearDownClass 期间被缓冲的 stdout/stderr▹/▸前缀还打印_newly_created_test_records记录的测试数据创建统计缓冲输出管理startTestRun/stopTestRun/startTest/stopTest接管 stdout/stderr 重定向保证模块级与类级输出排在首个测试之前、单个测试输出紧随该测试traceback 局部变量tb_locals True让失败 traceback 附带局部变量便于定位错误列表printErrors以ERROR/FAIL标签红底分组打印错误详情。此外还保留了FrappeTestResult较精简的变体供--lightmode路径使用。环境准备与清理测试前的三件套environment.py 负责测试环境的生命周期管理_initialize_test_environment(site, config)frappe.init(site)初始化站点未连接数据库时frappe.connect()随后_disable_scheduler_if_needed()关闭调度器并记录用户原本的开关状态frappe.clear_cache()清缓存toggle_test_mode(True)进入测试模式并依据日志级别设置frappe.flags.print_messages与frappe.flags.tests_verbose_cleanup_after_tests()若调度器原本由框架关闭非用户显式关闭则恢复enable_scheduler()frappe.db.commit()结束事务并再次清缓存IntegrationTestPreparation集成测试分类执行前的准备器——除非skip_before_testsTrue否则执行该应用的before_testshooksfrappe 框架自身的before_tests frappe.utils.install.before_tests定义于 frappe/hooks.py随后加载{app}.tests模块中的global_test_dependencies列表逐个调用make_test_records(doctype, commitTrue)创建全局测试记录frappe 应用默认依赖User。CLI 落地bench run-tests 如何串联整个框架frappe.testing的实际主战场是 CLI。bench run-tests命令定义在 frappe/commands/testing.py其完整选项如下选项作用--app指定要测试的 App--doctype指定 DocType--module-def运行某个 Module Def 下所有 DocType 的测试--case选择特定 TestCase--doctype-list-path指向含 DocType 列表的.txt文件如erpnext/tests/server/agriculture.txt--test指定具体测试可多次使用--module在指定模块内运行测试--debug关闭缓冲在 breakpoint 或异常时进入 pdb--profile启用性能剖析--coverage启用覆盖率收集--skip-before-tests不运行 before_tests hooks--junit-xml-output输出 JUnit XML 报告到指定路径--failfast首个错误/失败即停止--test-categoryunit/integration/all默认all--test-service只运行声明了指定外部服务的测试--lightmode跳过所有 before 测试准备走轻量执行路径其核心执行流程main()testing.py前置检查allow_tests站点配置或 CI 环境变量为真才允许运行否则提示bench --site {site} set-config allow_tests true开启互斥校验--doctype、--doctype-list-path、--module-def、--module四者互斥同时指定会报click.UsageError构造TestConfigprofile/failfast/tests/case/pdb_on_exceptions/selected_categories/skip_before_tests/test_service一一映射_initialize_test_environment准备环境按优先级分支调用发现函数doctype或doctype_list_path→discover_doctype_testsmodule_def→ 先查该 Module Def 下所有可导入测试的非子表 DocType 再批量发现_run_module_def_testsmodule→discover_module_tests否则 →discover_all_tests(frappe.get_installed_apps(), runner)遍历runner.iterRun()逐应用逐分类执行可配合xmlrunner输出 JUnit XML--junit-xml-outputfinally中_cleanup_after_tests()兜底清理。若启用--lightmode则走run_tests_in_light_mode分支不执行 before_tests hooks 与全局测试记录创建直接用FrappeTestLoaderFrappeTestResult快速执行适合对测试基础设施依赖极小的场景。此外 testing.py 还提供bench run-parallel-tests按 build 分片并行执行支持 Orchestrator 模式与bench run-ui-tests封装 Cypress 运行 UI 测试自动安装 cypress、cypress-drag-drop、cypress-real-events 等依赖两个配套命令。小结frappe.testing是一套unittest 之上、面向 Frappe 业务的完整测试框架TestConfig用数据类集中管理执行策略TestRunner通过分类优先级、环境准备分发器、pdb 注入与 cProfile 剖析扩展标准 unittest三大发现函数覆盖全应用 / 按 DocType / 按模块三种粒度并内置应用归属校验TestResult让测试输出具备颜色、分组与耗时提示而environment.py则保证了调度器、缓存、hooks 与全局测试记录在测试前后的确定性。理解这套机制后无论是直接调用 API 编写自定义测试工具还是通过bench run-tests各种选项组合精确控制测试范围都能得心应手。【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考