【初学者必看】从Mock的基础知识到如何使用*PHP、Mockery
目录
感谢大家的辛勤劳动!
我是 Eita,一名刚入职第一年的工程师!
加入职场已经半年了,很多方面都对时间的速度感到恐惧(入职第二年的压力,明年应届毕业生的教育,还有对差异的不耐烦)时间的流逝和技术发展的速度之间)等)。
妈!抛开我的焦虑和沮丧,这次我将谈论PHP 测试时的模拟。
我来解释一下“什么是mock?”,“实际使用情况,以及各种使用方式”!
很高兴见到你! *这是一种使用Mockery的方法。
什么是模拟?
首先,什么是模拟?我将从那里解释。
Mock-up(模型)是mock-up的缩写,单元测试时替换测试目标所依赖的类验证类和方法是否被正确调用的目的!
此外,使用测试双精度之一,您可以指定方法的返回值+验证调用参数和次数
但是,如果这就是您所拥有的全部内容,您可能想知道它的全部内容,所以让我给您一个如何使用它的具体示例......
案例一
我们实现了一种使用外部 API 进行支付的方法。
因此,当您决定运行测试时,如果不使用模拟,则每次运行测试时都必须访问外部 API,从而产生费用,并编写执行大量方法的描述。否则,可能会出现“对提供外部 API 的服务器进行大量访问会造成麻烦(无意的 DoS 攻击) ”之类的情况。
情况2 未实施前提方法时
假设您要测试一个方法,该方法的行为会根据该方法的返回值 bool 值而变化。
但是,我们还没有完成返回 bool 值的方法的实现。
我应该怎么办!好吧,您可以先实现它,但我们假设由于某种原因您无法先实现它。
在!模拟对于上述两种情况很有用!
实际使用情况(附代码)
基本用法
这次,作为一个例子,假设我们要测试一种获取支付信息并使用外部支付服务(API)执行支付的方法。
这时候就需要mock使用外部支付服务(API)的部分。
我通过一系列步骤简单地编写了用于模拟的代码。
使用模拟的部分从第 38 行
// 使用外部支付服务的类 class PaymentDataFromExternal { // 支付成功返回 true 的方法 public function paymentImmediately(int $ paymentId) { // 支付成功返回 true return true } } class; PaymentService { public function __construct( private PaymentDataFromExternal $ paymentDataFromExternal ){} // 待测试方法 public function execPayment() { $ payment = fetchPaymet(); // 获取支付信息 //根据获取的支付信息执行支付(进行外部API通信) $isPayment = $this-> paymentDataFromExternal-> paymentImmediately($ payment->id); return $isPayment; } } class Test { public function test_ payment_Payment is success 是否执行? () { // 创建临时支付信息 $ payment = Payment::factory(); // 你要模拟的方法所在的类$this->mock(PaymentDataFromExternal::class, function($mock) { $mock->shouldReceive(' paymentImmediately') // 方法名称 ->with($ payment->id) // 参数 ->andReturn(true) ; // 返回值 }); // 将要模拟的类替换为模拟对象 $ paymentService = app(PaymentService::class); // 执行替换为模拟对象的方法$result = $ paymentService->execPayment(); $this->assertTrue($result);
所以,我想列出一些我实际遇到的mock模式(其中很多在mockery官方文档中没有直接描述)。
需要注意的一点是,为了在测试时使用mock,似乎必须正确完成
1.我想将模拟方法的返回值作为继承某个类的对象返回。
我想模拟一个从用户表中检索所有用户的方法(返回值是 Collection)。
但是,如果您在mocking时只是将User模型作为返回值传递,您会因为返回值不同而生气,并且无法正确mock它。
经过研究后,似乎模拟要求参数和返回类型与您想要模拟的实际方法相匹配
因此,在这种情况下,就需要利用匿名类
实际代码如下。
示例:模拟一个方法,使用基于 user_id 的集合类型检索所有用户信息
$mock->shouldReceive('fetchUsersById') ->once() ->with($user->id) ->andReturn(new class extends Illuminate\Database\Eloquent\Collection([$userMock]));
如上所示,通过写“ new classextends你想要继承的类”,就可以使其成为指定的类型,并成功mock它。
2.当多次调用模拟方法时,我想根据调用次数返回不同的值。
这次要测试的方法中有一个方法被调用了两次,我想mock它。
如果想要两次返回值相同,可以像andReturn(true)这样写,每次调用mock方法都会返回true,所以没有问题,但是在这种情况下,第一次是true,我希望它第二次返回 false 。
在这种情况下,如果您指定类似 andReturn(1st time, 2nd time)
例子:如果你想用一个判断是否可以使用的方法第一次返回true,第二次返回false。
$mock->shouldReceive('isEnabled') ->twice() ->andReturn(true, false);
3.我想为模拟对象的类属性设置一个值
这次,我想模拟从用户表中检索用户信息的方法。
但,
由于在方法中将值设置为类属性,因此我想验证该值设置是否正确。
当然,我无法仅通过模拟来为类属性设置值,这是一个问题。
在这种情况下, andSet(property, value you want to set) 将值设置为类属性
示例:模拟返回用户信息的方法,设置name属性为“Mr. Sato”
$mock->shouldReceive('getUserInfo') ->once() ->andReturn($user) ->andSet('name', '佐藤先生');
4.我想mock被测试类的一些方法
这次有一个类需要在与要测试的方法同一个类中进行mock。
如果你只是简单地mock方法,那么除了被测试的方法之外,也会被替换为mock对象。
因此,只需要模拟要测试的方法。
在这种情况下,“部分模拟”似乎是有效的。
然而,当我只是使用部分模拟来模拟它时,它没有被正确地替换为模拟对象。
当我研究它时...
看来部分mock不能仅仅通过实例化来替换为mock对象,并且似乎有必要在这一侧显式地注入依赖项。
class SampleClass { public function getName() { etc... } public function fetchDataFromExternal() { // 执行外部通信的描述 } } class test { $partialMock = Mockery::mock(SampleClass::class)->makePartial() ; $mock->shouldReceive('fetchDataFromExternal') ->once(); // 通过编写以下内容,当调用该类时,将替换为 $partialMock $this->instance(SampleClass::class, $partialMock); }
如上所述,您可以通过 makePartial 来使用部分模拟,还可以通过编写类似“ $this->instance(class name::class,partialmock object)
5.我想模拟静态方法
这次,我想模拟创建付款历史记录的静态方法。
然而,普通的模拟通过模拟实例方法来修改对象的行为,而静态方法使用特殊的方法,因为它们与类本身而不是实例化的类实例绑定在一起,这似乎是您需要的。
为了解决这个问题,有一种叫做 aliasmock
示例:模拟静态方法,根据付款 ID 和价格创建付款历史记录
Mockery::mock('alias:'.History::class)->shouldReeive('makePurchase')->with($ payment->id, $ payment->price)->andReturn($purchaseHistory);
看来你可以通过像上面所示的 ('alias:'.class name::class)
然而...
虽然使用上述方法支持mocking静态方法,但官方文档指出不推荐这样做
原因是如果两个或多个测试中存在同名的类,就会出现错误,因此似乎有必要将每个测试分开。
解决方案:需要在Doc中编写以下内容(但是,这仍然会导致许多其他问题,因此仍然不建议这样做)。
您可以通过搜索下面的描述找到它。
/** * @runInSeparateProcess * @preserveGlobalState 禁用 */
上述描述参考来源: kaihttps://docs.phpunit.de/en/11.4/annotations.html
今天的介绍就到此为止。
上面使用的方法的解释,包括once()、with()、Return()(还有很多),官方文档中有描述,请参考。
嘲笑文档: https://docs.mockery.io/en/latest/
概括
你觉得对你有一点帮助吗? ?
我正在写关于嘲笑自己的文章,每次我遇到我想要嘲笑的新模式时,我都会对自己说,“我该怎么做?!”并且我每天都尝试这样做(也许。 ..)。
所以,这次我们讨论了测试双打中的模拟,但测试双打还包括存根、间谍、假对象和虚拟对象。
我自己只知道简单的行为,所以如果您有兴趣,如果您能研究一下并让我知道,我将不胜感激,哈哈。
以后我会从新晋工程师的角度发布很多信息,所以请务必将其添加到您的收藏夹中!