首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何以正确的状态通过对象的单元测试?

如何以正确的状态通过对象的单元测试?
EN

Stack Overflow用户
提问于 2017-03-21 19:59:30
回答 1查看 898关注 0票数 3

让我们说有这样的功能:

代码语言:javascript
复制
function a() 
{
    $entity = $this->getEntity();

    $entity->setSomePrivateVar();

    $service = $this->getService();

    $service->doSomething($entity);

}

我想测试一下

代码语言:javascript
复制
 $service->doSomething($entity);

是用正确的$entity调用的。

$entity调用setSomePrivateVar()

在实际应用程序代码中,我执行了如下操作:

获取实体的模拟并测试是否调用了setSomePrivateVar。

获取$service的模拟,并测试使用参数$entity调用doSomething()。

看上去没问题。

但问题是--如果我重构代码并首先在服务上调用doSomething(),然后在$entity上调用setSomePrivateVar(),那么测试仍然可以通过。

但是这个函数现在错了,因为doSomething依赖于由setSomePrivateVar()设置的$entity私有字段。

例如,我将对此进行重构:

代码语言:javascript
复制
function a() 
{
    $entity = $this->getEntity();

    $service = $this->getService();

    $service->doSomething($entity);

    // this line moved
    $entity->setSomePrivateVar();

}

因此,看起来PhpUnit没有检查$entity私有字段。如果是例如数组,那么with()函数将看到传递的数组与预期的不一样。

那么,如何测试doSomething()是否使$entity处于正确的状态(在将setSomePrivateVar()传递给doSomething()之前对实体进行了调用)?

也许$entity被嘲笑是有什么用的。

用真实世界的例子进行更新

代码语言:javascript
复制
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方法是否有效。我试着试着不去访问数据库就进行测试。一个人认为这可能需要用命中数据库进行测试,因为如果不改变逻辑的重构方法,则可能需要进行测试来重构。另一方面,这种方法不太可能被重构那么多。

但是,也可能有一种方法,只需要测试()之后调用flush(),而不告诉索引,因为在其他示例中,在持久化之前添加了一些调用之后,可能需要更新索引,因此可能需要做太多的工作才能使测试工作正常。

,但是对于这个主题--首先,我想知道如何使测试失败--如果在setNotifyUsers ()之后移动,那么测试不会失败。如果我们使用命中数据库进行测试,我们将看到$consolidation状态没有更新。

一个人告诉我们要检查,断言传递给持久化方法的是什么。我还没有尝试,但我不确定在模拟的$consolidation上这是否是可能的。被嘲弄的$consolidation是否像真实的$consolidation那样有某种状态?

EN

回答 1

Stack Overflow用户

发布于 2017-03-30 21:21:52

正如你在问题中所说的

一个人告诉我们要检查,断言传递给持久化方法的是什么。

这是可行的方法,但是您的代码会使这非常困难,我认为您应该稍微重构一下,以使代码可测试。

首先,您的方法名为" setNotifyUsers“,但它实际上执行了两个操作,它在合并对象上调用了setNotifyUsers,并保存/保存了这些数据。在我看来,这是两种不同的行动,应该属于两种不同的方法。如果你写成这样的话,它可以帮助你的测试:

代码语言:javascript
复制
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来进行双重测试。与使用$this->getMockBuilder不同,您还可以创建一个实际为您保存数据的FakeConsolidation

然后,您还需要一个AnnualConsolidation的模拟,因为您希望能够检查该值是否被正确设置。

代码语言:javascript
复制
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
  } 
}

现在,因为您将为具有状态的持久化提供一个对象,所以我们可以在"with“部分中检查该状态。

当然,我不知道您的代码是如何构造的,所以我做了一些假设,只要在需要的地方进行调整,并使用您所拥有的接口。

就像这样,您甚至可以在这个问题中展示代码时测试它:

代码语言:javascript
复制
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移动到持久化下面时

代码语言:javascript
复制
with($this->callback(
              function($savedConsolidation) {
                return $savedConsolidation->shouldNotifyUsers() === true;
              }
          )); 

您的测试将失败,因为状态尚未设置。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42937049

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档