在PHPUnit中的内存SQLITE数据库中模拟数据

3

我目前正在学习如何在PHPUnit框架中测试数据库,并遇到了这样一个问题:我不想在我的测试中连接真正的数据库。因为当我在另一台电脑上运行测试时,该计算机可能没有相同的数据库。

我实现了\PHPUnit\DbUnit\TestCaseTrait特性并设置了以下方法:

/**
 * Returns the test database connection.
 *
 * @return \PHPUnit\DbUnit\Database\Connection
 */
protected function getConnection()
{
    $pdo = new PDO('sqlite::memory:');
    return $this->createDefaultDBConnection($pdo, ':memory:');
}

/**
 * Returns the test dataset.
 *
 * @return \PHPUnit\DbUnit\DataSet\IDataSet
 */
protected function getDataSet()
{
    return $this->createXMLDataSet(dirname(__FILE__) . '/test-dataset.xml');
}

数据集文件已经存在,且被正确地找到。

在我的测试中的setUp方法中,我将对象中的一个变量设置为\PDO实例。

/**
 * @var PDO $databaseServerConnection
 */
private $databaseServerConnection;

public function setUp()
{
    $this->databaseServerConnection = $this->getConnection()->getConnection();
}

我希望我可以使用从 getDataSet() 方法中获取的数据来连接 PDO。

为了尝试自己做,我尝试将其与以下代码进行比较:

# Specify the tables we want to have in our connection dataset
$tables = ['users'];

# Create the dataset in the connection with the tables
$dataset = $this->getConnection()->createDataSet($tables);

# Query all results from the user table  in the connection
$queryTable = $this->getConnection()->createQueryTable(
    'users', 'SELECT * FROM users'
);

# Get the raw table data from the dataset file
$expectedTable = $this->getDataSet()->getTable('users');

# Check if theyre equal
$this->assertTablesEqual($queryTable, $expectedTable);

在调试时,我注意到$dataset内的$tables数组变量是空的。这里是$dataset变量的var_dump

class PHPUnit\DbUnit\Database\FilteredDataSet#18 (3) {
  protected $tableNames =>
  array(1) {
    [0] =>
    string(5) "users"
  }
  protected $tables =>
  array(0) {
  }
  protected $databaseConnection =>
  class PHPUnit\DbUnit\Database\DefaultConnection#16 (2) {
    protected $connection =>
    class PDO#15 (0) {
    }
    protected $metaData =>
    class PHPUnit\DbUnit\Database\Metadata\Sqlite#17 (6) {
      protected $columns =>
      array(0) {
        ...
      }
      protected $keys =>
      array(0) {
        ...
      }
      protected $truncateCommand =>
      string(11) "DELETE FROM"
      protected $pdo =>
      class PDO#15 (0) {
        ...
      }
      protected $schema =>
      string(8) ":memory:"
      protected $schemaObjectQuoteChar =>
      string(1) """
    }
  }
}

此外,$queryTable 变量内的 $data 数组为 null。以下是 $queryTable 变量的 var_dump

class PHPUnit\DbUnit\DataSet\QueryTable#22 (6) {
  protected $query =>
  string(19) "SELECT * FROM users"
  protected $databaseConnection =>
  class PHPUnit\DbUnit\Database\DefaultConnection#20 (2) {
    protected $connection =>
    class PDO#19 (0) {
    }
    protected $metaData =>
    class PHPUnit\DbUnit\Database\Metadata\Sqlite#21 (6) {
      protected $columns =>
      array(0) {
        ...
      }
      protected $keys =>
      array(0) {
        ...
      }
      protected $truncateCommand =>
      string(11) "DELETE FROM"
      protected $pdo =>
      class PDO#19 (0) {
        ...
      }
      protected $schema =>
      string(8) ":memory:"
      protected $schemaObjectQuoteChar =>
      string(1) """
    }
  }
  protected $tableName =>
  string(5) "users"
  protected $tableMetaData =>
  NULL
  protected $data =>
  NULL
  private $other =>
  NULL
}

这里,$expectedTable 变量中的 $data 数组充满了数据集文件中创建的数据。

class PHPUnit\DbUnit\DataSet\DefaultTable#30 (3) {
  protected $tableMetaData =>
  class PHPUnit\DbUnit\DataSet\DefaultTableMetadata#34 (3) {
    protected $columns =>
    array(3) {
      [0] =>
      string(2) "id"
      [1] =>
      string(4) "name"
      [2] =>
      string(5) "email"
    }
    protected $primaryKeys =>
    array(0) {
    }
    protected $tableName =>
    string(5) "users"
  }
  protected $data =>
  array(4) {
    [0] =>
    array(3) {
      'id' =>
      string(1) "1"
      'name' =>
      string(3) "test1"
      'email' =>
      string(9) "test1@me.nl"
    }
    [1] =>
    array(3) {
      'id' =>
      string(1) "2"
      'name' =>
      string(3) "test2"
      'email' =>
      string(9) "test2@me.nl"
    }
    [2] =>
    array(3) {
      'id' =>
      string(1) "3"
      'name' =>
      string(6) "test3"
      'email' =>
      string(12) "test3@me.nl"
    }
    [3] =>
    array(3) {
      'id' =>
      string(1) "4"
      'name' =>
      string(4) "test4"
      'email' =>
      string(10) "test4@me.nl"
    }
  }
  private $other =>
  NULL
}

我也尝试在getConnection()方法中使用pdo连接对象执行2个查询来创建带有值的表:

protected function getConnection()
{
    $pdo = new PDO('sqlite::memory:');
    $pdo->exec("CREATE TABLE users (id PRIMARY KEY, name VARCHAR(50), email VARCHAR(50))");
    $pdo->exec("INSERT INTO users (id, name, email) VALUES (20, 'Bas', 'aa@me')");

    return $this->createDefaultDBConnection($pdo, ':memory:');
}

为什么我的连接中没有任何数据可用,如何将数据从数据集文件导入到这里以使测试通过?

此外,这种做法是否是一个好习惯?

2个回答

4
就我所见,在您的测试用例中,setUp() 覆盖了 \PHPUnit\DbUnit\TestCaseTrait 中包含负责 设置和拆卸 的逻辑的 setUp()
如果您需要在测试用例中有不同的setUp,可能更好的做法是像文档中 描述 的那样制作自己的基类,并从中扩展您的测试用例,并从子测试用例中调用 parent::setUp()更新:

在运行测试套件之前,必须创建数据库、表、序列、触发器和视图。

这是从 这里 获取的,那里还有几个有用的提示。
基本上,这意味着在运行测试之前,测试涉及到和依赖于数据库相关代码的所有表格、列、索引、约束和其他内容都应该就位。运行测试之前数据库中的任何内容都不重要,TestCase数据集中的所有表格都将被截断并填充来自该数据集的数据。
更新2:(免责声明:以下是我的个人偏好)
通常通过某种类型的网关实现对数据库的访问。它们是我实现的最后一件事情(至少在包范围内)。这使我有机会在开始处理这些网关时对需要存储在数据库中的数据有真正的了解。因此,当我开始编写某个网关的某个TestCase时,我只需使用管理工具(通常是一些GUI,如phpMyAdmin)创建我认为需要存在的表格(表格)和可能的列,以存储该网关处理的数据。然后编写测试并在其间运行,也许在更改表格结构以更好地适应时进行。
采用这种方法,数据库的所有结构都是手动创建的(而不是由测试代码创建),并且随着处理数据库的代码一起增长。我发现它有几个方便之处。
首先,这更简单,因为我不必在每个测试用例之前管理创建(或重新创建)结构。特别是如果我有几个与相同表一起工作的测试用例。
其次,我始终拥有适合测试通过的数据库结构。任何不适当的结构更改都将被捕获。此外,我始终可以生成用于启动真实生产数据库的sql导出指令,其中包含所有所需的表、列、索引、键等。
第三,有时需要(甚至必须)根据数据库特定问题查看数据库。因此,我始终可以打开当前的测试数据库并清楚地看到它由什么组成。
关于内存数据库的说明。 在这种情况下,结构需要在代码中创建。在这里可能会有两个明显的选项--为整个测试套件或特定测试用例(或它们的组)设置具有特定结构的数据库。对于我来说,基于上述原因,我会选择第一个选项。
最简单的实现方式是在引导文件中建立连接并创建结构。但我会投入一些时间,并添加一些动态性,例如:
<?xml version="1.0" encoding="UTF-8" ?>
<phpunit>
    <php>
        <var name="MAKE_IN_MEMORY_DB" value="yes" />
    </php>
</phpunit>

并且:

abstract class AbstractDbTestCase extends \PHPUnit\DbUnit\TestCase
{
    private $connection;

    protected function getConnection() {
        if($this->connection === NULL){
            $this->connection = $this->createConnection();
        }
        return $this->connection;
    }

    private function createConnection(){
        if($GLOBALS['MAKE_IN_MEMORY_DB'] == 'yes'){
            return $this->createInMemory();
        }
        else {
            return $this->createRealDbConnection();
        }
    }

    private function createInMemory(){
        // create connection and set up db;
    }

    private function createRealDbConnection(){
        // create connection using some data from phpunit.xml
    }
}

那会使测试与环境限制更加解耦 -- 只需要调整配置即可运行测试。实际上,我会做得更多,使用一个包含 SQL 语句的文件将其加载到 createInMemory() 中(需要一些额外的工作,但我认为这是值得的)。

1
很遗憾,那并没有起作用...我收到了错误信息 COMPOSITE[TRUNCATE]操作在查询时失败: DELETE FROM "users" using args: Array ( ) [SQLSTATE[HY000]: General error: 1 no such table: users] - Bas
谢谢更新!那么导入这些表的最佳实践是什么呢?在getConnection方法内,在一个继承自抽象数据库测试用例的setUp方法中吗? - Bas
我很感激你的帮助,但是当它不是内存数据库时,你难道不能只设置一个数据库吗?我有点困惑。 - Bas
嗯,是的,看起来我忘记了源头,走得太远了)) 对此感到抱歉。我又做了一个更新,希望对你的情况有意义。 - xmike

2

虽然我不想发表无意义的评论,但我认为这个对于解决问题很有帮助;

// phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
 <phpunit backupGlobals="false"
  <php>
    <server name="APP_ENV" value="testing"/>
    <server name="SESSION_DRIVER" value="array"/>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="DB_DATABASE" value=":memory:"/>
  </php>
 </phpunit>

1
那又怎么样?这怎么回答这个问题? - DarkSide

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接