【问题标题】:PHPUnit: How do you Unit test for multiple if-else/factory?PHPUnit:你如何对多个 if-else/factory 进行单元测试?
【发布时间】:2021-06-03 04:16:23
【问题描述】:

我有一个 ParentIdResolver 类,它根据类型返回产品的父 ID。
该类看起来像:

<?php

namespace App\Model;

use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;

class ParentIdResolver
{
    /**
     * @var Bundle
     */
    private $bundle;
    /**
     * @var Configurable
     */
    private $configurable;
    /**
     * @var Downloadable
     */
    private $downloadable;

    public function __construct(
        Bundle $bundle,
        Configurable $configurable,
        Downloadable $downloadable
    ) {
        $this->bundle = $bundle;
        $this->configurable = $configurable;
        $this->downloadable = $downloadable;
    }

    public function getParentId($productId, $productType)
    {
        $parentIds = [];
        if ($productType == 'bundle') {
            $parentIds = $this->bundle->getParentIdsByChild($productId);
        } elseif ($productType == 'configurable') {
            $parentIds = $this->configurable->getParentIdsByChild($productId);
        } elseif ($productType == 'downloadable') {
            $parentIds = $this->downloadable->getParentIdsByChild($productId);
        }
        return $parentIds[0] ?? null;
    }
}

我正在尝试将getParentId() 测试为:

<?php

namespace App\Test\Unit;

use PHPUnit\Framework\TestCase;
use App\Model\ParentIdResolver;
use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;

class ParentIdResolverTest extends TestCase
{
    protected $model;
    protected $bundleMock;
    protected $configurableMock;
    protected $downloadableMock;

    public function setUp(): void
    {
        $this->bundleMock = $this->createPartialMock(
            Bundle::class,
            ['getParentIdsByChild']
        );
        $this->configurableMock = $this->createPartialMock(
            Configurable::class,
            ['getParentIdsByChild']
        );
        $this->downloadableMock = $this->createPartialMock(
            Downloadable::class,
            ['getParentIdsByChild']
        );

        $this->model = new ParentIdResolver(
            $this->bundleMock,
            $this->configurableMock,
            $this->downloadableMock
        );
    }

    /**
     * @dataProvider getParentIdDataProvider
     */
    public function testGetParentId($productId, $productType, $parentId)
    {
        if ($productType == 'bundle') {
            $this->bundleMock->expects($this->any())
                ->method('getParentIdsByChild')
                ->willReturn([$parentId]);
        }
        if ($productType == 'configurable') {
            $this->configurableMock->expects($this->any())
                ->method('getParentIdsByChild')
                ->willReturn([$parentId]);
        }
        if ($productType == 'downloadable') {
            $this->downloadableMock->expects($this->any())
                ->method('getParentIdsByChild')
                ->willReturn([$parentId]);
        }

        $this->assertEquals($parentId, $this->model->getParentId($productId, $productType));
    }

    public function getParentIdDataProvider()
    {
        return [
            [1, 'bundle', 11],
            [2, 'configurable', 22],
            [3, 'downloadable', 33],
        ];
    }
}

而且我觉得我做的不正确,也许我需要重构主类?
请建议在这种情况下您将如何重构或编写单元测试。

【问题讨论】:

  • 如果测试成功并且代码有效,这将是Code review的更好候选者。
  • @El_Vanja 写得好!
  • 您可以从测试中删除所有if 语句,并立即模拟每个案例。这样测试会更简洁
  • @PtrTon 是的,这是重构单元测试的另一种选择。但是关于重构主类?你觉得有什么改进的余地吗?
  • 我给你留下了更详细的方法here

标签: php unit-testing phpunit tdd


【解决方案1】:

我个人会考虑将解决正确班级的责任转移到每个班级本身。有些人会称之为“问,不说”。它看起来像这样

<?php

namespace App\Model;

use App\Model\Product\ResolvesParentId;

class ParentIdResolver
{
    /** @var ResolvesParentId[] */
    private $parentIdResolvers;

    public function __construct(array $parentIdResolvers)
    {
        $this->parentIdResolvers = $parentIdResolvers;
    }

    public function getParentId(int $productId, string $productType): int
    {
        foreach ($this->parentIdResolvers as $parentIdResolver) {
            if ($parentIdResolver->supports($productType)) {
                return $parentIdResolver->getParentId($productId)[0] ?? null;
            }
        }

        return null;
    }
}
<?php

namespace App\Model\Product;

interface ResolvesParentId
{
    public function supports(string $productType): bool;

    public function getParentIdsByChild(int $productId): array;
}
<?php

namespace App\Model\Product;

class Bundle implements ResolvesParentId
{
    public function supports(string $productType): bool
    {
        return $productType === 'bundle';
    }

    public function getParentIdsByChild(int $productId): array
    {
        // Your implementation here.
    }
}
<?php

namespace App\Model\Product;

class Configurable implements ResolvesParentId
{
    public function supports(string $productType): bool
    {
        return $productType === 'configurable';
    }

    public function getParentIdsByChild(int $productId): array
    {
        // Your implementation here.
    }
}
<?php

namespace App\Model\Product;

class Downloadable implements ResolvesParentId
{
    public function supports(string $productType): bool
    {
        return $productType === 'downloadable';
    }

    public function getParentIdsByChild(int $productId): array
    {
        // Your implementation here.
    }
}

有些人认为这太过分了,这完全取决于您所处的情况。您是否期望 if/else 将来会增长?那么这个解决方案可能适合你。

【讨论】:

  • 您的意思是“告诉,不要问”TDA 方法?
  • 现在我觉得一直错在很傻:)
  • 你会如何使用它? #用法?
  • 您将一个包含Bundle、Configurable 和Downloadable 实例的数组注入ParentIdResolver。然后它将在该数组中寻找正确的实现并根据它解析父 id
猜你喜欢
  • 1970-01-01
  • 2010-09-07
  • 2017-06-10
  • 2018-10-09
  • 2011-06-06
  • 2012-08-18
  • 1970-01-01
  • 2011-01-07
  • 2019-05-05
相关资源
最近更新 更多