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

Mocking data in a in-memory SQLITE database in PHPUnit

我目前正在学习如何在 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。这里有一个 var_dump$queryTable 变量。

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 个查询,以创建其中包含值的 table:

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:');
}

我的连接中没有任何可用数据是怎么来的,我怎样才能将数据集文件中的数据导入到这里来使测试通过?

另外,这样做是不是一个好习惯?

据我所见,setUp() 在你的 TestCase 中覆盖了 \PHPUnit\DbUnit\TestCaseTraitsetUp(),其中包含负责 setting up and tearing down.

的逻辑

如果您需要在 TestCases 中使用不同的 setUp 可能更好的做法是将您自己的基础 class 作为文档中的 described 并从中扩展您的 TestCase,并且从子 TestCase

调用 parent::setUp()

更新:

The Database, tables, sequences, triggers and views have to be created before you run the test suite.

它取自 here,那里还有更多有用的提示。

基本上这意味着所有 tables、列、索引、约束和其他与测试中的数据库相关的代码处理和依赖的东西,应该在 运行ning 之前到位测试。 运行ning 之前该数据库中的任何内容都无关紧要,TestCase 数据集中的所有 tables 都将被 t运行 分类并填充来自该数据集的数据。

更新 2: (免责声明:以下是我个人的喜好)

通常对数据库的访问是通过某种网关实现的。它们是我最后实现的(至少在包范围内)。当我开始做这些网关时,这让我有机会真正了解需要存储在数据库中的数据。因此,当开始在某个网关上编写一些 TestCase 时,我只是使用管理工具(通常是一些 GUI,如 phpMyAdmin)并创建 table (tables) 以及我认为需要显示的列存储网关处理的数据。然后编写测试和 运行 宁他们可能在改变 table 结构之间如何更好地适应。

通过这种方法,db 的所有结构都是手动创建的(而不是通过测试代码),并且它与在 db 上运行的代码一起增长。我发现它很方便有几个原因。

首先,这更简单,因为我不必在每个 TestCase 之前管理创建(或重新创建)结构。特别是如果我有几个使用相同 tables.

的测试用例

其次,我总是有 suitable 的数据库结构,以便通过测试。结构中的任何 unsuitable 变化都将被捕获。此外,我总是可以为这个适当的结构生成一个 sql 导出指令,以启动具有所有需要的 tables、列、索引、键等的现实生活数据库。

第三,有时需要(甚至必须)查看数据库,只考虑特定于数据库的问题。所以我总是可以打开当前的测试数据库并清楚地看到它是由什么组成的。

关于内存数据库的注意事项。 在这种情况下,需要在代码中创建结构。这里可能有两个明显的选择——为整个测试套件或特定测试用例(或其中的一组)设置具有特定结构的数据库。至于我,基于上述原因,我会做第一个。

最简单的实现方式是在 bootrstrap 文件中建立连接和创建结构。但我会投入一些时间并像这样添加一点动态:

<?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() 中(需要一些额外的工作,但我认为这是值得的)。

对 necro-post 没有帮助,但我发现这很有用;

// 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>