让我们说有这样的功能:
function a()
{
$entity = $this->getEntity();
$entity->setSomePrivateVar();
$service = $this->getService();
$service->doSomething($entity);
}
我想测试一下
$service->doSomething($entity);
使用正确的$ entity调用。
$ entity调用setSomePrivateVar()
在实际的应用程序代码中,我做了类似的事情:
模拟实体并测试调用setSomePrivateVar。
模拟$ service并测试使用参数$ entity调用doSomething()。
看起来不错。
但问题是 - 如果我重构代码并首先在服务上调用doSomething()然后在$ entity上调用setSomePrivateVar(),则测试仍然会通过。
但是函数现在是错误的,因为doSomething依赖于由setSomePrivateVar()设置的$ entity private字段。
例如,我会重构:
function a()
{
$entity = $this->getEntity();
$service = $this->getService();
$service->doSomething($entity);
// this line moved
$entity->setSomePrivateVar();
}
所以看起来PhpUnit没有检查$ entity私有字段。如果它是例如array,则with()函数会看到传递的数组与预期的不一样。
那么我如何测试doSomething()获得正确状态的$ entity(在将实体传递给doSomething()之前在实体上调用了setSomePrivateVar())?
也许它有一些事情要做,$实体被嘲笑。
public function setNotifyUsers(AnnualConsolidation $consolidation, $status)
{
$consolidation->setNotifyUsers($status); // if move this method after the flush(), tesst does not fail
$this->entityManager->persist($consolidation);
$this->entityManager->flush();
}
public function testNotifyUsers()
{
$consolidation = $this->getMockBuilder(AnnualConsolidation::class)
->setMethods(['setNotifyUsers'])
->getMock();
$consolidation
->expects($this->once())
->method('setNotifyUsers')
;
$this->entityManager
->expects($this->at(0))
->method('persist')
->with($consolidation)
;
$this->entityManager
->expects($this->at(1))
->method('flush')
;
/** @var AnnualConsolidation $consolidation */
$this->consolidationsService->setNotifyUsers($consolidation, true);
}
我们正在讨论以这种方式测试setNotifyUsers方法是否合适。我试图在没有碰到数据库的情况下进行测试。一个人认为这可能需要通过命中数据库进行测试,因为如果重构方法没有改变逻辑,则可能需要进行重构测试。另一方面 - 这种方法不太可能被重构。
但是也许还有一种方法可以测试在persist()之后调用flush()而不告诉索引,因为在其他示例中,在持久化之前添加一些调用之后可能需要更新索引,因此可能是太多的工作让测试工作。
但是对于这个主题 - 首先我想知道如何使测试失败 - 如果我在flush()之后移动setNotifyUsers。测试没有失败。如果我们测试数据库,我们会看到$ consolidation状态没有更新。
一个人告诉检查,断言传递给persist方法的内容。我还没有尝试,但我不确定是否可以通过嘲笑$合并。嘲讽美元整合是否具有某种状态,因为真正的美元整合会有哪些?
答案 0 :(得分:0)
正如你在问题中所说的那样
一个人告诉检查,断言传递给持久化方法的内容。
这将是要走的路,但你的代码使得相当困难,我认为你应该重构一下以使代码可测试。
首先调用你的方法" setNotifyUsers"但它实际上做了2个动作,它调用整合对象上的setNotifyUsers,并保存/保存这些数据。 在我看来,这两种不同的行为属于两种不同的方法。如果您编写它可以帮助您进行测试:
public function setNotifyUsers(AnnualConsolidation $consolidation, $status) {
$consolidation->setNotifyUsers($status);
}
public function persistConsolidation(AnnualConsolidation $consolidation) {
$this->entityManager->persist($consolidation);
$this->entityManager->flush();
}
您可以单独测试setNotifyUser和persistConsolidation,并为调用这些函数的部分编写功能测试(使用consolidationsService的方法) 然后你可以使用at()功能来查看是否以正确的顺序调用这些函数。
但: 其次,您将状态作为此功能的合并,仅将其添加到一起。我不认为这样的东西属于服务而是属于方法 调用该服务。 移动该功能将再次给您带来麻烦,因为您无法测试它们的调用顺序。
但是你不需要使用mockBuilder来进行双倍的测试。 您可以创建一个实际为您保存数据的FakeConsolidation,而不是使用$ this-> getMockBuilder
然后你还需要一个模拟AnnualConsolidation,因为你希望能够检查值是否正确设置。
class FakeConsolidation extends AnnualConsolidation {
protected $id;
proteced $status;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function setNotifyUsers($status) {
$this->status = $status;
}
public function shouldNotifyUsers() {
$this->status
}
}
现在因为你将一个对象提供给具有状态的持久化合作,我们可以在"中使用"来检查该状态。一部分。
当然,我不确切知道你的代码是如何构造的,所以我做了一些假设,只需根据需要进行调整,并使用你拥有的接口。
就像你在这个问题中提出的那样,你甚至可以测试代码:
class SomethingTest extends PHPUnit_Framework_TestCase {
private $consolidationsService;
private $entityManager;
/**
* {@inheritdoc}
*/
public function setUp() {
$this->entityManager = $this->getMockBuilder(EntityManager::class)->getMock();
$this->consolidationsService = new ConsolidationsService($this->entityManager);
}
public function testNotifyUsers() {
$consolidation = new FakeConsolidation();
$consolidation->setId(1);
$this->entityManager
->expects($this->at(0))
->method('persist')
->with($this->callback(
function($savedConsolidation) {
return $savedConsolidation->shouldNotifyUsers() === true;
}
));
$this->entityManager
->expects($this->at(1))
->method('flush');
/** @var AnnualConsolidation $consolidation */
$this->consolidationsService->setNotifyUsers($consolidation, TRUE);
}
}
现在当你将setNotifyUsers移动到持久性
之下时with($this->callback(
function($savedConsolidation) {
return $savedConsolidation->shouldNotifyUsers() === true;
}
));
您的测试将失败,因为状态尚未设置。