从PHPUnit测试访问数据库

时间:2015-01-16 16:33:44

标签: php mysql unit-testing mocking phpunit

我仍然是PHPUnit的新手,我正在试图找出如第3类所示的最佳测试方法。

我理解模拟数据库是如何工作的(我认为),因为它们可以根据输入,XML文件等返回值。我不确定如何为第3个示例提供数据,当SQL查询是在方法本身内部运行。

我正在尝试测试从数据库访问信息并对其执行操作的代码。目前无法将模拟的数据库数据(例如,数组)提供给这些方法。

问题:向内部处理所有SQL查询的方法提供数据的最佳方法是什么?

<?php
class ThisMakesSense {

    public function checkPassword($original, $hash) {
        return (md5($original) == $hash);
    }

}


class ThisMakesSenseTest {

    public function testCheckPassword() {
        $tms = new ThisMakesSense();
        $data = array('jdoe@example.com' => 'password1', 'bsmith@example.com' => 'password2');        
        foraech ($data as $email => $password) {
            $hash = $this->mockDB()->doStuff()->getPasswordHashByEmail($email);
            $this->assertTrue($tms->checkPassword($password, $hash), "Password for {$email} is invalid");
        }

        $tms->checkPassword($password, $hash);
    }
}

/* The '$this->_db' object is basically an OOP way of using the
 * mysql_* /mysqli_* functions. Replacing it is not an option
 * right now.
 */
class DontUnderstand {
    public function checkPassword($email, $password) {
        $this->_db->query("SELECT password_hash FROM users WHERE email = '{$email}'");
        $row = $this->_db->fetchAssoc();
        return (md5($password) == $row['password_hash']);
    }
}


class DontUnderstandTest extends PHPUnit_Framework_TestCase {
    public function testCheckPassword() {
        $du = new DontUnderstand();
        $data = array('jdoe@example.com' => 'password1', 'bsmith@example.com' => 'password2');

        foreach ($data as $email => $pass) {
            $this->assertTrue($du->checkPassword($email, $pass), "Password for {$email} is invalid");
        }
    }
}

(为了省去评论的麻烦,md5和查询方法只是一个简单的例子)

1 个答案:

答案 0 :(得分:1)

我不确定什么是最好的方法,但这是我的方式。它基于一个内部与数据库和单个表连接的类的假设。访问是通过INSERT,UPDATE,DELETE,SELECT等类似的,因此没有复杂的JOIN或UNION。另一个假设是我在运行phpunit之前建立了一次数据库(不是测试例程的一部分)。我查看了PHPUnit的数据库扩展,但在我看来这里使用起来太麻烦了,所以我很快就嘲笑了这个:

class UserProfileTest extends PHPUnit_Framework_TestCase {

  protected static
    $options,   
    $dbHandle;      

  public static function setUpBeforeClass() {
    self::$options = array(
               'dbHostName' => 'localhost',
               'dbUserName' => 'tester',
               'dbPassword' => 'pw4tester',
               'dbName' => 'test',
               'dbTableName' => 'Test',
               );            
    self::$dbHandle = new mysqli( self::$options[ 'dbHostName'], self::$options[ 'dbUserName'], self::$options[ 'dbPassword'], self::$options[ 'dbName'] );
    if( self::$dbHandle->connect_errno)
      exit( 'Error: No DB connection.');
  }

  protected function fillDb() {
    $query = 'INSERT INTO ' . self::$options[ 'dbTableName'] . ' VALUES (default,"foo","bar",7)';
    if( !self::$dbHandle->query( $query) )
      exit( 'Error: Could not fill DB.');
  }

  protected function setUp() {
    // always start a TC with empty DB
    $query = 'DELETE FROM ' . self::$options[ 'dbTableName'];
    if( !self::$dbHandle->query( $query) )
      exit( 'Error: Could not empty DB.');
  }

  // -- test --
  public function testGetNumberOfProfiles() {
    $profileMgr = new UserProfile( self::$options);
    $this->assertEquals( 0, $profileMgr->getNumberOfProfiles() );
    $this->fillDb();
    $this->assertEquals( 1, $profileMgr->getNumberOfProfiles() );
    $this->fillDb();
    $this->assertEquals( 2, $profileMgr->getNumberOfProfiles() );
  }

因此,在实例化类(setUpBeforeClass)时连接到DB,并在每个测试用例之前清空表(在setUp中)。有一个辅助函数可以在表中插入一行;它在需要时被调用。