在 PHPUnit 中,当返回值是复杂对象时,我应该如何测试



我有3个类。

class Box{
public $item1;
public $item2;
public function __construct($item1,$item2){
$this->item = $item1;
$this->item2 = $item2;
}
public function getItem1(){
return $this->item1;
}
}
class Details{
public $stuff
public $item1;
public $item2;
public $item3;
public function __construct($stuff){
$this->stuff = $stuff
}
public function setItem1($item){
$this->item1 = $item;
}
public function setItem2($item){
$this->item2 = $item;
}
}
class Crate{
public $box;
private $stuffString = "Stuff";
public function __construct(Box $box){
$this->box = $box;
}
public function getDetails(){
$details = new Details($stuffString);
$details->setItem1($box->item1);
$details->setItem2("Detail");
return $details;
}
}

Crate->getDetails()方法返回一个 Details 对象,其中包含来自 Box 对象的数据。我想为这种方法编写测试。

function test_get_details(){
$box = Mockery::mock(Box::class);
$box->shouldReceive('getItem1')->andReturn("BoxItem");
$crate= new Crate($box);
$details = $crate->getDetails();
$this->assertInstanceOf(Details::class,$details);
}

我创建了一个 Box 类的模拟,并将其传递给 Crate 的构造函数。当我调用$crate->getDetails();时,它应该返回一个 Details 对象

  • $item 1 = "盒子项目">
  • $item 2 = "详细信息">
  • $item 3 = 空

我知道我可以通过对每个项目$this->assertEquals("BoxItem",$details->item1);等进行测试......但这是最好的方法吗?是否有一些 PHPUnit 工具来构建所需的 Detials 结果并进行比较

例如

$this->assertEquals(MockDetailObject,$details)

还是我必须做一系列断言以确保结果是我期望的。

注意*

我知道对于我的例子来说,这不是什么大问题,我很快就建立了它来解释我的意思。但是在我正在处理的代码中,我遇到了相同类型的问题,除了 Details 对象比 3 个字符串更复杂。

TL;DR:创建一个工厂并 100% 测试这个工厂。

据我了解,您的Crate类既是实体又是工厂。您可以通过将此创建责任转移到工厂来重构Crate::getDetails

这样,您只能使用"给定、何时、然后"结构对创建逻辑进行单元测试。查看这篇关于干净测试的文章,并导航到"测试应该简洁而有意义"。

拥有这种结构将帮助您分辨输入和输出是什么。 例如:

板条箱详细信息工厂测试.php

class CrateDetailFactoryTest extends TestCase
{
public function testCreateCrateDetail(): void
{
// Given
$crate = $this->givenThereIsACrate();
$boxes = $this->givenThereAreTwoRedBoxes();
// When
$crateDetail = $this->crateDetailFactory->createCrateDetail(
$crate,
$boxes
);
// Then
// (Unnecessary instanceof, if you have strict return types)
self::assertInstanceOf(Detail::class, $crateDetail);
self::assertCount(2, $crateDetail->getBoxes());
self::assertEquals(
'red',
$crateDetail->getBoxes()->first()->getColor()
);
}
}

这样就涵盖了您的创建逻辑;从这里,您可以简单地将工厂注入到您需要的地方,在单元测试期间,您只需将其模拟即可:

板条箱服务.php

class CrateServiceTest extends TestCase
{
private $crateDetailFactory;
private $crateService;
public function setUp(): void
{
$this->crateDetailFactory = $this->prophesize(CrateDetailFactory::class);
$this->crateService = new CrateService(
$this->crateDetailFactory->reveal()
);
}
public function testAMethodThatNeedsCrateDetails(): void
{
// Given
$crate = $this->givenIHaveACrateWithTwoBoxesInIt();
$boxes = $crate->getBoxes();
// When
$result = $this->crateService->AMethodThatNeedsCrateDetails();
// Then
$this->crateDetailFactory->createCrateDetail($crate, $boxes)
->shouldBeCalledOnce();
}
}

我希望这是有用的。干杯!:)

使用上面的类,要正确对此进行单元测试,您必须使用 DI 将Details::class注入getDetails()或__constructor。然后为每个类的每个方法编写测试,模拟任何类依赖项/属性

class Create
{
public function getDetails(Details $details)
}
//test.php
$mockDetails = $this->createMock(Details::class)
->expects($this-once())
->method('item1')
->with('some_arg')
->willReturn('xyz')
$mockBox = $this-createMock(Box::class)
......
$crate = new Create($boxMock);
$result = $crate->item1($mockDetails);
$this-assertSame('xyz', $result);

如果感觉像是你对一种方法的嘲笑方式,那么你应该考虑重构以使代码更易于测试。

对于多个项目的断言,在 PHPUnit 中,您可以使用数据提供程序将值数组作为单个测试传递给一个测试方法。 PHPUnit 文档 - 数据提供程序

您还将为 \Details::class 编写单独的单元测试,该单元测试断言传递给 \Details::setItem1($item( 的内容实际上是在 item1 属性上设置的。即。

测试 \详情::类 -

//test2
public function test() {
$details = new Details('some stuff');
$details->setItem1('expected');
self::assertSame('expected', $details->item1); 
}

最新更新