持续集成,将实际测试数据输入数据库的最佳实践,使用 Propel ORM

Continuous Integration, best practice to input actual test data into database, using Propel ORM

我使用 Propel ORM 复制了一个 table 模式,以便进行持续集成,但是 Propel 只给我一个完全充实的模式,它没有给我测试数据(或基本必要的数据)完全没有)。

如何从版本受控的 live/test 数据库中获取数据 propel-gen Propel ORM 生态系统?

他们说 "best practice" 根本不存在 - 它是如此主观,以至于人们应该接受几种形式的 "good practice" 中的一种。我 认为 以下符合该标签的条件 - 最终它对我来说效果很好。我已经使用 PHPUnit 大约一年了,也许在我的项目中从头开始使用了六个月。

这是我在 PHPUnit bootstrap 阶段(在 phpunit.xml 中指定)所做的工作的概要:

  • 删除并创建 myproject_test 数据库
  • 在生成的 SQL
  • 的预迁移副本上调用 insert-sql Propel 命令
  • 调用migrate推进命令
  • 扫描我的测试文件夹以构建 classes 以设置测试,然后依次 运行 每个

手动插入 SQL 然后 运行 迁移的好处是迁移得到了真正彻底的测试。这特别方便,因为在开发中我有时会做一个 down,修改一个迁移 class,然后做一个 up 来重新 运行 它:因此让人放心知道它将 运行 按顺序排列。目前我打算永久保留我所有的移民历史;虽然它会给测试和新构建增加非常小的延迟,但升级部署不会受到影响。

由于我的构建依赖于旧的 SQL 文件,因此我避免使用 sql 生成命令;如果意外发布,修改后的 SQL 文件可以在版本控制中轻松恢复。

目前,我只是在localhost上使用myproject_test的数据库名称,这样测试在运行的任何地方都不会影响其他数据库。在构建服务器上,您可能需要使用不同的凭据进行连接:考虑在 switch() 语句中检测机器名称,并相应地选择连接详细信息。

为了给你测试数据,我一般倾向于建议你不要使用从你的实时系统导出数据。一方面,它通常太多了,而且您通常希望为每个测试创建数据片段,以便测试完全隔离。我认为这是一个好主意,原因有二:

  • 您可以并行化独立的测试。因此,当您的浏览器测试套件需要五个小时才能 运行 (!) 您可以设置更多构建服务器以更快地获得绿色构建。
  • 您可能希望在本地 运行 测试套件本身,或测试本身,或匹配特定字符串的一组测试,如果一个测试依赖于另一个测试,这可能不起作用.

这是我的构建器 classes 发挥作用的地方。我在我的 bootstrap.php 中使用它,并在包含测试 classes:

的每个文件夹上调用它
function runBuilders($buildFolder, $namespace)
{
    // I use ! to mark common builders that need to be run first.
    // Since this confuses autoloader, I load that manually.
    $commonBuilder = $buildFolder . '/!CommonBuild.php';
    if (file_exists($commonBuilder))
    {
        require_once $commonBuilder;
    }

    foreach(glob($buildFolder . '/*Build.php') as $class)
    {
        $matches = array();
        $found = preg_match('#/([!a-zA-Z]+)\.php#', $class, $matches);
        if ($found)
        {
            echo '.';

            // Don't use ! characters when creating the class
            $className = str_replace('!', '', $matches[1]);
            call_user_func($namespace . "\{$className}::build");
        }
    }
}

!CommonBuild.php中,我添加了不会被测试修改的只读数据,因此只有一个副本是安全的。

每个 PHPUnit 测试 class 我有一个构建 class:对于我拥有的每个 *Test.php 文件,我都会有一个对应的 *Build.php。在每个构建器中,一个 build 静态方法被调用,并且我手动 运行 一个方法用于每个需要构建的东西的测试。这是一个简单的:

public static function build()
{
    self::buildWriteVarToFieldSuccessfully();
    self::buildWriteVarToFieldUsingFailedMatch();
    self::buildWriteVarToFieldUsingFoundMatch();
    self::buildFailIfVariableIsAnArray();
}

在未来的某个时候,我可能会使用反射来自动 运行 这些,就像 PHPUnit 用于测试一样,但现在没问题。

现在,在我的 bootstrap 脚本中,我使用测试连接完全初始化了 Propel,因此可以使用普通的 Propel 语句。因此,我将只创建我需要的数据,如下所示:

protected static function buildWriteVarToFieldUsingFoundMatch()
{
    // Save an item in the holding table
    $employer = self::createEmployer();
    $job = new \Job\Model\JobHolding();
    $job->setReference('12345');
    $job->setLocationAlias('Rhubarb patch');
    $job->setEmployerId($employer->getPrimaryKey());
    $job->save();

    $process = self::createProcessingUsingRowMatching($employer);
    $process->createSource('VarToFieldTest_buildWriteVarToFieldUsingFoundMatch');
}

我有一个命名约定,在测试 class 中对 testWriteVarToFieldUsingFoundMatch 的测试在相应的构建 class 中得到一个名为 buildWriteVarToFieldUsingFoundMatch 的构建器。它并没有在代码中强制执行,但这种命名有助于轻松找到一个给定的另一个(我经常使用 IDE 的分屏功能同时编辑两者)。

因此,在上面的示例中,我只需要一个雇主记录、一个工作记录、一个过程记录和一个源记录来 运行 这个特定的测试(而不是整个实时导出)。源记录被赋予一个与测试名称相关的唯一名称,因此它只会在本次测试中使用(我发现我必须注意这里的复制和粘贴错误 - 使用测试中的错误数据!)。

创建此类测试数据非常容易,无论您拥有何种数据库:user.name 字段、address.line1 字段等通常都可以创建包含唯一标识符,因此当您在测试中修改此数据,您知道只有该测试会使用它,因此它与其他测试隔离。

为了简单起见,我选择 运行 bootstrap 中的所有构建器,而不管正在进行什么测试 运行。因为这只需要额外的 15 秒,所以在我的例子中,可能不值得做更复杂的事情。但是,如果您愿意,可以在每个 PHPUnit 测试中使用 setUp 方法做一些巧妙的事情,检测当前测试(如果可能)然后 运行 适当的构建 class.