ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BookStack PHP 测试实战指南:从测试环境搭建到用例编写与源码原理

BookStack PHP 测试实战指南:从测试环境搭建到用例编写与源码原理 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),仅供参考
返回列表