【问题标题】:Mocking data in a in-memory SQLITE database in PHPUnit在 PHPUnit 中的内存 SQLITE 数据库中模拟数据
【发布时间】:2017-06-20 08:04:22
【问题描述】:

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

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

/**
 * 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();
}

我希望我现在可以将该 PDO 连接与来自 getDataSet() 方法中的数据集文件的数据一起使用。

对于我自己的尝试,我尝试将它们与以下代码进行比较:

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

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

我的连接中没有任何可用数据是怎么来的?如何将数据集文件中的数据导入此处以使测试通过?

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

【问题讨论】:

    标签: php sqlite phpunit dbunit


    【解决方案1】:

    据我所知,setUp() 在您的 TestCase 中覆盖了 \PHPUnit\DbUnit\TestCaseTraitsetUp(),其中包含负责 setting up and tearing down 的逻辑。

    如果您需要在 TestCase 中使用不同的 setUp 可能更好的办法是在文档中将自己的基类设置为 described 并从中扩展您的 TestCase,并从子 TestCase 调用 parent::setUp()

    更新:

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

    取自here,那里有一些更有用的提示。

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

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

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

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

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

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

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

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

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

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

    【讨论】:

    • 不幸的是这不起作用...我收到错误COMPOSITE[TRUNCATE] operation failed on query: DELETE FROM "users" using args: Array ( ) [SQLSTATE[HY000]: General error: 1 no such table: users]
    • 感谢您的更新!那么导入这些表的最佳实践是什么?在getConnection 方法中,在一个从抽象数据库测试用例扩展而来的setUp 方法中?
    • 感谢您的帮助,但是当它不是内存数据库时,您不能只设置这样的数据库吗?我有点困惑。
    • 嗯,是的,好像我忘记了起源并且移得太远了))对此感到抱歉。我做了另一个更新,希望这对你的情况有意义。
    【解决方案2】:

    不是要发布 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>
    

    【讨论】:

    • 那又怎样?这如何回答这个问题?
    猜你喜欢
    • 1970-01-01
    • 2013-02-23
    • 2011-10-24
    • 1970-01-01
    • 2018-11-15
    • 2011-01-26
    • 2011-07-17
    • 2010-12-03
    • 2012-07-30
    相关资源
    最近更新 更多