BookStack PHP 测试实战指南从测试环境搭建到用例编写与源码原理【免费下载链接】BookStackNOW MANAGED ON CODEBERG项目地址: https://gitcode.com/gh_mirrors/bo/BookStack本指南以 dev/docs/php-testing.md 为核心系统讲解 BookStack 基于 PHPUnit 与 Laravel 测试框架的完整测试体系如何准备独立的mysql_testing测试数据库、如何运行与过滤测试、以及如何遵循项目约定编写功能测试。文中所有配置与结论均以当前仓库的实际源码composer.json、phpunit.xml、app/Config/database.php、tests/TestCase.php 等为依据帮助你在本地开发环境快速跑通测试并写出符合项目风格、可维护的测试用例。一、测试体系概览功能测试为主PHPUnit Laravel 为基座BookStack 的测试用例全部定义在仓库根目录的tests/目录下。从 composer.json 的require-dev可以看到测试栈的关键依赖phpunit/phpunit: ^11.5—— 测试执行框架laravel/framework: ^v12.26.4—— Laravel 自带的Illuminate\Foundation\Testing扩展TestCase、DatabaseTransactions、actingAs、assertSee等mockery/mockery: ^1.5—— 对象级 Mock 库ssddanbrown/asserthtml: ^3.1—— 基于 DOM 的 HTML 断言库文档中withHtml的底层fakerphp/faker、nunomaduro/collision、larastan/larastan、squizlabs/php_codesniffer—— 数据生成、错误渲染、静态分析与代码规范工具。与纯单元测试不同BookStack 的测试mostly functional大部分是功能测试它们模拟真实用户动作与系统组件交互因此必须依赖真实数据库。为了让测试不污染开发环境数据项目单独配置了mysql_testing数据库连接这就是下文环境准备的核心。二、测试环境准备2.1 独立的mysql_testing数据库连接测试专用的数据库连接定义在 app/Config/database.php 中mysql_testing [ driver mysql, url env(TEST_DATABASE_URL), host 127.0.0.1, database bookstack-test, username env(MYSQL_USER, bookstack-test), password env(MYSQL_PASSWORD, bookstack-test), port $mysqlPort, charset utf8mb4, collation utf8mb4_unicode_ci, prefix , prefix_indexes true, strict false, ],默认连接参数为参数默认值Host127.0.0.1Usernamebookstack-testPasswordbookstack-testDatabasebookstack-test同时 phpunit.xml 中通过server nameDB_CONNECTION valuemysql_testing/将测试运行时的默认连接强制指向该连接并设置了CACHE_DRIVERarray、SESSION_DRIVERarray、QUEUE_CONNECTIONsync、MAIL_DRIVERarray等轻量驱动让测试不依赖 Redis、队列和真实邮件服务。DISABLE_EXTERNAL_SERVICEStrue则从全局上阻止测试向外部系统发起真实请求。你需要在本地 MySQL 中创建上述数据库并为该账号授权若默认参数不合适可通过TEST_DATABASE_URL环境变量或写入.env覆盖连接格式为TEST_DATABASE_URLmysql://username:passwordhost-name:port/database-name该变量会直接映射到mysql_testing连接的url字段见上源码因此端口、库名均可自由指定。2.2 迁移与填充测试数据测试库需要先迁移并填充种子数据项目在 composer.json 中封装了现成脚本refresh-test-database: [ putenv APP_TIMEZONEUTC, php artisan migrate:refresh --databasemysql_testing, php artisan db:seed --classDummyContentSeeder --databasemysql_testing ]执行composer refresh-test-database会完成重置测试库所有表 → 运行全部迁移database/migrations/下 130 个迁移文件→ 以 DummyContentSeeder 填充演示数据。DummyContentSeeder会创建管理/编辑/查看者等系统角色与示例用户、书架/书/章节/页面等实体这些数据正是EntityProvider、UserRoleProvider等测试辅助类取数的来源。补充如果你使用 PHPUnit 的并行测试--parallel可以用composer t-reset即php artisan test --recreate-databases自动重建每个并行 worker 的数据库避免并行数据库命名冲突。2.3 多数据库版本兼容验证Docker 方案仓库在 dev/docker/db-testing/run.sh 提供了一套 CI 风格的多数据库兼容测试脚本它会依次针对mysql:8.0、mysql:8.4、mysql:9.5以及mariadb:10.6/10.11/11.4/11.8/12.0等镜像启动容器通过TEST_DATABASE_URLmysql://bookstack:bookstackbs-dbtest-db:3306注入连接随后执行artisan migrate --force --databasemysql_testing artisan db:seed --force --classDummyContentSeeder --databasemysql_testing vendor/bin/phpunit这说明TEST_DATABASE_URL的实际用法与测试库的迁移/填充流程是项目官方验证过的你可以直接用同一思路在任意 MySQL/MariaDB 实例上搭建测试环境。三、运行测试3.1 两种基本运行方式在应用根目录执行# 方式一通过 composer 脚本推荐 composer test # 方式二直接调用 PHPUnit php vendor/bin/phpunit两种方式等价——composer.json 中test: phpunit只是对 PHPUnit 的一层封装。测试套件定义在 phpunit.xml它把整个./tests/目录注册为 Application Test Suite。3.2 按文件、目录与名称过滤PHPStorm 等 IDE 内置了按文件/目录/类运行测试的支持命令行下则可以使用 PHPUnit 的路径与--filter参数# 运行 ./tests/HomepageTest.php 文件中的全部测试 php vendor/bin/phpunit ./tests/HomepageTest.php # 运行 ./tests/User 目录下的全部测试 php vendor/bin/phpunit ./tests/User # 按测试方法名过滤snake_case php vendor/bin/phpunit --filter test_default_homepage_visible # 按测试类名过滤 php vendor/bin/phpunit --filter HomepageTest--filter支持正则表达式因此也能组合过滤例如--filter /(test_view|test_delete)/。3.3 以弃用Deprecation模式运行当需要验证代码在 PHP 弃用告警下的表现通常是依赖升级、PHP 大版本升级等维护任务而非日常 PR时可以取消注释 tests/TestCase.php 中setUp()里的// $this-withoutDeprecationHandling();一行将弃用告警升级为失败。源码注释也提醒该开关不能常开因为部分弃用只能在上游依赖中修复。3.4 测试运行前的.env约束虽然测试的绝大多数环境变量由 phpunit.xml 的phpserver区块注入如APP_ENVtesting、APP_KEY、AUTH_METHODstandard、STORAGE_TYPElocal等但TEST_DATABASE_URL需要你自行在.env或 shell 环境中提供若默认本机账号满足要求则无需配置。四、编写测试约定与规则4.1 基本规范测试类必须位于tests/目录下类名以Test结尾所有测试类必须继承Tests\TestCase即 tests/TestCase.php它组合了CreatesApplication、DatabaseTransactions每个测试在事务中执行、结束后回滚保证数据隔离与TestsHtmlHTML 断言能力测试方法使用 snake_case 命名、以test_开头且必须是 public 方法。Tests\TestCase的createApplication()还额外注册了 tests/Helpers/TestServiceProvider.php用于在测试环境中注入辅助服务。4.2 项目通用的四条测试原则原文档明确了团队遵守的通用规则结合源码可以进一步理解其动机所有外部远程资源必须 Mock包括 HTTP 调用、LDAP 连接等。TestCase为此内置了mockHttpClient()基于 app/Http/HttpRequestService.php 的mockClient()内部返回HttpClientHistory记录请求历史以及partialMockService()基于 Mockery 的部分 Mock方便仅替换服务中的某个方法。DISABLE_EXTERNAL_SERVICEStrue也在配置层面兜底。优先硬编码期望文本与 URL例如直接断言My Recently Viewed、/books这类字面量而不是通过动态引用拼接从而对系统文本/路由的意外变更保持高敏感度。非必要不使用 admin 用户只有真正需要管理员权限时才用asAdmin()其余场景用 editor/viewer 等低权限用户确保权限系统在每个测试中被真实激活与校验。断言不存在必须搭配存在确认例如使用assertDontSee(TextAfterChange)时应同时有assertSee(TextBeforeChange)做正向确认防止断言因整段内容缺失而假通过。4.3 登录身份辅助方法$this-asAdmin(); // 以管理员身份运行测试 $this-asEditor(); // 以编辑者身份 $this-asViewer(); // 以查看者身份这三个方法定义在 tests/TestCase.php内部调用 Laravel 的actingAs()并分别从UserRoleProvider取出对应系统角色用户asAdmin()→UserRoleProvider::admin()取系统角色admin关联的第一个用户tests/Helpers/UserRoleProvider.phpasEditor()→Role::getRole(editor)下的用户asViewer()→Role::getRole(viewer)下的用户。此外UserRoleProvider还提供了guest()系统访客用户、newUser()新建空白用户、newUserWithRole()新建用户全新角色、createRole()按权限名数组建角色等方法用于构造更细粒度的权限场景。4.4 四大 Provider 属性entities/users/permissions/filesTests\TestCase::setUp()tests/TestCase.php初始化了四个受保护属性覆盖了实体、用户、权限、文件四大类测试场景$this-entitiestests/Helpers/EntityProvider.php—— 提供书架/书/章节/页面的获取与创建动作且内部维护fetchCache保证每次调用返回未被本测试取用过的全新模型避免同一测试内重复操作同一实体$this-entities-page(); // 任意一页 $this-entities-pageWithinChapter(); // 章节内页面 $this-entities-bookHasChaptersAndPages(); // 含章节和页面的书 $this-entities-newBook(); // 新建书 $this-entities-newPage([name ..., html ...]); // 新建并发布页面 $this-entities-newDraftPage(); // 新建草稿页 $this-entities-createChainBelongingToUser($user); // 创建从书到页且归属指定用户的实体链 $this-entities-sendToRecycleBin($entity); // 将实体送入回收站$this-userstests/Helpers/UserRoleProvider.php—— 各类用户与角色能力详见 4.3。$this-permissionstests/Helpers/PermissionsProvider.php—— 系统与内容权限相关操作$this-permissions-makeAppPublic(); // 将应用设为公开访问 $this-permissions-grantUserRolePermissions($user, [bookshelf-view-all]); // 授予角色权限 $this-permissions-removeUserRolePermissions($user, [...]); // 移除角色权限 $this-permissions-changeEntityOwner($entity, $newOwner); // 变更实体所有者 $this-permissions-setEntityPermissions($entity, [view], [$role]); // 设置实体级权限 $this-permissions-regenerateForEntity($entity); // 重建实体权限其中setEntityPermissions会先清空实体权限再以默认全部拒绝 指定角色放行的方式构造最小化权限环境见 PermissionsProvider.php 中role_id 0的默认拒绝条目用于精确验证权限边界。$this-filestests/Helpers/FileProvider.php—— 文件与上传相关$this-files-uploadedImage(name.png); // 构造图片上传 $this-files-uploadedTextFile(name.txt); // 构造文本附件上传 $this-files-uploadGalleryImage($this, name.png); // 发起画廊图片上传请求 $this-files-uploadAttachmentFile($this, file.txt); // 发起附件上传请求 $this-files-pngImageData(); / $this-files-jpegImageData(); // 读取测试图片原始字节测试用文件统一存放在 tests/test-data/含 png/jpg/gif/avif/ttf/base64 等素材。4.5 基于 DOM 的 HTML 断言withHtmlwithHtml($resp)返回一个基于ssddanbrown/asserthtml库的 HTML 断言对象可按 CSS 选择器定位元素后再断言比字符串级assertSee更精确$this-withHtml($this-get(/))-assertElementContains(p[idtop], Hello!);以 tests/HomepageTest.php 中的真实用法为例$this-withHtml($homeVisit)-assertElementContains(.content-wrap, $shelf-name); $this-withHtml($homeVisit)-assertElementNotContains(.content-wrap, $book-name);TestCase还封装了基于withHtml的快捷断言如assertNotificationContains($resp, $text)断言.notification[rolealert]元素包含指定提示文本见 tests/TestCase.php。4.6 其他高频断言辅助tests/TestCase.php 中还提供了大量开箱即用的断言与工具方法$this-assertPermissionError($resp); // 断言响应为权限错误403 或带错误提示的重定向 $this-assertNotPermissionError($resp); // 断言非权限错误 $this-assertSessionError(message); // 断言 session 中存在指定 error 通知 $this-assertSessionHas(key); // 断言 session 包含某键 $this-assertActivityExists(page_create, $entity); // 断言活动记录activities 表存在某类型条目 $this-assertDatabaseHasEntityData(page, [...]) // 断言 entities 表及其细分表的实体数据 $this-assertArrayMapIncludes($subset, $map); // 断言数组是另一数组的子集 $this-setSettings([app-homepage $page-id]); // 快捷写入系统设置 $this-runWithEnv([APP_THEME x], $callback); // 在指定环境变量下运行回调结束后自动还原含并行数据库兼容处理 $this-usingThemeFolder($callback); // 创建临时主题目录并注入 APP_THEME 后执行回调 $this-withTestLogger(); // 启用可捕获日志的测试日志驱动返回 TestHandler 供断言例如 tests/HomepageTest.php 就展示了assertSessionHas(success)/assertSessionMissing(error)组合判断删除操作是否成功$pageDeleteReq $this-delete($customPage-getUrl()); $pageDeleteReq-assertStatus(302); $pageDeleteReq-assertSessionHas(success); $pageDeleteReq-assertSessionMissing(error);runWithEnv的实现尤其值得注意tests/TestCase.php它通过Env::disablePutenv()直接操作$_SERVER注入变量、刷新应用实例并在并行测试场景下恢复mysql_testing的数据库名后开启事务确保环境变量被还原且数据库状态不串扰。五、一个完整的测试示例结合 tests/HomepageTest.php 与上述辅助方法一个典型的 BookStack 功能测试长这样?php namespace Tests; class HomepageTest extends TestCase { public function test_default_homepage_visible() { $this-asEditor(); $homeVisit $this-get(/); // 硬编码期望文本正向确认默认首页的各组件渲染 $homeVisit-assertSee(My Recently Viewed); $homeVisit-assertSee(Recently Updated Pages); $homeVisit-assertSee(Recent Activity); $homeVisit-assertSee(home-default); } public function test_custom_homepage_can_be_deleted_once_no_longer_used() { $this-asEditor(); $name My custom homepage; $content str_repeat(This is the body content of my custom homepage., 20); $customPage $this-entities-newPage([name $name, html $content]); $this-setSettings([ app-homepage $customPage-id, app-homepage-type default, ]); $pageDeleteReq $this-delete($customPage-getUrl()); $pageDeleteReq-assertStatus(302); $pageDeleteReq-assertSessionHas(success); $pageDeleteReq-assertSessionMissing(error); } }这个示例浓缩了前文要点asEditor()选择低权限用户、$this-entities-newPage()构造实体、setSettings()写入系统设置、硬编码文本断言、以及正向/负向断言的组合使用。六、调试与进阶提示查看更多真实用例仓库的tests/目录按模块组织Activity、Api、Auth、Entity、Exports、Permissions、Search、Settings、Sorting、Uploads、User、Util等编写新测试前建议先精读与目标功能最相近的既有用例——原文档明确指出scrappy tests are better than no tests粗糙的测试也胜过没有测试测试代码允许比核心业务代码更接地气不必过度追求优雅。Mock 外部 HTTPmockHttpClient([...])可以预设响应序列并捕获请求历史配合HttpClientHistory断言确实发出了符合预期的请求是测试 Webhook、OIDC、外部内容抓取等功能的利器。并行测试数据库使用composer t-resetartisan test --recreate-databases让每个并行 worker 获得独立的mysql_testing数据库这是runWithEnv中数据库名恢复逻辑存在的意义。排查弃用问题需要临时开启 deprecation 失败模式时取消 tests/TestCase.php 中withoutDeprecationHandling()的注释跑完后记得还原。通过以上环境准备、运行方式与编写约定你可以完全基于当前仓库在本地跑通 BookStack 全量测试并遵循团队规范新增高质量的功能测试用例遇到权限、上传、外部服务等复杂场景时$this-permissions、$this-files、mockHttpClient()等内置设施足以覆盖绝大多数测试需求。【免费下载链接】BookStackNOW MANAGED ON CODEBERG项目地址: https://gitcode.com/gh_mirrors/bo/BookStack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
光伏电池原理与组件选型:从光生伏特效应到工程实战 简介:光伏电池工作原理的完整技术讲解文档,面向光伏、电子、电气等相关专业的初学者与工程技术人员,帮助读者系统理解光能转化为电能的核心机制。文档从光伏效应出发,以硅原子四价键结构为载体,详细解释了光线如何激发… · 2026/9/21 1:52:42
Carla自动驾驶仿真环境搭建与Python API实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/21 1:52:42
多传感器融合中的时间同步与卡尔曼滤波位姿估计实战解析 简介:面向计算机视觉、机器人与自动驾驶位姿估计研发人员的一份OpenCV多传感器融合技术文档,全书共483页、50个章节,系统讲解时间同步与卡尔曼滤波相结合的高精度位姿估计优化设计。文档重点覆盖传感器选型、GPIO硬件触发与NTP/PTP软件同步、… · 2026/9/21 1:52:42
3个实战案例教你挑对软件下载网站哪个好防挂马 3个实战案例教你挑对软件下载网站哪个好防挂马 上周帮客户复盘,发现官网弹窗全是博彩广告,后台日志被清空,这种被黑挂马的恐惧,很多站长都经历过。 别慌,选对底层架构的下载站,比事后打补丁重要十倍。 结合3个被黑过的实战案例,我拆解一下“软件下载网站哪个好”的评判标准。 设计原则与信任感构建… · 2026/9/21 9:02:23
网站标识代码怎么加实操详解及对比评测避坑指南 网站标识代码怎么加实操详解及对比评测避坑指南 备案流程一头雾水,是很多中小企业在上线官网时最容易卡壳的环节。很多老板以为只要把网站做出来,挂上域名就能收流量,结果发现没ICP备案根本打不开,或者加了备案代码位置不对导致审核不通过。这时候,一份清晰的网站标识代码怎么加的操作指南,加上不同服务商方案的对… · 2026/9/21 8:45:49
别被网页制作模板中文坑了,懂建站报价才不亏 别被网页制作模板中文坑了,懂建站报价才不亏 网站做好了没人访问,这钱白花得冤不冤?很多老板找外包,问完建站报价,对方甩给你一个“网页制作模板中文”链接,说这是高端定制。你一看,哦,是套壳的。更坑的是,有些模板连基础的SEO结构都没做好,上线三个月,百度搜不到你公司名字。… · 2026/9/21 8:31:34
2026最新微信小程序连接wordpress:解决域名服务器搞不懂的实战指南 2026最新微信小程序连接wordpress:解决域名服务器搞不懂的实战指南 域名解析指向不对,服务器端口没开放,SSL证书配置报错——这三座大山,劝退了一半想用微信小程序展示WordPress内容的开发者。别急,2026最新的连接方案早已绕开了传统Web服务器配置的深坑,核心逻辑是:… · 2026/9/21 8:17:36
企业网站做电脑营销多少钱?揭秘防黑挂马的底层逻辑 企业网站做电脑营销多少钱?揭秘防黑挂马的底层逻辑 网站突然被黑,首页挂满赌博广告,后台密码怎么改都没用,这种绝望感做过站的都懂。很多老板第一反应是问:“清理一次病毒多少钱?”或者“换个服务器多少钱?”但真相往往扎心:单纯清理病毒的费用可能只要几百块,但重建信任、修复SEO权重、补全安全漏洞的成本,往… · 2026/9/21 8:03:27
3步搞定做品管圈网站从零搭建到上线避坑指南 3步搞定做品管圈网站从零搭建到上线避坑指南 不会写代码,但想给团队搭个品管圈展示平台?别慌。 很多河南的创业老板都卡在这一步:手里有现成的QCC成果,想做个官网放上去,结果一搜全是“前端开发教程”,看得头大。 做品管圈网站 这事儿,真没你想的那么玄乎。只要路子对,零基础也能 从零搭建… · 2026/9/21 7:45:56
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化 直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/21 0:02:39
Word表格编号全攻略:从列表编号到题注交叉引用 写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/21 0:02:39
从第一个站到第二个站:独立开发者的静态网站选型与落地实践 1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41
agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and … · 2026/9/21 0:00:18
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,… · 2026/9/21 0:00:18