如何为使用实时数据库数据的方法编写 PHP 单元测试?

Den*_*nis 1 php phpunit unit-testing php-7

我究竟如何为使用实时数据库的方法编写测试?

考虑这个代码:

class PricingRepository extends GenericRepository
{
    public function getOptionPrice(int $productId, int $quantity, float $productPrice = 0.0): float
    {
        //retrieves option record for a given product
        $row = $this->getMySql()->paramQuery("
            select * from pricing
            where product_id = ?", array(
            $productId
        ))->getSingleArray();

        //based on pricing type computes appropriate value
        if ($row['pricing_type'] === 'Quantity-based')
            return $row['base'] + $row['amount_per_quantity'] * $quantity;
        if ($row['pricing_type'] === 'Percentage-based')
            return $productPrice * $row['percentage'];

        throw new \InvalidArgumentException("invalid pricing type detected");
    }
}
Run Code Online (Sandbox Code Playgroud)

我有很多像上面那样的方法,所以我想确保我的测试是可靠的,并且不会在数据库数据发生变化时发生变化。我正在寻找关于一流单元测试方法的建议/解决方案,以及一种可能不依赖于数据库中数据更改的方法。

我现在编写一个简单的单元测试的方式可能是这样的:

use PHPUnit\Framework\TestCase;
class OptionPricingTest extends TestCase
{    
    function setUp()
    {
        $this->pricingRepository = new PricingRepository();
    }

    function testOptionPricing()
    { 
        $actual_option_price = $this->pricingRepository->getOptionPrice(111, 1, 100);
        $this->assertEquals(10.0, $actual_option_price);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是如果数据或定价类型发生变化,我的测试也必须改变。

nib*_*bra 6

存储库的设计使其难以测试。

使用依赖注入

考虑不要在存储库类中创建数据库连接,而是通过构造函数注入它。

interface DBInterface
{
    public function paramQuery($query, array $params = []): DBInterface;
    public function getSingleArray(): array;
    // ...
}

class GenericRepository
{
    /** @var DBInterface */
    private $mysql;

    public function __construct(DBInterface $mysql)
    {
        $this->mysql = $mysql;
    }

    protected function getMySql(): DBInterface
    {
        return $this->mysql;
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后很容易注入一个模拟对象。

模拟依赖

对于上面的测试用例,模拟可能如下所示:

class MysqlMock implements DBInterface
{
    private $resultSet = [];
    private $currentQuery;
    private $currentParams;

    public function paramQuery($query, array $params = []): DBInterface
    {
        $this->currentId = array_shift($params);
    }

    public function getSingleArray(): array
    {
        return $this->resultSet[$this->currentId];
    }

    public function setResultSet($array records)
    {
        $this->resultSet = $records;
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

这样,您就独立于价格的实际变化和产品的移除。如果您的数据结构发生变化,您只需更改测试。

use PHPUnit\Framework\TestCase;
class OptionPricingTest extends TestCase
{    
    private $pricingRepository;
    private $mysqlMock;

    public function setUp()
    {
        $this->mysqlMock         = new MysqlMock;
        $this->pricingRepository = new PricingRepository($this->mysqlMock);
    }

    public function testOptionPricing()
    { 
        $this->mysqlMock->setResultSet([
            111 => [
                'pricing_type'        => 'Quantity-based',
                'base'                => 6,
                'amount_per_quantity' => 4,
            ]
        ]);

        $actual_option_price = $this->pricingRepository->getOptionPrice(111, 1, 100);
        $this->assertEquals(10.0, $actual_option_price);
    }
}
Run Code Online (Sandbox Code Playgroud)