资讯动态

在 Laravel 5.1 应用中使用 Mockery 构建 PHP 单元测试替身:安装、期望声明与参数匹配实战指南

发布时间:2026/9/24 6:30:59 来源:尧图企业网站定制
示例工程数据库教程后端【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址https://gitcode.com/gh_mirrors/sq/sql-server-samples点击查看免费下载导读Mockery 是一个面向 PHP 单元测试的 mock 对象框架以贴近自然语言的可读 API 定义对象的调用与交互是 PHPUnit 自带 phpunit-mock-objects 的轻量级替代方案。本文以本仓库 Laravel 示例应用 为载体系统讲解 Mockery 的安装、期望声明Expectation Declaration、参数验证Argument Validation、PHPUnit 集成与底层实现原理帮助你写出可读性强、隔离彻底的测试替身。一、Mockery 是什么以可读 DSL 定义测试替身Mockery 的核心定位是simple yet flexible简单且灵活的 PHP mock 对象框架可配合 PHPUnit、PHPSpec 或任意其他测试框架使用。其设计目标是用一套人类可读的领域特定语言DSL清晰地定义对象可能发生的所有操作与交互。在单元测试中mock 对象用于模拟真实对象的行为常见场景包括提供测试隔离将被测对象与外部依赖数据库、网络服务、文件系统解耦替身尚未实现的对象让开发不必等待依赖类完成即可先行开发在没有实现的情况下探索性设计类的 API先行敲定交互契约。mock 框架的价值在于能以灵活 API 生成 mock 对象与 stub并允许设置期望的方法调用和返回值用尽量接近自然语言的方式描述真实对象行为。本仓库的 Laravel 示例应用composer.json将 Mockery 声明为开发依赖require-dev: { mockery/mockery: 0.9.*, phpunit/phpunit: ~4.0 }而 Mockery 自身的 composer.json 明确声明了运行时要求php 5.3.2、lib-pcre 7.0并推荐安装hamcrest/hamcrest-php ~1.1提供更多参数匹配器。二、安装 MockeryComposer、PEAR 与 Git 三种方式2.1 Composer推荐composer require mockery/mockery若希望锁定 master 分支的开发版本可在composer.json中写入{ require-dev: { mockery/mockery: dev-master } }然后执行php composer.phar update作为require-dev依赖Mockery 在--no-dev的生产安装中不会被打包。2.2 PEARMockery 托管在pear.survivethedeepend.comPEAR 频道sudo pear channel-discover pear.survivethedeepend.com sudo pear channel-discover hamcrest.googlecode.com/svn/pear sudo pear install --alldeps deepend/Mockery2.3 Git 克隆git clone git://github.com/padraic/mockery.git cd mockery sudo pear channel-discover hamcrest.googlecode.com/svn/pear sudo pear install --alldeps package.xml说明上述过程会同时安装 Mockery 与 Hamcrest。省略 Hamcrest 不会破坏 Mockery但 Hamcrest 推荐安装因为它为参数匹配提供了更丰富的功能。2.4 运行测试安装完成后可在 Mockery 源码目录运行vendor/bin/phpunit其测试配置位于 phpunit.xml.dist测试用例集中在 tests/ 目录。三、第一个示例用 mock 替身替换未实现的服务官方文档getting_started/simple_example.rst提供了一个经典场景Temperature类需要从一个尚未实现的温度服务读取三次采样并求平均class Temperature { public function __construct($service) { $this-_service $service; } public function average() { $total 0; for ($i 0; $i 3; $i) { $total $this-_service-readTemp(); } return $total / 3; } }即使真实服务类还不存在我们也能从使用方式推断其契约然后通过 Mockery 生成替身完成测试use \Mockery as m; class TemperatureTest extends PHPUnit_Framework_TestCase { public function tearDown() { m::close(); } public function testGetsAverageTemperatureFromThreeServiceReadings() { $service m::mock(service); $service-shouldReceive(readTemp)-times(3)-andReturn(10, 12, 14); $temperature new Temperature($service); $this-assertEquals(12, $temperature-average()); } }这里的期望声明shouldReceive(readTemp)-times(3)-andReturn(10, 12, 14)直译过来就是期望readTemp被调用 3 次依次返回 10、12、14——这正是人类可读 DSL的体现。三次采样求和10121436平均值 12断言通过。若tearDown()中的m::close()检测到调用次数与期望不符将抛出\Mockery\CountValidator\Exception。四、期望声明Expectation Declarations全解析创建 mock 后核心工作是用期望声明定义它该如何被调用。完整说明见 reference/expectations.rst。4.1 声明期望方法声明形式说明shouldReceive(method_name)声明 mock 期望收到对指定方法的调用是所有后续约束的起点shouldReceive(m1, m2, ...)一次声明多个期望方法所有方法共用后续链式约束shouldReceive(array(m11, m22))同时声明方法及其返回值shouldReceive(closure)仅用于 partial mock将代理对象传给闭包执行操作自动记录为期望常用于重构时的行为录制shouldNotReceive(method)便捷方法等价于shouldReceive()-never()4.2 参数约束声明形式说明with(arg1, arg2, ...)/withArgs(array(...))仅匹配参数完全符合的调用可按不同参数为同一方法设置不同期望withAnyArgs()匹配任意参数默认行为withNoArgs()仅匹配零参数调用4.3 返回值与行为声明形式说明andReturn(value)返回固定值andReturn(v1, v2, ...)按调用顺序依次返回后续调用总是返回最后一个值andReturnNull()/andReturn([NULL])显式标记返回null主要为了测试可读性andReturnValues(array)andReturn()的数组语法变体超出数组长度后返回末位元素andReturnUsing(closure, ...)将调用参数传给闭包由闭包计算结果返回多个闭包可排队andThrow(Exception)调用时抛出指定异常对象andThrow(exception_name, message)以类名 消息创建异常并抛出andSet(name, value)/set(name, value)方法被匹配调用时同时设置 mock 的公共属性passthru()绕过返回值队列实际调用被 mock 类的真实方法并返回其结果同时仍保留期望匹配与调用次数校验注意andReturnUsing()与andReturn()不能混用。4.4 调用次数约束声明形式说明zeroOrMoreTimes()允许调用零次或多次默认once()/twice()/times(n)精确限定调用次数never()禁止调用atLeast()与下一次次数期望组合atLeast()-times(3)表示至少 3 次atMost()与下一次次数期望组合atMost()-times(3)表示最多 3 次0 次亦可between(min, max)次数范围等价于atLeast()-times(min)-atMost()-times(max)次数约束违反时会抛出\Mockery\CountValidator\Exception——这一点在 library/Mockery/CountValidator/ 目录下有AtLeast.php、AtMost.php、Exact.php等具体实现类。4.5 顺序与默认值控制声明形式说明ordered()声明该方法与同类标记方法之间存在调用顺序约束ordered(group)按命名/编号分组组内可任意顺序组间保持顺序globally()与ordered()组合时顺序约束跨所有 mock 对象生效byDefault()标记为默认期望除非存在非默认期望否则默认期望生效非默认期望会立即替换默认期望。适合在setup()中配置默认 mock、在具体测试中微调getMock()从期望链中返回当前 mock 对象便于把 mock 配置写成单条语句\Mockery::mock(foo)-shouldReceive(foo)-andReturn(1)-getMock()五、参数验证Argument Validation精确匹配与通用匹配器with()中声明的参数决定调用与期望的匹配标准同一方法可为不同参数组合配置多个期望。匹配采用**最佳拟合best fit**策略显式匹配优先于泛化匹配。显式匹配指参数可用或直接等价判定泛化匹配则通过正则、类型提示和通用匹配器实现。完整说明见 reference/argument_validation.rst。5.1 通用匹配器速查表写法含义with(1)匹配整数1先宽松时也接受字符串1with(\Mockery::any())或with(anything())匹配任意参数with(\Mockery::type(resource))类型匹配type接受任何可拼成is_xxx()的字符串如float→is_float()、callable→is_callable()也接受类/接口名做instanceof判断with(\Mockery::on(closure))闭包匹配闭包对实参求值返回true即匹配用于复杂条件with(/^foo/)字符串被视为正则仅在无/匹配且preg_match()校验有效时使用with(\Mockery::ducktype(foo, bar))鸭子类型匹配实参是包含指定方法列表的对象即可with(\Mockery::mustBe(2))强类型匹配要求值与类型完全一致如字符串2不匹配整数2对象不做 identical 比较with(\Mockery::not(2))匹配不等于且不恒等于参数的值with(\Mockery::anyOf(1, 2))匹配等于任一给定参数with(\Mockery::notAnyOf(1, 2))匹配不等于且不恒等于任一给定参数with(\Mockery::subset(array(0 foo)))实参数组必须包含给定子集同时比较键与值with(\Mockery::contains(v1, v2))实参数组包含所列值忽略键名with(\Mockery::hasKey(key))实参数组包含给定键with(\Mockery::hasValue(value))实参数组包含给定值5.2 与 Hamcrest 的关系Mockery 的通用匹配器并未覆盖所有可能性但可选支持 Hamcrest 匹配器库PHP 移植自同名 Java 库。文档明确建议使用 HamcrestMockery 无需重复实现 Hamcrest 已有的强大工具。上表中多数匹配器都有 Hamcrest 等价物\Mockery::any()↔ Hamcrestanything()\Mockery::type(resource)↔resourceValue()/typeOf(resource)\Mockery::mustBe(2)↔identicalTo(2)\Mockery::not(2)↔not(2)with(/^foo/)↔matchesPattern(/^foo/)而\Mockery::on()、ducktype()、notAnyOf()、subset()没有 Hamcrest 版本子集可用hasEntry()/hasKeyValuePair()做单条检查。六、与 PHPUnit 集成从 tearDown 到 TestListenerMockery 本身是独立的 mock 框架与测试框架的集成完全可选。集成只需在测试中定义tearDown()public function tearDown() { \Mockery::close(); }该静态调用负责清理当前测试使用的 Mockery 容器并执行期望验证。完整集成说明见 reference/phpunit_integration.rst。6.1 命名空间别名与自动加载可用use \Mockery as m;缩短调用。Mockery 自带自动加载器避免到处写require_once()require_once Mockery/Loader.php; require_once Hamcrest/Hamcrest.php; $loader new \Mockery\Loader; $loader-register();使用 Composer 时只需引入自动加载文件vendor/autoload.phprequire __DIR__ . /../vendor/autoload.php;注意Hamcrest 1.0.0 之前的文件名是小写hamcrest.php升级后需确认文件名大小写。6.2 TestListener自动验证期望为避免每次手写close()并让 Mockery 自动从代码覆盖率报告中移除自身可在测试套件中注册\Mockery\Adapter\Phpunit\TestListener源码位于 library/Mockery/Adapter/Phpunit/TestListener.php$suite new PHPUnit_Framework_TestSuite(); $result new PHPUnit_Framework_TestResult(); $result-addListener(new \Mockery\Adapter\Phpunit\TestListener()); $suite-run($result);使用 PHPUnit XML 配置时listeners listener class\Mockery\Adapter\Phpunit\TestListener/listener /listeners⚠️ 已知限制使用 PHPUnit 的进程隔离runTestsInSeparateProcesses时listener 不会在正确的进程中执行可能导致期望未验证也不抛异常。此时应放弃 TestListener改在tearDown()中显式调用Mockery::close()。七、从源码结构看 Mockery 的底层实现结合 library/ 目录可以推断 Mockery 的分层架构入口与容器Mockery.php 提供mock()、close()等静态门面Container.php 管理当前测试生命周期内的 mock 注册与清理这解释了tearDown()中m::close()的语义。期望与验证Expectation.php 实现shouldReceive()-with()-andReturn()-times()的链式 DSLExpectationDirector.php 负责调度VerificationDirector.php 与 CountValidator/ 下的Exact.php、AtLeast.php、AtMost.php对应调用次数约束的底层校验。匹配器Matcher/ 目录下Any.php、Type.php、Closure.php、Ducktype.php、MustBe.php、Subset.php、Contains.php、HasKey.php、HasValue.php等类一一对应第五节表格中的匹配器印证了最佳拟合匹配策略的实现载体。运行时生成Generator/ 负责 mock 类的运行时生成含StringManipulationGenerator与多个 PassLoader/ 下的EvalLoader.php、RequireLoader.php提供不同的类加载方式。PHPUnit 适配Adapter/Phpunit/ 提供TestListener.php、MockeryTestCase.php、MockeryPHPUnitIntegration.php即第六节所述集成的实现基础。八、在本仓库 Laravel 项目中的实战位置本仓库的 Laravel 应用 的测试目录 tests/ 中有TestCase.php、HomeTest.php、TodoTest.php、SignInTest.php。例如 HomeTest.php 展示了基于 Laravel 内置visit()/see()的功能测试写法class HomeTest extends TestCase { public function testLandingPage() { $this-visit(/) -see(Welcome to Laravel ToDo App); } }这类功能测试可直接运行vendor/bin/phpunit当测试对象依赖数据库、外部服务或尚未实现的协作类时即可引入 Mockery 替身。Mockery 与 Laravel 5.1 的require-dev配置mockery/mockery: 0.9.*相匹配其接近自然语言的 DSL 与 Laravel 的测试风格天然互补用shouldReceive(method)-with(...)-andReturn(...)-once()描述依赖契约用m::close()或 PHPUnit TestListener在测试结束时验证期望是否全部兑现。九、总结Mockery 以一套简洁而富有表现力的 DSL把模拟对象行为这件事变成了近乎自然语言的声明shouldReceive声明方法、with约束参数、andReturn指定返回值、once/times限定次数、ordered约束顺序。结合 Hamcrest 匹配器与 PHPUnit 的TestListener可以在不引入重依赖的前提下获得完整的测试替身能力。理解其期望声明、参数验证与调用次数校验的语义是在本仓库 Laravel 示例应用乃至任何 PHPUnit 项目中写出高质量单元测试的关键前提。赞分享示例工程数据库教程后端【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址https://gitcode.com/gh_mirrors/sq/sql-server-samples点击查看免费下载相关推荐Cilium Operator for Alibaba Cloudcilium-operator-alibabacloud 命令行完整参考与 ENI IPAM 实现解析Cilium Operator for Alibaba Cloudcilium operator alibabacloud 命令行完整参考与 ENI IPAM测试开发工具Agent Zero /poll 状态快照接口WebUI 轮询同步的完整契约与源码解析Agent Zero /poll 状态快照接口WebUI 轮询同步的完整契约与源码解析 Agent Zero 的 WebUI 通过 HTTP 轮询接口 /po测试开发工具Hydra 单元测试实战使用 initialize() 与 compose() 在测试中组装配置Hydra 单元测试实战使用 initialize 与 compose 在测试中组装配置 在 Hydra 框架中 hydra.main 是让应用接入命令行开发工具后端CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价