测试方法的行为类似于getter / setter,但更像是工厂

时间:2013-04-24 00:32:17

标签: unit-testing phpunit factory

假设我有一个处理MySQL数据库连接的小类:

class myDatabaseHandler{

  private $_databases;

  public function addDatabase($name, $dsn, $username, $password){
     $this->_databases[$name] = array('dsn' => $dsn, 'username' => $username, 'password' => $password);
     return true;
  }

  public function getDatabase($name){

     $connectionInfo = $this->_databases[$name];
     $database = new Database($connectionInfo['dsn'], $connectionInfo['username'], $connectionInfo['password']);

     $database->doSomeSetup();
     $database->attachMoreThings();

     return $database;
  }
}

我想对这两种方法进行单元测试:

class myDatabaseHandlerTest extends \PHPUnit_Framework_TestCase
{
    public function testAddDatabase(){

    }

    public function testGetDatabase(){

    }
}

我如何测试这两种方法?如果我addDatabase(),最多会返回Boolean告诉我操作成功。由于它写入私有属性,我无法确认是否确实写入了正确的数据。

我觉得使用getDatabase()来获取数据库对象并对其进行测试并不完全理想,因为我需要公开dsnusernamepassword只是为了测试。此外,Database对象可能会将这些值修改为它使用的格式,因此我需要存储原始值以供测试。

解决此问题的最佳方法是什么?

1 个答案:

答案 0 :(得分:0)

当您尝试构建在同一个地方使用对象时,测试肯定会变得棘手。在这种情况下,您要在Database方法中构建getDatabase($name)并在其上调用方法。例如,这使得几乎不可能模拟,并且为了获得合适的覆盖率,您的测试需要测试Database类提供的功能,以确保系统按预期运行。

更好的方法可能是使用合适的工厂作为依赖。

interface iDatabaseFactory 
{
    public function buildDatabase($dsn, $username, $password);
}

然后,您可以模拟数据库工厂和数据库实例本身,以验证它是否正确构造并正确初始化:

class MockDatabaseFactory implements iDatabaseFactory
{
    public $databaseParams = array();
    public $databaseToReturn = NULL;

    public function buildDatabase($dsn, $username, $password)
    {
        $this->databaseParams['dsn'] = $dsn;
        $this->databaseParams['username'] = $username;
        $this->databaseParams['password'] = $password;
        return $this->databaseToReturn;
    }
}

class myDatabaseHandlerTest extends PHPUnit_Framework_TestCase
{
    public function testAddAndGetDatabaseUsesCorrectDbParameters(){
        $mockDatabaseFactory = new MockDatabaseFactory();
        $dbHandler = new myDatabaseHandler($mockDatabaseFactory);

        // implement MockDatabase according to your interface
        $mockDatabase = new MockDatabase(); 
        $mockDatabaseFactory->databaseToReturn = $mockDatabase;

        $dbHandler.addDatabase("some name", "some dsn", 
                               "some username", "pa$$w0rd");
        $builtDatabase = $dbHandler.getDatabase("some name");

        $this->assertEquals($mockDatabase, $builtDatabase);
        $dbParams = $mockDatabaseFactory->databaseParams;
        $this->assertEquals("some dsn", $dbParams['dsn']);
        $this->assertEquals("some username", $dbParams['username']);
        $this->assertEquals("pa$$w0rd", $dbParams['password']);
    }

    public function testAddAndGetDatabaseInitializesDb(){
        $mockDatabaseFactory = new MockDatabaseFactory();
        $dbHandler = new myDatabaseHandler($mockDatabaseFactory);

        $mockDatabase = new MockDatabase();
        $mockDatabaseFactory.setDatabaseToBuild($mockDatabase);

        $dbHandler.addDatabase("name", "dsn", "user", "pass"); 
        $builtDatabase = $dbHandler.getDatabase("some name");

        $this->assertTrue($mockDatabase->doSomeSetupWasCalled);
        $this->assertTrue($mockDatabase->attachMoreThingsWasCalled);
    }
}