创建测试数据

背景

一般来讲,在运行单元测试的时候,每一个 case 都应该拥有完全初始化的数据库状态,也就是空数据,然后由 case 自己制造数据,所以需要一个相对方便的制造测试数据的方式,但是 Hyperf 好想没有对应的组件,所以只好自己搞出来一个,这里采用的方案参考了 Laravel

实现

  • 在项目根目录新建 model_factory 目录,在 model_factory 中新建基类 ModelFactory,具体代码如下

    <?php
    
      declare(strict_types=1);
    
      namespace ModelFactory;
    
      use Faker\Factory;
      use Hyperf\DbConnection\Model\Model;
    
      class ModelFactory
      {
          protected $model;
    
          /** @var Model */
          protected $modelIns;
    
          protected $faker;
    
          protected $loopNumber = 0;
    
          public function __construct()
          {
              $this->modelIns = new $this->model();
              $this->faker = Factory::create('zh_CN');
          }
    
          public function __call($name, $arguments)
          {
              return $this->modelIns->newQuery()->{$name}(...$arguments);
          }
    
          public function create(array $coverData = [])
          {
              return $this->modelIns->newQuery()->create(array_merge($this->definition(), $coverData));
          }
    
          public function many(int $number)
          {
              $this->loopNumber = $number;
              return $this;
          }
    
          public function createMany(array $coverData = [])
          {
              for ($i = 0; $i < $this->loopNumber; ++$i) {
                  $this->modelIns->newQuery()->create(array_merge($this->definition(), $coverData));
              }
          }
    
          public function definition()
          {
              return [];
          }
      }
    
  • 同 Laravel 一样,我们为每个 Model 设置其对应的 Factory 来创建数据,其配置也非常简单,举个🌰

    <?php
    
      declare(strict_types=1);
    
      namespace ModelFactory\Config;
    
      use App\Model\User\UserTagConfig;
      use ModelFactory\ModelFactory;
    
      class UserTagConfigFactory extends ModelFactory
      {
          public $model = UserTagConfig::class;
    
          public function definition()
          {
              return [
                  'tag_name' => $this->faker->title,
              ];
          }
      }
    

    如此,Model UserTagConfig 继承 ModelFactory,并设置成员属性 $model 指明 Model 类,然后重写 definition 方法,设置此 model 的字段值以用作生成,另外值的指出的是,由于基类已经设置了 Faker 类,所以在具体的 factory 中可以直接使用 this->faker 来创建虚拟数据

  • 使用也非常简单,依旧使用具体的代码来举例

      class UserTagTest extends BaseTest
      {
          public function testUserTag()
          {
              (new UserTagConfigFactory())->create();
              (new UserTagConfigFactory())->create([
                  'other' => 'xxx',
              ]);
              (new UserTagConfigFactory())->many(20)->createMany();
              $service = ApplicationContext::getContainer()->get(UserTagService::class);
              $tagList = $service->fetchTagList();
              $this->assertEquals(20, count($tagList[0]['tags']));
              $service->setUserTag(1, 1, range(1, 10));
              $service->setUserTag(1, 2, range(5, 10));
              $service->setUserTag(1, 3, range(10, 15));
              $userTags = $service->fetchUserTags(1);
              $this->assertEquals(6, count($userTags[2]['tags']));
          }
      }
    

    基于一个简单的单测 case,我们可以很清晰的看出 Factory 是如何被使用的,直接调用 create 方法会使用 definition 中设置的值来创建数据,当然 create 支持传参后覆盖初始设置,many 方法可以指定需要创建的数据个数,只不过后续需要调用 createMany 方法来执行创建,当然和 create 方法一样,支持传参后覆盖初始设置

  • 当然我们需要将 model_factory 目录纳入 composer 加载目录,对 composer.json 做如下修改,在 autoload-dev.psr-4 中增加配置项

     "autoload-dev": {
          "psr-4": {
              "HyperfTest\\": "./test/",
              "ModelFactory\\": "./model_factory"
          }
      },