【问题标题】:Testing an abstract method of a child-class from an abstract class从抽象类测试子类的抽象方法
【发布时间】:2016-05-06 14:56:30
【问题描述】:

继续我使用here 的相同示例:

我现在想在我的子类中测试受保护方法的实现。
因为我在对抽象类的测试中将它们存根,所以实现本身没有经过测试。
但是受保护的方法没有被正常测试,所以我想听听你关于如何测试它们的建议。

就像my other thread 一样,我想在不重构我的代码的情况下解决这个问题

父类:

abstract class Order
{
    public function __construct( $orderId, User $user )
    {
        $this->id = $this->findOrderId( $user->getId(), $orderId );

        if ($this->id !== false) {
            $this->setOrderData();
        }
    }

    abstract protected function findOrderId( $userId, $orderIdToSearch );

    private function setOrderData()
    {
        ...
    }
}

要测试的子类:

public class OrderTypeA extends Order
{
    protected function findOrderId($userId, $orderId)
    {
        ...
    }
}

测试代码:

class OrderTypeATest extends PHPUnit_Framework_TestCase
{
    public function testFindOrderId() {
        ???
    }
}

【问题讨论】:

    标签: php unit-testing phpunit abstract-class


    【解决方案1】:

    您可以使用反射测试受保护/私有方法。阅读此tutorial。在其他解决方案中,您会找到直接的解决方案:

    /**
     * Call protected/private method of a class.
     *
     * @param object &$object    Instantiated object that we will run method on.
     * @param string $methodName Method name to call
     * @param array  $parameters Array of parameters to pass into method.
     *
     * @return mixed Method return.
     */
     public function invokeMethod(&$object, $methodName, array $parameters = array())
     {
         $reflection = new \ReflectionClass(get_class($object));
         $method = $reflection->getMethod($methodName);
         $method->setAccessible(true);
    
         return $method->invokeArgs($object, $parameters);
     }
    

    另外,关于您的上一个问题,您正在尝试测试抽象类。 phpunit mocking 的解决方案必须有效。但是如果你使用 PHP 7,你可以使用Anonymous classes 来达到同样的效果:

    abstract class Order
    {
        protected $id;
    
        public function __construct($orderId, $userId)
        {
            $this->id = $this->findOrderId($userId, $orderId);
    
            if ($this->id !== false) {
                $this->setOrderData();
            }
        }
    
        abstract protected function findOrderId($userId, $orderIdToSearch);
    
        private function setOrderData()
        {
            echo 'setOrderData';
        }
    }
    
    $orderId = 1;
    $userId = 1;
    
    $order = new class($orderId, $userId) extends Order {
        protected function findOrderId($userId, $orderIdToSearch)
        {
            return 1;
        }
    };
    

    您将得到工作的 $order 对象,该对象已准备好进行测试。此外,最好将此代码放在测试用例的 setUp() 方法中。

    【讨论】:

    • 感谢这真的帮助了我! :-)
    【解决方案2】:

    如果您仅在找到正确的订单时获得有效的$this->id。做一些类似的事情:

    $order = new OrderTypeA($orderId, $user);
    $this->assertNotEquals(false,$order->id);
    

    或者如果$orderId 等于$this->id

    $order = new OrderTypeA($orderId, $user);
    $this->assertEquals($orderId,$order->id);
    

    但此处显示的代码/逻辑不足以告诉您更多信息;)

    【讨论】:

    • findOrderId 方法只返回 id(= $orderId 如果找到)或 null 如果没有找到
    • 那么这个if ($this->id !== false) 首先必须是if ($this->id !== NULL)
    • 您可以使用我的答案的 OR 部分,如果没有找到 $orderID,也许您应该抛出一个异常来正确处理这种情况;通常你不会在 _constructor 中这样做。
    • 也许公开findOrderById 以将这些东西移出构造函数。
    • 很抱歉,如果找不到,它会返回 false
    【解决方案3】:

    你的抽象对我来说没有意义。

    我了解到您有一个表示订单的对象。您通过提供用户和订单 ID 来实例化它。但是订单不止一种,这些类型的订单之间的区别在于您在数据库存储中搜索它们的方式?这听起来不对。

    您的代码确实讲述了一个奇怪的故事。你有这个订单 ID,你要做的第一件事就是搜索订单 ID。我只是认为您已经拥有订单 ID,因此不需要再次搜索它。或者该方法的名称可能有误,而不是 findOrderId() 它应该被称为 findOrderById() - 或 findUserOrderById()

    此外,您确实在构造函数中工作。不应该在那里搜索东西。

    您的测试问题源于您决定将不同的搜索策略实现为抽象方法。您必须测试一个受保护的抽象方法,这并不容易。这也使得对主要的抽象订单类进行属性测试变得很困难,因为您必须提供一个实现——而且这个实现听起来像是隐藏了一个数据库访问层,所以在实际代码中可能会出现很多问题。

    我建议不要让订单自行搜索。搜索订单应该在订单对象之外完成。这样,您可能会将该搜索实现为可以正常测试的公共方法。然后搜索订单的代码将决定您是否成功找到了OrderTypeA,或者可能缺少MissingOrderTypeA,两者都扩展了订单类。订单对象应该携带订单数据,而不是在数据库中找到它们的搜索逻辑。

    提示:如果您在测试代码时遇到问题,99.9% 的可能性是您的代码试图以错误的方式做事。这并不是说事情不能那样做,而是说您将要生成难以测试的代码,这也难以维护,并且寻找替代策略来实现解决方案是一个好主意。优雅的代码总是很容易测试,因为所有必要的方法在相关类中都是公共的,因此可以按预期协同工作。

    【讨论】:

    • findOrderId 方法只是额外检查给定 ID 是否来自真实订单,因此如果不是,您不必设置所有数据。在这种情况下,我还想更改我的代码,只需对其进行测试。
    猜你喜欢
    • 2018-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    相关资源
    最近更新 更多