- 示例工程
- 数据库
- 教程
- 后端
【免费下载链接】sql-server-samples
Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge
导读
本文以 sql-server-samples 仓库中 Laravel 示例项目(samples/development-frameworks/laravel)所依赖的 Mockery 0.9 框架文档为蓝本,系统讲解 Mockery 的**参数验证(Argument Validation)**机制——即通过with()声明为期望(expectation)设置参数匹配规则,从而区分同一方法上的多条期望。读完本文,你将掌握 Mockery 内置的全部通用匹配器(any、type、on、ducktype、mustBe、not、anyOf、notAnyOf、subset、contains、hasKey、hasValue)的语义与用法,理解与 Hamcrest 库的对应关系,并看到这些匹配器在仓库内源码中的真实实现,为在 PHPUnit 单测中编写精确、可维护的 Mock 期望打下基础。
说明:Mockery 由 composer.json 中
"mockery/mockery": "0.9.*"引入(见 samples/development-frameworks/laravel/composer.json),其文档位于vendor/mockery/mockery/docs/reference/,本文即是对其中 argument_validation.rst 的展开解读。
参数匹配的基本原理:期望如何与调用对应
在 Mockery 中,当你设置一个期望时,传给with()声明的参数决定了该期望与真实方法调用之间的匹配标准。因此,你可以为同一个方法设置多条期望,每条期望通过参数不同而相互区分。参数匹配采用"best fit"(最契合优先)原则:显式匹配优先于泛化匹配。
所谓"显式匹配",是指期望参数与实际参数可以被直接比较(即通过===或==完成判定);而更泛化的匹配则借助正则表达式、类型提示(class hinting)以及 Mockery 提供的通用匹配器实现。泛化匹配器的意义在于,允许你用非显式的方式描述参数,例如在with()中传入\Mockery::any(),表示该位置接受任意参数。
从源码结构看,这一设计体现在匹配器的抽象基类中。所有内置匹配器都继承自 library/Mockery/Matcher/MatcherAbstract.php,其中:
- 构造函数接收"期望值"
_expected; - 抽象方法
match(&$actual)负责判断实际参数是否命中——注意实际参数以引用方式传入,以保留指向原始方法参数的引用链; - 抽象方法
__toString()返回匹配器的字符串表示,便于在失败消息中呈现期望内容。
例如Any匹配器的match()直接return true(见 library/Mockery/Matcher/Any.php),这正对应文档中"任何参数都能通过"的描述。
匹配器的入口:Mockery 静态工厂方法
Mockery 通过 library/Mockery.php 提供一组静态工厂方法来创建上述匹配器实例,与文档一一对应:
| 文档中的写法 | 工厂方法(源码位置) | 返回的匹配器类 |
|---|---|---|
\Mockery::any() | any() | Matcher\Any |
\Mockery::type($expected) | type() | Matcher\Type |
\Mockery::ducktype(...) | ducktype() | Matcher\Ducktype |
\Mockery::subset(array $part) | subset() | Matcher\Subset |
\Mockery::contains(...) | contains() | Matcher\Contains |
\Mockery::hasKey($key) | hasKey() | Matcher\HasKey |
\Mockery::hasValue($val) | hasValue() | Matcher\HasValue |
\Mockery::on($closure) | on() | Matcher\Closure |
\Mockery::mustBe($expected) | mustBe() | Matcher\MustBe |
\Mockery::not($expected) | not() | Matcher\Not |
\Mockery::anyOf(...) | anyOf() | Matcher\AnyOf |
\Mockery::notAnyOf(...) | notAnyOf() | Matcher\NotAnyOf |
注意ducktype、contains、anyOf、notAnyOf使用func_get_args()收集可变数量的参数,因此可以一次传入多个方法名或值。
内置匹配器逐一详解
以下逐一展开文档中的每个匹配器示例,并给出对应的 Hamcrest 等价写法(Hamcrest 使用无命名空间的函数,Mockery 支持可选接入 Hamcrest 匹配器库以扩展能力;Mockery 官方强烈推荐使用 Hamcrest,因为 Mockery 无需重复实现 Hamcrest 已有的丰富工具,后者还提供贴近自然英语的 DSL)。
1. 显式值匹配:with(1)
with(1)匹配整数1。它通过===(恒等)测试;同时,Mockery 也支持较宽松的==(相等)检查,此时字符串'1'也能匹配该参数位。这是文档所述的"显式匹配"的基础形态。
2. 匹配任意参数:with(\Mockery::any())
with(\Mockery::any()) // Mockery 写法 with(anything()) // Hamcrest 等价写法匹配任意参数。传在该参数位上的任何值都不受约束地通过,实现上即Matcher\Any::match()恒返回true。
3. 类型匹配:with(\Mockery::type('resource'))
with(\Mockery::type('resource')) // Mockery 写法 with(resourceValue()) // Hamcrest 等价写法 with(typeOf('resource')) // Hamcrest 的另一种等价写法匹配任何 resource 类型的值,即is_resource()返回true。Type 匹配器接受任何可以拼接为is_前缀形成合法类型检查的字符串:
\Mockery::type('float')或 Hamcrest 的floatValue()、typeOf('float')使用is_float()检查;\Mockery::type('callable')或 Hamcrest 的callable()使用is_callable()检查。
Type 匹配器还接受类名或接口名,此时会对实际参数执行instanceof求值(Hamcrest 对应anInstanceOf())。其底层实现可在 library/Mockery/Matcher/Type.php 中看到:
public function match(&$actual) { $function = 'is_' . strtolower($this->_expected); if (function_exists($function)) { return $function($actual); } elseif (is_string($this->_expected) && (class_exists($this->_expected) || interface_exists($this->_expected))) { return $actual instanceof $this->_expected; } return false; }即:先尝试把期望值小写化为is_xxx()函数;若不存在该函数,再尝试作为类/接口名进行instanceof判断。完整的 PHP 类型检查函数清单可查阅 PHP 手册的 Variable handling 章节,Hamcrest 函数列表可浏览其 Hamcrest.php 源码。
4. 回调式匹配:with(\Mockery::on(closure))
with(\Mockery::on(closure))On 匹配器接受一个闭包(匿名函数),实际参数会被传入该闭包;如果闭包返回布尔值true,则认为该参数命中期望。当你的参数期望逻辑过于复杂、或现有默认匹配器无法表达时,这个匹配器极为有用。Hamcrest 没有对应实现。
5. 正则匹配:with('/^foo/')
with('/^foo/') // Mockery 写法 with(matchesPattern('/^foo/')) // Hamcrest 等价写法参数声明器会假定任何给定的字符串都可能是一条正则表达式,用于对实际参数做匹配。正则选项仅在满足以下两个条件时才启用:
- 不存在
===或==的显式匹配; - 该正则被验证为合法正则(即
preg_match()不会返回false)。
如果你不喜欢这种隐式正则探测的行为,Hamcrest 提供了更显式的matchesPattern()函数。
6. 鸭子类型匹配:with(\Mockery::ducktype('foo', 'bar'))
with(\Mockery::ducktype('foo', 'bar'))Ducktype 匹配器是"按类类型匹配"之外的另一种思路:它匹配任何包含给定方法列表的对象,即只要对象实现了列出的这些方法就命中,而不管其具体类是什么("走起来像鸭子、叫起来像鸭子,那就是鸭子")。Hamcrest 没有对应实现。
7. 严格恒等匹配:with(\Mockery::mustBe(2))
with(\Mockery::mustBe(2)) // Mockery 写法 with(identicalTo(2)) // Hamcrest 等价写法MustBe 匹配器比默认参数匹配器更严格。默认匹配器允许 PHP 的类型转换(casting),而 MustBe 还要求参数与期望值类型一致。例如默认情况下字符串'2'可以匹配整数 2(经==比较),但使用 MustBe 时该场景会失败,因为期望值是字符串而实际传入的是整数。
注意事项:对于对象,MustBe 匹配器不进行恒等(identical)比较,因为 PHP 中两个对象若不是完全相同的实例,比较必然失败——当对象在返回之前才被创建时,恒等匹配永远不可能成功,这反而成为一种阻碍。因此对象场景请改用其他匹配方式。
8. 取反匹配:with(\Mockery::not(2))
with(\Mockery::not(2)) // Mockery 写法 with(not(2)) // Hamcrest 等价写法Not 匹配器匹配任何不等于且不恒等于其参数的值,即与参数既不==也不===才通过。
9. 多选一匹配:with(\Mockery::anyOf(1, 2))
with(\Mockery::anyOf(1, 2)) // Mockery 写法 with(anyOf(1, 2)) // Hamcrest 等价写法匹配等于给定参数中任意一个的参数,即与列表中的任一值相等即命中。
10. 全排除匹配:with(\Mockery::notAnyOf(1, 2))
with(\Mockery::notAnyOf(1, 2))匹配不等于且不恒等于给定参数中任何一个的参数。Hamcrest 没有对应实现。
11. 数组子集匹配:with(\Mockery::subset(array(0 => 'foo')))
with(\Mockery::subset(array(0 => 'foo')))匹配任何包含给定数组子集的数组参数。它同时强制键名与键值的比对——即实际元素的键和值都要与子集中的键值对一一对应。Hamcrest 没有完全对应的实现,不过它可以用hasEntry()或hasKeyValuePair()检查单个条目。
12. 数组包含值匹配:with(\Mockery::contains(value1, value2))
with(\Mockery::contains(value1, value2))匹配任何包含所列值的数组参数,键名被忽略,只关心值是否存在。
13. 数组包含键匹配:with(\Mockery::hasKey(key))
with(\Mockery::hasKey(key))匹配任何包含给定键名的数组参数。
14. 数组包含值匹配:with(\Mockery::hasValue(value))
with(\Mockery::hasValue(value))匹配任何包含给定值的数组参数。
在 Laravel 项目中的落地:从依赖到测试
在 sql-server-samples 仓库的 Laravel 示例项目 samples/development-frameworks/laravel 中,Mockery 0.9 是require-dev依赖之一(见 composer.json),与 PHPUnit~4.0、PHPSpec~2.1一同服务于开发与测试阶段。该项目的运行环境为 PHP 7、Laravel 5.1、SQL Server PHP/ODBC 驱动(见 README.md),其tests/TestCase.php被注册到autoload-dev的 classmap 中。
将本文的匹配器应用到该项目的测试中时,典型模式如下:
$repository = Mockery::mock(TodoRepository::class); $repository->shouldReceive('find') ->with(\Mockery::type('int')) // 仅匹配整数 id ->once() ->andReturn($todo); $repository->shouldReceive('update') ->with(\Mockery::anyOf(1, 2), \Mockery::subset(['status' => 'done'])) ->andReturn(true);- 第一条期望利用
type('int')限定find只接受整数主键; - 第二条期望用
anyOf(1, 2)限定可操作的 id,用subset校验传入的更新数组必须包含status => done键值对。
这种写法正是文档所述"best fit"原则的实践:同一方法的多条with()期望共存时,Mockery 会优先匹配最显式、最具体的声明,保证测试行为可预期。
总结与选型建议
Mockery 的通用匹配器并不覆盖所有可能性,但它提供了可选的 Hamcrest 匹配器库支持作为补充。选型时可遵循以下原则:
- 简单值与正则:优先使用默认显式匹配与正则探测,代码最简洁;
- 类型、类与接口约束:使用
type()(或 HamcrestanInstanceOf()/typeOf()); - 复杂判定逻辑:使用
on()闭包,逻辑自由度最高; - 数组结构断言:按需选择
subset、contains、hasKey、hasValue,注意subset同时校验键名与键值,contains忽略键名; - 否定与多选:
not、anyOf、notAnyOf可组合出取反与枚举语义; - 严格类型:需要同时约束值与类型时使用
mustBe(),但切记对象不适合恒等比较。
需要进一步深入时,可直接查阅仓库内 Mockery 的完整文档目录 vendor/mockery/mockery/docs,以及匹配器实现源码 library/Mockery/Matcher 和工厂方法定义 library/Mockery.php。
- 示例工程
- 数据库
- 教程
- 后端
【免费下载链接】sql-server-samples
Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge
相关推荐
Mockery 参数校验(Argument Validation)完全指南:with() 与全套 Matcher 深入解析
Mockery 参数校验(Argument Validation)完全指南:with 与全套 Matcher 深入解析 本文是 Mockery 官方文档 doc
测试开发工具终极指南:掌握Hamcrest-PHP对象匹配器的10个高级技巧
Hamcrest PHP是一个强大的PHP测试框架,专门用于编写更具表达力和可读性的断言。作为PHP Hamcrest的官方实现,它提供了丰富的对象匹配器,让您
测试OmenSuperHub终极指南:惠普游戏本性能优化神器
OmenSuperHub终极指南:惠普游戏本性能优化神器 还在为官方OMEN Gaming Hub的臃肿体验而烦恼?OmenSuperHub为你带来纯净高效的硬
桌面应用硬件开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考