dopq87915 2018-07-12 18:18
浏览 73
已采纳

SUT父级使用get_class检查依赖项

It started when I was performing null checks everywhere to make sure I have the necessary entities for my interactor. Fortunately, I came across this post which points towards not allowing the entities to be in an invalid state do the check in your constructor. Now my Interactors use protected static $request to state explicitly which entities they require, which are then passed in during instantiation. I chose static so the check could be done prior to creating an instance of the Interactor.

abstract class Interactor {
  protected static $request = [];
  protected $entities = [];

  final public function __construct(Entity ...$entities) {
    $this->setEntities(...$entities);
    $this->checkEntities();
  }

  final private function setEntities(Entity ...$entities) {
    foreach($entities as $entity) {
      $this->setEntity($entity);
    }
  }
  final private function setEntity(Entity $entity){
    $className = get_class($entity);

    if (!in_array($className, static::$request)){
      throw new Exception("Not a requested entity");
    }
    $this->entities[$className] = $entity;
  }
  final private function checkEntities(){
    if (count(static::$request) != count($this->entities))
      throw new Exception("Entity mismatch");

    foreach(static::$request as $index=>$name) {
      if (!array_key_exists($name, $this->entities))
        throw new Exception("Missing requested entity ($name)");
      if (!is_a($this->entities[$name], $name))
        throw new Exception("Not the specified entity");
    }
  }

  final public static function getRequest(){
    return array_values(static::$request);
  }
}

Ok great, now I just do the check in a single location and I don't need to worry about performing null checks at the beginning of my functions. The problem with the way I am going about it now is that my Interactor is checking the class name against a static class name request array. Thus, when I DI the mocked entities during testing, my parent Interactor throws an exception saying it isn't in the pre approved list.

To demonstrate is the following simplified Chess example:

class Chess extends Interactor {
  protected static $request = ['Piece','Engine','Board'];
}

Then we have our Entities:

abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}

And finally our test:

class ChessTest extends TestCase {
  function setUp(){
    $this->piece = $this->getMockBuilder(Piece::class)->getMock();
    $this->engine = $this->getMockBuilder(Engine::class)->getMock();
    $this->board = $this->getMockBuilder(Board::class)->getMock();

    $this->chess = new Chess($this->piece, $this->engine, $this->board);
  }
  function testCanSetup(){
    $this->assertTrue(
      is_a($this->chess, Chess::class)
    );
  }
}

Which throws Exception: Interactor receiving entity not requested (Mock_Piece_faaf8b14)

Of course Mock_Piece_faaf8b14 is not going to be in our static::$request array, so this is destined to throw an exception.

The workaround I have come up with so far is to include in Entity:

public function getClassName(){
  return get_called_class();
}

Then in Interactor->setEntity($entity) instead of using get_class($entity) I would use $entity->getClassName() which then becomes trivial to mock.

I thought the way I had created the Interactor was inline with what the previously mentioned post was getting at, only take the entities in the constructor. However, it all feel apart when I injected mocked entities.

1) Is there a way to avoid getClassName() in my entities?

2) Is there something in the entities I can mock that gets called in get_class() instead?

Thank you for your help!

  • 写回答

1条回答 默认 最新

  • dongzhao3040 2018-07-12 19:21
    关注

    You are checking to see if the name of your class is one of the keys in your $request array. And it isn't. The keys in your array are numerical 0, 1, 2 so you are throwing the exception. I think that you want to use in_array instead.

    Though at the same time, this still wouldn't pass with the mock because you are checking to see if the class name is in $request. So the name won't be there at all either and the exception will still be thrown.

    If all that your Interactor class is doing is making sure that the correct objects are passed into the constructor why not just use PHP's native type hinting?

    Your Chess class becomes:

    class Chess {
        public function __construct(Piece $piece, Engine $engine, Board $board) { }
    }
    

    PHP will make sure that the passed in objects are of the correct type and will allow you to mock them for testing.

    You get the type checking that you are looking for without need to use getClassName() at all.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 #MATLAB仿真#车辆换道路径规划
  • ¥15 java 操作 elasticsearch 8.1 实现 索引的重建
  • ¥15 数据可视化Python
  • ¥15 要给毕业设计添加扫码登录的功能!!有偿
  • ¥15 kafka 分区副本增加会导致消息丢失或者不可用吗?
  • ¥15 微信公众号自制会员卡没有收款渠道啊
  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘