用于生成测试用例的 Plain Old Php Object 工厂。

1.0.0 2016-01-13 16:27 UTC

This package is not auto-updated.

Last update: 2024-09-24 19:11:49 UTC


README

Build Status

Plain Old Php Object 工厂

这是一个用于在 PHP 中生成对象图的库。它主要用于与其他库结合生成数据测试用例。它不包含将数据持久化到数据库的功能;用户需要自行确定最佳方法。

为什么编写这个库?

许多测试用例库对底层存储机制做出了假设,或者相对复杂。对于某些测试,可能只需要访问从数据存储中检索到的对象,而不需要在实际测试中涉及这一层。但在集成测试中,你确实希望数据库层参与。Popov 允许您以相对简单但仍然适用于多种环境的方式定义测试用例模型。通过不做出关于持久化的假设,您可以自由地将此库集成到您的应用程序中,并保持完全的控制。

安装

将其添加到项目的 composer.json 文件中

{
    "require": {
        "globalprofessionalsearch/popov": "~1.0"
    }
}

用法

以下是一些用法示例,从最简单的开始,逐渐增加复杂度。由于测试用例定义通常需要使用闭包,并且 Faker 在生成数据点方面被广泛使用,我们强烈建议使用 league/factory-muffin-faker 库与 Popov 结合使用。以下大多数示例都使用了它,您会看到使用外观极大地减少了手动在字段定义中包装闭包的需求。

以下示例显示了可能存在于应用程序中的虚构类集合的定义。假设这些类存在,并为描述的属性提供 getter/setter。这不是要求 - Popov 将使用反射来设置任何定义的公共/受保护/私有属性。

以下每个示例都添加到前一个示例中。

定义池

假设我们有一些用户和组,我们需要为每个生成假实例。以下示例定义了两个对象池,每个池中预设了实例数量。当您定义一个池时,您提供用于创建对象的完全限定类名(FQCN),以及在池初始化时要创建的实例数量选项。在这里,我们将定义生成 100 个用户和 20 个组的池

<?php

use GPS\Popov\Facade as Factory;
use League\FactoryMuffin\Faker\Facade as Faker;

// Returns an instance of `GPS\Popov\Factory`
$factory = Factory::instance();

// Define a pool of 100 users; `Factory::definePool` returns an instance 
// of `GPS\Popov\Definition`.  Defining a pool creates and stores an instance 
// of `GPS\Popov\Pool` in the factory.
$factory->definePool('App\User', 100)->setAttrs([

  //use the Faker facade to easily wrap calls to faker in closures...
  'firstName' => Faker::firstName(),
  'lastName' => Faker::lastName(),
  
  // ... or set explicit values, which will be the same for all objects
  'enabled' => true,
  
  // ... or reference any callable
  'address' => 'Some\Class::generateAddress',
  
  // ... or define custom closures to be called. Closures will receive the 
  // instance of the object that is being created
  'fullName' => function($user) {
    return $user->getFirstName().' '.$user->getLastName();
  }
]);

// Define a pool of 20 groups
$factory->definePool('App\Group', 20)->setAttrs([
  'number' => Faker::randomNumber()
]);

//  You can retrieve generated objects by accessing the created pools 
// directly...
$user1 = $factory->getPool('App\User')->fetchRandom();
$group1 = $factory->getPool('App\Group')->fetchRandom();

// ... or you can use the wrapper methods on the factory to interact with the
// underlying pool instances
$user2 = $factory->fetchRandom('App\User');
$group2 = $factory->fetchRandom('App\Group');

第一次从工厂检索对象时,工厂将按照定义的顺序初始化所有定义的池。

您还可以使用别名定义池。这样做可以避免使用 FQCN 来引用池,但如果需要为同一类创建多个配置不同的池,这也提供了这个选项。

<?php

//...

$factory->definePool('User:MyApp\Models\User')->setAttrs([
  // ...
]);

$pool = $factory->getPool('User');

检索对象

一旦您定义了池,您可以通过使用 GPS\Popov\Pool 提供的 API 来检索创建的对象。您可以直接从工厂检索池,或者使用工厂提供的包装方法。

池提供了一些检索对象的方式

$obj = $factory->getPool('App\User')->fetchRandom();
$objs = $factory->getPool('App\User')->fetchMultipleRandom();
$obj = $factory->getPool('App\User')->fetchBy('firstName', 'Foobert');
$objs = $factory->getPool('App\User')->fetchMultipleBy('isEnabled', true);
$objs = $factory->getPool('App\User')->fetchMatching(function ($obj) {
  // if the callable returns true, this object will be added 
  // to the returned set
  return in_array('Foobert', ['Alice','Bob', 'Foobert']);
});
$objs = $factory->getPool('App\User')->fetchAll();

嵌套

为了在上面的示例中添加更多内容,假设用户还有账户偏好,这是一个嵌套对象。每次创建用户时,都应该创建一个用于存储其偏好的嵌套对象。我们将更新上面的用户定义,并添加一个新的定义

<?php

// ...

$factory->definePool('App\User', 100)->setAttrs([
  'firstName' => Faker::firstName(),
  'lastName' => Faker::lastName(),
  'preferences' => Factory::create('App\AccountPrefs')
]);

// Note that we don't define a starting number for the pool.  In this case 
// there is no need since an instance will just be created any time a user is 
// created
$factory->definePool('App\AccountPrefs')->setAttrs([
  'allowEmails' => true,
]);

// the fetched user should contain the nested object
$allow = $factory
  ->fetchRandom('App\User')
  ->getPreferences()
  ->getAllowEmails()
;

引用

嵌套对象相当简单,但有时你需要从另一个池中获取对象,并且你不应该触发创建新的对象。例如,假设我们希望所有用户都在一个组中,但我们不希望为每个用户创建一个新组。为此,我们使用工厂来添加引用。引用实际上与属性是同一件事,但工厂会在所有池初始化之后解决它。这允许你从其他池中获取引用对象,包括循环依赖,而不会冒着无限循环的风险。

要向另一个池中的对象添加引用,我们只需使用工厂外观将操作包装在闭包中

<?php

//...

$factory
  ->definePool('App\User', 100)
  ->setAttrs([
    // ...
  ])
  ->setRefs([
    'groups' => Factory::fetchMultipleRandom('App\Group', 5)
  ])
;

$factory
  ->definePool('App\Group', 20)
  ->setAttrs([
    // ...
  ])
  ->setRefs([
    'owner' => Factory::fetchRandom('App\User')
  ])
;

钩子

Popov还提供了一种在每个对象创建后以及整个池初始化后调用回调的方法。

//...

$factory
  ->definePool('App\User')
  ->setAttrs([
    // ...
  ])
  ->setRefs([
    // ...
  ])
  ->after(function($user) {
    // this will be called for each user after it's been created
    
    $user->doSomething();
  })
;

$factory
  ->getPool('App\User')
  ->after(function($pool) {
    // this will be called after the pool is
    // initialized
    foreach($pool->fetchAll() as $obj) {
      // ... do something
    }
  })
;

硬编码对象

你可能希望池中有50个随机对象,但你可能也想要一些你可以引用以进行测试的对象,这些对象是由你明确定义的,这样你就可以事先知道预期的内容。如果你已经定义了一个池,你可以手动创建新实例,无论它是否会自动填充预设的数量。任何手动创建的实例都将被添加到池中。

但这有一个重要的含义——如果你想创建一些明确的对象,你应该在定义所有你的池之后这样做。手动创建对象将触发所有定义的池的初始化。

你可以在创建时间通过覆盖定义来创建特定的对象

<?php

//... define some pools

// create a hard coded user... any attributes you provide
// will be merged with the existing attributes from the pool
$user = $factory->create('App\User', [
  'firstName' => 'Foobert',
  'lastName' => 'Bartleby',
  'preferences' => Factory::create('App\AccountPrefs', [
    'allowEmail' => false
  ])
]);

与持久性相关的示例

如果你知道你应用程序的要求,处理持久性可以相当简单。虽然Popov没有提供这一点作为默认功能,下面是一个简单的示例,说明在使用Doctrine的应用程序中如何实现。

<?php

// ...

$factory->definePool('App\User', 100)->setAttrs([/* ... */]);
$factory->definePool('App\Group', 20)->setAttrs([/* ... */]);
$factory->definePool('App\Transaction', 50)->setAttrs([/* ... */]);

// ... maybe hard-code some example fixtures

// assuming we know that certain objects should be persisted in a certain order...
$persistOrder = [
  'App\User',
  'App\Group',
  'App\Transaction'
];

// use doctrine to persist each object in each pool
// in the needed order
foreach ($persistOrder as $poolName) {
  $pool = $factory->getPool($poolName);
  foreach ($pool->fetchAll() as $obj) {
    $doctrineManager->persist($obj);
  }

  $doctrineManager->flush();
}

贡献

欢迎贡献——只是请遵守[PSR]编码标准。

测试

使用vendor/bin/phpunit运行测试。