如何使用使用ORM的应用程序进行单元测试?

时间:2009-03-23 14:12:38

标签: php unit-testing phpunit propel

我已经查看了有关单元测试的各种问题,但找不到专门回答这个问题的问题。

我有几个包含如下函数的PHP类:

    static function _setSuspended($Suspended, $UserID)
    {
        try {
            $con = Propel::getConnection();

            $c1 = new Criteria();
            $c1->add(DomainsPeer::USERID,$UserID);

            $update = new Criteria();
            $update->add(DomainsPeer::SUSPENDED,$Suspended);

            BasePeer::doUpdate($c1, $update, $con);

            return true;
        } catch(PropelException $e) {
            return $e->getMessage();
        }
    }

我正在使用Propel作为我的ORM。我已经阅读了各种单元测试主题,讨论创建“模拟”和“存根”以及什么不是,但我找不到任何具体告诉您如何测试上述功能的内容。

我的想法是这样的:我需要测试上面的函数,所以我想调用它。但是,如果我称之为它,它使用Propel作为ORM,并且根据单元测试原则,我应该单独隔离每个函数。

我只是没有办法做到这一点。我在这里缺少什么?

5 个答案:

答案 0 :(得分:2)

这是一个通用的答案,因为我根本不熟悉Propel,只是对PHP更熟悉。基本答案是您使用依赖注入。不是直接引用你的ORM,而是在它周围创建一个包装器,然后将包装器注入你的类/函数中以实际使用。要进行单元测试,然后创建一个模拟或虚假版本的包装器,它不与ORM连接,而是允许您配置从包装器到方法调用的响应。这允许您在单元测试函数时分解ORM。

答案 1 :(得分:1)

我发现嘲笑ORM并没有给我任何信心,因为ORM配置永远不会被测试。 ORM在距离效应方面也有很多动作,这可能会对单元测试产生错误的信心。模拟数据库驱动程序或提供备用内存数据库让我更有信心,我的代码是正确的,并且与模拟ORM一样困难。

SQLite是一个用于单元测试的优秀内存数据库。它位于PDO支持的数据库列表中。 (PDO是Propel 1.3数据库驱动程序。)如果您不想使用内存数据库,您可能能够找到已编写的PDO模拟。

答案 2 :(得分:1)

我在构建PHPUnit plugin for Symfony时试图解决同样的问题。我最终接近它与Django's test framework类似 - 使用单独的数据库/连接,并在每次测试之前销毁并重建它。

我发现在测试运行中的第一次测试之前(或者如果测试明确指示它),我也能够只重建测试数据库;在其他测试之前,它只是删除所有数据以加快速度。

答案 3 :(得分:0)

我最近一直在阅读Misko Hevery's blog about testing。它涵盖了这种情况;你需要使用DI(依赖注入)。

我也在努力解决这个问题,我也使用推进器。

首先,您可以将“suspend”方法移动到“Object”类而不是对等方。无论如何,对于这个特定的函数,您不需要使用静态方法来实现此目的。您的API可能如下所示:

MyObjectPeer::retrieveByPK(1)->suspend();

这可以通过正常的单元测试方法测试。

如果它确实是需要测试的数据库,那么AFAIK你需要实际让数据库参与测试。我在当前项目中使用了ltree和postgis,我想不出任何其他方法来运行依赖于DB的模型逻辑的单元测试,而不是将其包含在我的测试中。

答案 4 :(得分:0)

这是一个具有硬依赖性的类的例子,它不能单元测试。

我们可以测试与另一个数据库的连接但是,它不再是单元测试,而是集成测试。

我想到的更好的选择是拥有一个QueryFactory类,它将包装你需要的所有不同的方法,然后,你将能够模拟它。

首先我创建一个接口

interface iQueryFactory
{
    function firstFunction($argument);
    function secondFunction($argument, $argument2);
}

QueryFactory包含我们需要的所有ORM请求

class QueryFactory implements iQueryFactory
{
    function firstFunction($argument) 
    {
        // ORM thing
    }

    function secondFunction($argument, $argument2)
    {
        // ORM stuff
    }
}

注入查询工厂有业务逻辑

class BusinessLogic 
{
    protected $queryFactory;

    function __construct($queryFactoryInjection) 
    {
        $this->queryFactory= $queryFactoryInjection;
    }

    function yourFunctionInYourBusinessLogique($argument, $argument2) 
    {
        // business logique

        try {
            $this->queryFactory->secondFunction($argument, $argument2);
        } catch (\Exception $e) {
            // log
            // return thing
        }

        // return stuff
    }
}

模拟部分,请注意我没有使用模拟框架作为我的例子(顺便说一句,你可以创建一个响应设置器)

class QueryFactoryMock implements iQueryFactory
{
    function firstFunction($argument) 
    {
        if (is_null($argument)) 
        {
            throw new \Exception("");
        } 
        else 
        {
            return "succes";
        }
    }

    function firstFunction($argument, $argument2) 
    { 
        // sutff  
    }
}

然后最后单元测试谁用模拟实现测试我们的业务逻辑

class BusinessLogicTest extends PHPUnit_Framework_TestCase 
{
    public function setUp() 
    {
        require_once "BusinessLogic.php";
    }

    public function testFirstFunction_WhenInsertGoodName() 
    {
        $queryMockup = new QueryFactoryMock();
        $businessLogicObject = new BusinessLogic($queryMockup);
        $response = $businessLogicObject ->firstFunction("fabien");

        $this->assertEquals($response, "succes");
    }

    public function testFirstFunction_WhenInsetNull() 
    {
        $queryMockup = new QueryFactoryMock();
        $businessLogicObject = new BusinessLogic($queryMockup);
        $response = $businessLogicObject->firstFunction(null);

        $this->assertEquals($response, "fail");
    }
}