【问题标题】:PHP dependency injection with constructor arguments making testing difficult带有构造函数参数的 PHP 依赖注入使测试变得困难
【发布时间】:2019-10-30 00:17:35
【问题描述】:

我正在尝试掌握一些依赖注入最佳实践。我正在尝试测试使用 braintree/braintree_php composer 库的服务类。假设我有一个像这样的BraintreeService 类:

namespace App\Service;

class BraintreeService
{
    /* @var Braintree_Gateway */
    private $gateway;

    public function __construct()
    {
        $this->gateway = new \Braintree_Gateway([
            'environment' => BRAINTREE_ENVIRONMENT,
            'merchantId' => BRAINTREE_MERCHANT_ID,
            'publicKey' => BRAINTREE_PUBLIC_KEY,
            'privateKey' => BRAINTREE_PRIVATE_KEY,
        ]);
    }

    /*
     * Generate a new client token from Braintree
     *
     * @return string $string
     */
    public function getClientToken() : string
    {
        return $this->gateway->clientToken()->generate();
    }
}

类的用法如下所示:

$btService = new \App\Service\BraintreeService();
$token = $btService->getClientToken();

明显的问题是这个服务类紧密依赖于Braintree_Gateway。因此,使单元测试BraintreeService 变得困难。为了使测试更容易,我想将Braintree_Gateway 移动到构造函数参数。这将允许我在单元测试中模拟 Braintree_Gateway 类。

但是,据我了解,如果我这样做,那么代码将如下所示:

namespace App\Service;

class BraintreeService
{
    /* @var Braintree_Gateway */
    private $gateway;

    public function __construct(Braintree_Gateway $gateway)
    {
        $this->gateway = $gateway;
    }

    /*
     * Generate a new client token from Braintree
     *
     * @return string $string
     */
    public function getClientToken() : string
    {
        return $this->gateway->clientToken()->generate();
    }
}

该类的用法如下所示:

$btService = new \App\Service\BraintreeService(
    new \Braintree_Gateway([
        'environment' => BRAINTREE_ENVIRONMENT,
        'merchantId' => BRAINTREE_MERCHANT_ID,
        'publicKey' => BRAINTREE_PUBLIC_KEY,
        'privateKey' => BRAINTREE_PRIVATE_KEY,
    ])
);
$token = $btService->getClientToken();

我觉得如果我在整个代码中的多个位置使用此服务会变得很麻烦。我想要一些关于如何更好地处理依赖关系同时仍然能够完全测试我的服务类的建议。谢谢!

【问题讨论】:

  • 您是否使用任何类型的框架或 DI 容器?
  • 目前没有,但如果我可以不使用整个框架,我不反对实施 DI 容器。我用过 Laravel,这些东西在那里更容易,但不幸的是在这种情况下无法迁移到 Laravel。
  • 如果不使用容器,您可以将您的服务创建封装在工厂类中
  • 我的测试会是什么样子?我还能测试BraintreeService::getClientToken()吗?
  • 您将在生产代码中使用工厂以减少实例化服务的麻烦,但可以在测试中使用模拟直接实例化它。尽管您提到的那个特定方法只是测试依赖关系。

标签: php dependency-injection mocking phpunit


【解决方案1】:

在构造函数中接受 null 作为默认值:

public function __construct(Braintree_Gateway $gateway=null) { 
  if(!gateway) { 
    $gateway = new \Braintree_Gateway([
    'environment' => BRAINTREE_ENVIRONMENT,
    'merchantId' => BRAINTREE_MERCHANT_ID,
    'publicKey' => BRAINTREE_PUBLIC_KEY,
    'privateKey' => BRAINTREE_PRIVATE_KEY,
    ]);
  }
}

这样你可以覆盖它进行测试,但在生产中保持向后兼容

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-12
    • 1970-01-01
    • 2021-10-16
    • 1970-01-01
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 2015-07-22
    相关资源
    最近更新 更多