woledzki/Phabric

语言: PHP

git: https://github.com/woledzki/Phabric

Behat的DB模型/夹具创建库
A DB mockup / fixture creation library for Behat
README.md (中文)

Phabric

Build Status

将Gherkin表转换为数据库插入/更新的工具。

它适用于BDD库Behat,可在以下网址找到:http://behat.org/

该项目的目的是允许用户“即时”定义灯具 使用小黄瓜桌子而不必经历痛苦的​​过程 维护用于驱动Behat测试套件的SQL文件。

介绍

当我在公司工作时采用Behat时,我们很快发现了这一点 编写清晰的测试,我们需要在场景中设置数据库的状态 而不是整体夹具文件。

夹具文件的问题:

  • 它们很难维护
  • 通过修改夹具中的数据很容易破坏现有的测试
  • 数据的语义在fixture文件中丢失而不是 在一个场景中明确说明。

我们解决的解决方案是加载一个只含有的初始夹具 基本的DB结构和定义Gherkin表中的所有数据 我们测试的场景。

进入Phabric ......

Phabric

Phabric允许用户标记数据以便插入到数据库中 场景。喜欢如此:

Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

为了使数据尽可能可读,Phabric支持以下内容:

列名转换 - 您可以将Gherkin中列的名称映射到a 数据库列名。 EG Desc> conf_description

列数据转换 - 您可以转换列中的数据 注册功能。 EG 08/10/2011 09:00> 2011-10-08 09:00:00

默认值 - 您可以为列分配默认值,这样您就没有 明确地将它们包含在小黄瓜中。

支持关系数据。 EG与许多与会者举办的活动

这些功能的目的是帮助用户设置场景 可读和可维护的方式。它应该促进行为驱动的发展 一旦初始创建者步骤已经设置,任何人都可以标记 系统中的实体(包括测试人员和BA!)。

预习

下面的文档概述了如何配置和使用Phabric。这里有一个 快速预览安装,配置和安装Phabric时可实现的最新信息 运行:

场景:

Scenario:
    Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

注意:该示例包含名称和数据转换。

步骤:

<?php 

/**
 * @Given /^The following events exist$/
 */
public function theFollowingEventsExist(TableNode $table) {

    $this->phabric->insertFromTable('event', $table);
}

数据创建后的数据库表:


    | name  | datetime            | venue                  | description      |
    | PHPNW | 2011-10-08 09:00:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 2012-02-27 09:00:00 | London Business Center | Quite good conf. |

注意:Gherkin列名称映射到数据库列名称和一些数据 (datetime)被转换。

对于那些热衷于做而不是阅读的人来说,有一些工作的例子 'examples'文件夹。有关设置的说明,请参阅以下部分 例子。

DOCS

安装

目前唯一支持安装Phabric的方法是通过git。

将github存储库克隆到本地计算机。

  • git clone git@github.com:benwaine/ Phabric.git

将目录更改为新克隆的存储库。

  • cd Phabric /

Phabric有许多依赖关系,可以通过初始化来满足这些依赖关系 以下子模块:

  • git submodule init lib / Vendor / mockery /
  • git submodule init lib / Vendor / Doctrine /
  • git submodule update --recursive

然后是Doctrines子模块

  • cd lib / Vendor / Doctrine /
  • git submodule init lib / vendor / doctrine-common /
  • git submodule update --recursive

设置Phabric

Phabric需要在你的behat的主要特征上下文文件中进行一些设置 测试文件夹。

Phabric需要一个数据源来保存Gherkin表。通常这是一个 关系型数据库。 Phabric附带一个Doctrine DBAL适配器。 这允许支持许多数据库“开箱即用”。最流行的数据库 支持包括MySQL,Oracle和MSSQL。

如果您希望支持Donctrine所做的非关系数据库或数据库 不支持你可以通过编写适配器和实现Phabrics来实现 'IDatasource'界面。

自动加载 使用Doctrine Project自动加载器加载类。

Doctrine和Phabric Classes需要在自动加载器中注册 特征上下文文件:

<?php 

require_once __DIR__ . '/PATH/TO/PHABRIC/lib/Vendor/Doctrine/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';

$phaLoader = new \Doctrine\Common\ClassLoader('Phabric', realpath(__DIR__ . '/../../../lib/'));
$phaLoader->register();

$docLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', __DIR__ . '/../../../lib/Vendor/Doctrine/lib');
$docLoader->register();

$docComLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', __DIR__ . '/../../../lib/Vendor/Doctrine/lib/vendor/doctrine-common/lib');
$docComLoader->register();

/**
 * Features context.
 */
class FeatureContext extends BehatContext {

public function __construct(array $parameters) {

}

// Rest of feature file.... 

需要创建Doctrine DBAL连接(数据库连接类) 注入到Phabric数据源对象中,此类管理与之交互 数据源。 应将数据库连接参数添加到behat.yml配置文件中。 此外,一些基本的元数据是关于我们希望映射的“实体”的输入。 实体是Gherkin到DB表映射,这将在下面进一步讨论 后面的部分。

default:
  context:
    class: 'FeatureContext'
    parameters:
      database:
        username: 'root'
        password: ''
        dbname:   'behat-demo'
        host:     '127.0.0.1'
        driver:   'pdo_mysql'
      Phabric:
        entities:
          event:
            tableName: 'event'
            primaryKey: 'id'
            nameCol: 'name'
          session:
            tableName: 'session'
            primaryKey: 'id'
            nameCol: 'session_code'
          attendee:
            tableName: 'attendee'
            primaryKey: 'id' 
            nameCol: 'name'
          vote:
            tableName: 'vote'
            primaryKey: 'id'
            nameCol: null

注意,Phabric部分需要一些基本的表元数据 behat.yml文件。 tableName,primaryKey和nameCol。

  • tableName:要映射的表的名称,例如'事件'
  • primaryKey:主键列的名称,例如'ID'
  • nameCol:这是一个用于标识phabric插入的数据的列。   它可以是数据库中的任何列,但应该是唯一的。例如'PHPNW'

创建DBAL Connections并将其设置为使用的数据库连接 Phabric:

<?php 

protected $phabric;

public function __construct(array $parameters) {

        $config = new \Doctrine\DBAL\Configuration();

        self::$db = \Doctrine\DBAL\DriverManager::getConnection(array(
                    'dbname' => $parameters['database']['dbname'],
                    'user' => $parameters['database']['username'],
                    'password' => $parameters['database']['password'],
                    'host' => $parameters['database']['host'],
                    'driver' => $parameters['database']['driver'],
                ));

    $datasource = new \Phabric\Datasource\Doctrine(self::$db, $parameters['Phabric']['entities']);

    $this->phabric = new Phabric\Phabric($datasource);

}

这应该是使用Phabric所需的所有设置。我们现在可以定义Phabric 实体,这些代表Gherkin数据表和数据之间的映射 数据库。

Phabric类

Phabric对象处理与所有“实体”的交互(Gherkin表> 使用Phabric时创建的db table mappings。 它接受数据源作为唯一的参数。应该创建它 FeatureContext类的构造函数并保存到成员变量(作为 在上面的例子中)。

Phabric实体

Phabric实体封装了Gherkin表和数据库之间的映射 表。

配置Phabric实体有两种方法:以编程方式和使用方式 配置文件。本文档将显示这两种方法。那些人 更喜欢使用较少设置的清洁功能文件应考虑使用 基于配置的方法。

编程方式:

<?php     

// Note: no second config parameter passed
$event = $this->phabric->createEntity('event');

$event->setTableName('event');

// @todo more entity config. @see The Docs bellow

并使用配置:

<?php     
// Note: The config array is pulled from the $parameters argument passed
// into the FeatureContext constructor method.
$this->phabric->createEntity('event', $parameters['Phabric']['entities']['event']);

一般原则

  • Phabric对象在FeatureContext构造函数中设置。
  • Phabric实体是通过Phabric类创建的,并在其中配置 FeatureContext构造函数。
  • 在步骤定义中检索实体,并用于插入和更新 场景中的Gherkin表中指定的数据。

示例域

出于以下示例的目的,我们将使用该数据库 桌子和小黄瓜在下面。

我们正在建模的系统描述了事件。

存在事件数据库表:

数据库表:


| id | ev_name | ev_desc           | ev_date             | ev_disp |
| 1  | PHPNW   | A hella cool gig! | 2011-10-08 09:00:00 | 1       |
| 2  | PHPUK   | A great event!    | 2012-02-26 09:00:00 | 0       |

我们决定使用Gherkin表来描述它如下:

Given the following event exists:

| Name  | Description       | Start Date      | 
| PHPNW | A hella cool gig! | 08/10/2011 9:00 | 
| PHPUK | A great event!    | 26/02/2011 9:00 |

注意:列'ev_disp'的默认值为1,除非 另有规定。

上表以更加商业友好的方式和摘要对数据进行建模 远离底层数据库实现。测试人员,BA和开发人员都可以 现在专注于在业务案例的上下文中建模数据 比数据库。

创建实体

设置Phabric对象后,您现在可以获得Phabric实体实例,如下所示:

<?php 

    $event = $this->phabric->createEntity('event', $config);

这将创建并返回名为“event”的实体。第二个论点 $ config是可选的。提供它将根据配置实例 配置提供。

注意:以下将提供$ config参数的说明 部分。

列名称转换

列名转换的目标是改变看起来丑陋的数据库 列名称为人类可读和商业友好名称。

在此示例中,我们要更改列名称,例如'ev_name'和 'ev_description'为更友好的'姓名'和'描述'。

首先创建一个实体:

<?php 

   $event = $this->phabric->createEntity('event', $config);

然后设置一些列名转换:

<?php

$event->setNameTransformations(array(
                            'Name' => 'ev_name',
                            'Description' => 'ev_description',
                            'Start Date' => 'ev_date',
                            'Displayed' => 'ev_disp'
                            ));

重要说明:首先应用列名转换。在引用时 后续方法/配置中的列使用数据库列名

默认情况下,任何没有特定映射的列都会转换为小写。 这可以通过在实体上设置defaultNameTransformation来配置

<?php

$event->setDefaultNameTransformation(function($name) {
    return str_replace(' ', '_', strtolower($name));
});

列数据转换

以同样的方式,最好将列名称表示为人类可读和 我们也应尽可能地商业友好,我们也应该在列中代表数据 以同样的方式。

在这个例子中,最好使用日期的英文表示 而不是MySQL日期时间(08/10/2011 9:00> 2011-10-08 09:00:00)。也 在售罄字段中,“是”和“否”可用于表示“0”和“1”。

这是通过使用Phabric对象注册闭包来实现的(所以每个Phabric 实例可以共享其中定义的功能)。关闭是 注册了一个名字。闭包名称和列的名称 然后,在表示该表的实体中注册已翻译。

通过提供名称和函数为Phabric对象注册一个闭包。 通常名称在CAPS中。

闭包接受来自列的数据并返回其翻译的表单。

<?php 

$this->phabric->addDataTransformation(
            'UKTOMYSQLDATE', function($date) {
                $date = \DateTime::createFromFormat('d/m/Y H:i', $date);
                return $date->format('Y-m-d H:i:s');
            }
    );

然后使用实体设置转换。

<?php 

    $event->setDataTransformations(array(
                                'ev_date' => 'UKTOMYSQLDATE'
                                ));

注意请注意在注册数据时使用实际数据库列名 转换闭包。

重要提示:使用总线注册封闭和注册 转换可以按任何顺序进行。但是,记住那辆公共汽车 注册必须在数据转换实际发生之前进行。

列默认值

默认值可用于减少Gherkin中的列数 表示数据库表数据。

在此示例中,数据库中的“ev_disp”列用于指示是否为 应该在应用程序的前端显示一个事件。我们默认 想将此设置为1(应显示事件)。我们总是可以覆盖 这包括我们的小黄瓜中的列。

使用数据库列名称和值的数组设置默认值:

<?php 

    $event->setDefaults(array(
                        'ev_disp' => 1
                        ));

要覆盖默认值,请确保设置名称转换(可选) 使用数据转换)并在Gherkin表中包含该列。

<?php

    $event->setNameTransformations(array(
                                'Name' => 'ev_name',
                                'Description' => 'ev_description',
                                'Start Date' => 'ev_date',
                                'Displayed' => 'ev_disp'
                                ));

    $this->phabric->addDataTransformation(
                'YESNOFLAG', function($ynFlag) {
                    switch($ynFlag) {
                    case 'YES':
                        return 1;
                    case 'NO':
                        return 0;
                    default:
                        throw new \Exception('Invalid Y/N Flag. Use: YES or NO);
                    } 
                },
                'UKTOMYSQLDATE', function($date) {
                    $date = \DateTime::createFromFormat('d/m/Y H:i', $date);
                    return $date->format('Y-m-d H:i:s');
                }
        );

    $event->setDataTransformations(array(
                                'ev_date' => UKTOMYSQLDATE
                                'ev_disp' => 'YESNOFLAG'
                                ));
| Name  | Description       | Start Date      | Displayed |
| PHPNW | A hella cool gig! | 08/10/2011 9:00 | YES       |
| PHPUK | A great event!    | 26/02/2011 9:00 | NO        |

插入数据

设置Phabric实体后,现在可以从小黄瓜转换数据 表进入数据库。

插入不相关的数据(基本插入)

从Behat功能文件:

Scenario:
    Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

并在相应的Behat步骤中:

<?php 

    /**
     * @Given /^The following events exist$/
     */
    public function theFollowingEventsExist(TableNode $table) {

        // With an entity previously configured just pass it's name and
        // the table node to the 'insertFromTable' method on the Phabric 
        //object.
        $this->phabric->insertFromTable('event', $table);
    }

关系数据

Phabric支持关系数据的输入。例如,链接多个 与会者参加活动。

内部Phabric会跟踪它所创建的数据库条目。它映射了 从数据库返回的最后一个插入ID到最左列的值。

可以通过使用'getNamedItemId()'方法来访问此id Phabric实体。可以通过注册a来替换Id的实体名称 标准数据转换。

注意:使用关系数据时,应确保最左边的列 始终如一。在这种情况下,“名称”是最左边的列和ID 映射到该值(EG-'PHPNW'=> 1)。

在以下示例中,与会者被要求投票选出他们最喜欢的会话。 投票数据库表如下所示:


| id | session_id | attendee_id | vote |
| 1  | 1          | 1           | 1    |

*注意:*以下示例仅显示关系数据功能。其他 要使示例工作的名称和数据需要转换。

从Behat功能文件:

Scenario: 
    Given the following sessions exist
    | Session Code | name                  | time  | description                               |
    | BDD          | BDD with behat        | 12:50 | Test driven behaviour development is cool |
    | CI           | Continuous Integration | 13:30 | Integrate this!                           |
    And the following attendees exist
    | name                  |
    | Jack The Lad          |
    | Simple Simon          |
    | Peter Pan             |
    And the following votes exist
    | Attendee     | Session | Vote | 
    | Jack The Lad | BDD     | UP   |
    | Simple Simon | BDD     | UP   |
    | Peter Pan    | BDD     | UP   |
    | Jack The Lad | CI      | UP   |
    | Simple Simon | CI      | UP   |
    | Peter Pan    | CI      | DOWN |

设置Phabric对象时,会注册数据转换以进行转换 具有ID的参加者姓名和会话名称:

<?php 

$this->phabric->addDataTransformation(
        'ATTENDEELOOKUP', function($attendeeName, $bus) {
            $ent = $bus->getEntity('attendee');

            $id = $ent->getNamedItemId($attendeeName);

            return $id;
        });

$this->phabric->addDataTransformation(
        'SESSIONLOOKUP', function($sessionName, $bus) {
            $ent = $bus->getEntity('session');

            $id = $ent->getNamedItemId($sessionName);

            return $id;
        });

名称和数据转换在投票实体中注册:

<?php 

$vote->setNameTransformations(array(
                            'Session' => 'session_id',
                            'Attendee' => 'attendee_id'));

$vote->setDataTransformations(array(
                            'session_id' => 'SESSIONLOOKUP',
                            'attendee_id' => 'ATTENDEELOOKUP'));

create()方法与前面的示例一样使用:

<?php 

    /**
     * @Given /^the following votes exist$/
     */
    public function theFollowingVotesExist(TableNode $table)
    {
        $this->phabric->insertFromTable('vote', $table);
    }

更新数据

更新数据与插入数据非常相似:

<?php 

    /**
     * @Given /^the following votes exist$/
     */
    public function theFollowingVotesExist(TableNode $table)
    {
        $this->phabric->updateFromTable('vote', $table);
    }

更新数据时需要注意一些问题。

  • 左栏是Phabric用于识别记录的列。这应该 在整个场景中保持不变。在这种情况下,引用了一个事件 内部由它的名字(PHPNW或PHPUK)。
  • 如果先前未插入数据,则无法更新。 Atempting 这会导致抛出异常。
  • 支持部分更新,但请记住引用列必须是 当下。

全表:

    | Name  | Date             | Venue                   | Desc             |
    | PHPNW | 08/10/2011 10:00 | Ramada Hotel MANCHESTER | An awesome conf! |
    | PHPUK | 27/02/2012 10:00 | London Business Center  | Quite good conf. |

一个正确的部分表:

    | Name  | Date             | Venue                   |
    | PHPNW | 08/10/2011 10:00 | Ramada Hotel MANCHESTER |
    | PHPUK | 27/02/2012 10:00 | London Business Center  |

部分表格不正确:

    |Date              | Venue                   | Desc             |
    | 08/10/2011 10:00 | Ramada Hotel MANCHESTER | An awesome conf! |
    | 27/02/2012 10:00 | London Business Center  | Quite good conf. |

配置方法

前面的示例演示了如何配置Phabric实体 编程。虽然这是有效的,但它也非常冗长。 Phabric 配置可以存储在测试套件'behat.yml'文件中并用于 简洁地创建和配置实体。

使用Phabric配置的'behat.yml'配置文件的示例:

default:
  context:
    class: 'FeatureContext'
    parameters:
      database:
        username: 'root'
        password: ''
        dbname:   'behat-demo'
        host:     '127.0.0.1'
        driver:   'pdo_mysql'
      baseurl: 'http://behat-demo.dev/'
      registry:
        baseurl: 'http://behat-demo.dev/'
        eventsResourceUri: events
        eventsResourceMethod: GET
      Phabric:
        entities:
          event:
            tableName: 'event'
            entityName: 'Event'
            nameTransformations:
              Date: datetime
              Desc: description
            dataTransformations:
              datetime: UKTOMYSQLDATE
          session:
            tableName: 'session'
            entityName: 'Session'
            nameTransformations:
              Session Code: session_code
          attendee:
            tableName: 'attendee'
            entityName: 'Attendee'
          vote:
            tableName: 'vote'
            entityName: 'Vote'
            nameTransformations:
              Attendee: attendee_id
              Session: session_id
            dataTransformations:
              attendee_id: ATTENDEELOOKUP
              session_id: SESSIONLOOKUP
              vote: UPDOWNTOINT

通过将Phabric配置放在FeatureContext> Parameters部分下,它就是 在Behat FeatureContext构造函数的$ parameters数组中可用。 这是Phabric总线和实体的所有配置发生的地方。

正如您可以看到名称和数据转换,实体映射到的数据库表的名称 和默认值可以包含在配置中。

在FeatureContext类的构造函数中:

<?php 

$event    = $this->phabric->createEntity('event', $parameters['Phabric']['entities']['event']);
$attendee = $this->phabric->createEntity('attendee', $parameters['Phabric']['entities']['attendee']);
$session  = $this->phabric->createEntity('session', $parameters['Phabric']['entities']['session']);
$vote     = $this->phabric->createEntity('vote', $parameters['Phabric']['entities']['vote']);

或者使用更方便的'createEntitiesFromConfig'方法来注册所有 你的实体一下子:

    $this->phabric->createEntitiesFromConfig($parameters['Phabric']['entities']);

因子方法返回Phabric实体实例,但它们也可以 使用总线在步骤方法中检索:

<?php 

    $eventPh = $this->phabric->getEntity('event');

例子

运行示例需要一些基本设置。

  • 在'behat.yml'中设置与数据库配置兼容的数据库

本文使用googletrans自动翻译,仅供参考, 原文来自github.com

en_README.md

Phabric

Build Status

A tool that translates Gherkin Tables into database inserts / updates.

It's for use with the BDD library Behat which can be found at: http://behat.org/

The aim of this project is to allow the user to define fixtures 'on the fly'
using Gherkin tables without having to undergo the painful process of
maintaining an SQL file used to drive Behat test suites.

Introduction

When adopting Behat at the company I work for we quickly found that in order to
write clear tests we needed to set the state of the database in scenarios rather
than in monolithic fixture files.

Problems with fixture files:

  • They are difficult to maintain
  • It's easy to wreck an existing test by modifying it's data in the fixture
  • The semantics of the data are lost in the fixture file rather than being
    explicitly stated in a scenario.

The solution we settled on was to load an initial fixture containing just the
basic DB structure and to define all the data in Gherkin tables within the
scenarios of our test.

Enter Phabric...

Phabric

Phabric allows the user to mark up data for insertion into the database in a
scenario. Like So:

Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

To make the data as readable as possible Phabric supports the following:

Column Name Transformations - You can map the name of a column in Gherkin to a
database column name. EG Desc > conf_description

Column Data Transformations - You can translate the data in the column by
registering functions. EG 08/10/2011 09:00 > 2011-10-08 09:00:00

Default Values - You can assign default values to columns so you do not have
to explicitly include them in the gherkin.

Relational data is supported. EG An Event with many Attendees

The aim of these features is to assist the user in setting up a scenario in a
readable and maintainable way. It should facilitate behaviour driven development
as once the initial creator steps have been setup anyone will be able to mark up
entities in your system (testers and BAs included!).

Preview

The documentation below outlines how to configure and use Phabric. Here is a
quick preview of whats achievable when Phabric is installed, configured and
running:

The scenario:

Scenario:
    Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

Note: The example contains name and data transformations.

The step:

<?php 

/**
 * @Given /^The following events exist$/
 */
public function theFollowingEventsExist(TableNode $table) {

    $this->phabric->insertFromTable('event', $table);
}

The database table after data creation:


    | name  | datetime            | venue                  | description      |
    | PHPNW | 2011-10-08 09:00:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 2012-02-27 09:00:00 | London Business Center | Quite good conf. |

Note: Gherkin column names are mapped to database column names and some data
(datetime) is transformed.

For those keen on doing rather than reading there are working examples in the
'examples' folder. See section below for instructions on setting up the
examples.

DOCS

Install

Currently the only supported method of installing Phabric is via git.

Clone the git hub repository onto your local machine.

Change directory into the newley cloned repository.

  • cd Phabric/

Phabric has a number of dependencies these can be met by initializing the
following submodules:

  • git submodule init lib/Vendor/mockery/
  • git submodule init lib/Vendor/Doctrine/
  • git submodule update --recursive

Then Doctrines submodules

  • cd lib/Vendor/Doctrine/

  • git submodule init lib/vendor/doctrine-common/

  • git submodule update --recursive

Setting Up Phabric

Phabric requires some setting up in the main feature context file of your behat
tests folder.

Phabric requires a datasource to persist Gherkin tables to. Usually this is a
relational database. Phabric ships with a Doctrine DBAL adapter.
This allows support for many databases 'out of the box'. Most popular databases
are supported including MySQL, Oracle and MSSQL.

If you wish to support non relational databases or databases that Donctrine does
not support you can do so by writting an adapter and implementing Phabrics
'IDatasource' interface.

Autoloading
Classes are loaded using the Doctrine Project autoloader.

Doctrine and Phabric Classes need to be registered with the auto loader in the
Feature Context File:

<?php 

require_once __DIR__ . '/PATH/TO/PHABRIC/lib/Vendor/Doctrine/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';

$phaLoader = new \Doctrine\Common\ClassLoader('Phabric', realpath(__DIR__ . '/../../../lib/'));
$phaLoader->register();

$docLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', __DIR__ . '/../../../lib/Vendor/Doctrine/lib');
$docLoader->register();

$docComLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', __DIR__ . '/../../../lib/Vendor/Doctrine/lib/vendor/doctrine-common/lib');
$docComLoader->register();

/**
 * Features context.
 */
class FeatureContext extends BehatContext {

public function __construct(array $parameters) {

}

// Rest of feature file.... 

A Doctrine DBAL connection (database connection class) needs to be created and
injected into a Phabric datasource object, this class manages interactions with
the datasource.
Database connection parameters should be added to your behat.yml config file.
Also some basic meta data is input about the 'entities' we wish to map.
An entity is a Gherkin to DB table mapping, this is discussed further in
later sections.

default:
  context:
    class: 'FeatureContext'
    parameters:
      database:
        username: 'root'
        password: ''
        dbname:   'behat-demo'
        host:     '127.0.0.1'
        driver:   'pdo_mysql'
      Phabric:
        entities:
          event:
            tableName: 'event'
            primaryKey: 'id'
            nameCol: 'name'
          session:
            tableName: 'session'
            primaryKey: 'id'
            nameCol: 'session_code'
          attendee:
            tableName: 'attendee'
            primaryKey: 'id' 
            nameCol: 'name'
          vote:
            tableName: 'vote'
            primaryKey: 'id'
            nameCol: null

NB Note that some basic table meta data is required in the Phabric section
of the behat.yml file. tableName, primaryKey and nameCol.

  • tableName: The name of the table to be mapped e.g. 'event'
  • primaryKey: the name of the primary key column e.g. 'id'
  • nameCol: this is a column used to identify a piece of data inserted by phabric.
    It can be any column in the database but should be unique. eg 'PHPNW'

Creating the DBAL Connections and setting it as the database connection used by
Phabric:

<?php 

protected $phabric;

public function __construct(array $parameters) {

        $config = new \Doctrine\DBAL\Configuration();

        self::$db = \Doctrine\DBAL\DriverManager::getConnection(array(
                    'dbname' => $parameters['database']['dbname'],
                    'user' => $parameters['database']['username'],
                    'password' => $parameters['database']['password'],
                    'host' => $parameters['database']['host'],
                    'driver' => $parameters['database']['driver'],
                ));

    $datasource = new \Phabric\Datasource\Doctrine(self::$db, $parameters['Phabric']['entities']);

    $this->phabric = new Phabric\Phabric($datasource);

}

This should be all the setup required to use Phabric. We can now define Phabric
entities, these represent the mapping between Gherkin tables of data and data in
our database.

The Phabric Class

The Phabric object handles interaction with all the 'entities' (Gherkin table >
db table mappings) created when using Phabric.
It accepts a datasource as it's only argument. It should be created in
the constructor of the FeatureContext class and saved to a member variable (as
in the example above).

Phabric Entities

A Phabric entity encapsulates the mapping between a Gherkin table and a database
table.

There are two ways to configure a Phabric entity: Programmatically and by using
a Configuration file. This documentation will show both methods. Those who
prefer cleaner feature files with less set up should consider using the
configuration based approach.

Programmatically:

<?php     

// Note: no second config parameter passed
$event = $this->phabric->createEntity('event');

$event->setTableName('event');

// @todo more entity config. @see The Docs bellow

And Using configuration:

<?php     
// Note: The config array is pulled from the $parameters argument passed
// into the FeatureContext constructor method.
$this->phabric->createEntity('event', $parameters['Phabric']['entities']['event']);

General Principles

  • The Phabric object is set up in the FeatureContext constructor.
  • Phabric entities are created via the Phabric class and configured in the
    FeatureContext constructor.
  • Entities are retrieved in step definitions and are used to insert and update
    data specified in Gherkin tables in the scenario.

Example Domain

For the purposes of the following examples we will use the database
tables and Gherkin below.

The system we are modeling describes events.

An event database table exists:

Database Table:


| id | ev_name | ev_desc           | ev_date             | ev_disp |
| 1  | PHPNW   | A hella cool gig! | 2011-10-08 09:00:00 | 1       |
| 2  | PHPUK   | A great event!    | 2012-02-26 09:00:00 | 0       |

We decide we would like to describe it using a Gherkin table as follows:

Given the following event exists:

| Name  | Description       | Start Date      | 
| PHPNW | A hella cool gig! | 08/10/2011 9:00 | 
| PHPUK | A great event!    | 26/02/2011 9:00 |

Note: The column 'ev_disp' will have a default value of 1 unless
otherwise specified.

The table above models the data in a more business friendly way and abstracts
away the underlying database implementation. Testers, BA's and developers can
now concentrate on modeling the data in the context of the business case rather
than the database.

Creating an Entity

With the Phabric object set up you can now obtain Phabric entity instances like so:

<?php 

    $event = $this->phabric->createEntity('event', $config);

This creates and returns a entity with the name 'event'. The second argument
$config is optional. Supplying it will configure the instance according to the
configuration provided.

Note: An Explanation of $config parameters will be provided in the following
sections.

Column Name Transformations

The goal of column name transformations is to change often ugly looking database
column names to human readable and business friendly names.

In this example we want to change column names like 'ev_name' and
'ev_description' to the more friendly 'Name' and 'Description'.

First create an entity:

<?php 

   $event = $this->phabric->createEntity('event', $config);

Then set some column name transformations:

<?php

$event->setNameTransformations(array(
                            'Name' => 'ev_name',
                            'Description' => 'ev_description',
                            'Start Date' => 'ev_date',
                            'Displayed' => 'ev_disp'
                            ));

Important: Column Name transformations get applied first. When referencing
columns in subsequent methods / configs use the database column name

By default, any columns without a specific mapping get transformed to lowercase.
This can be configured by setting the defaultNameTransformation on an entity

<?php

$event->setDefaultNameTransformation(function($name) {
    return str_replace(' ', '_', strtolower($name));
});

Column Data Transformations

In the same way it is preferable to represent column names as human readable and
business friendly as possible we should also represent the data in the column in
the same manner.

In this example it is preferable to use and English representation of the date
rather than a MySQL date time (08/10/2011 9:00 > 2011-10-08 09:00:00). Also
in the Sold Out field 'YES' and 'NO' can be used to represent '0' and '1'.

This is achieved by registering closures with the Phabric object (so every Phabric
instance can share the functionality defined in them). The closures are
registered against a name. The closure name and the name of the column to be
translated is then registered with the entity representing the table.

Register a closure with the Phabric object by supplying a name and a function.
Conventionally names are in CAPS.

The closure accepts the data from a column and returns its translated form.

<?php 

$this->phabric->addDataTransformation(
            'UKTOMYSQLDATE', function($date) {
                $date = \DateTime::createFromFormat('d/m/Y H:i', $date);
                return $date->format('Y-m-d H:i:s');
            }
    );

Then set the transformation(s) with the entity.

<?php 

    $event->setDataTransformations(array(
                                'ev_date' => 'UKTOMYSQLDATE'
                                ));

Note Notice the use of the real database column name when registering data
transformations closures.

Important: Registration of closures with the bus and registering
transformations can be carried out in any order. However, remember that bus
registration must occur before data transformation actually occurs.

Column Default Values

Default values can be useful to reduce the number of columns in the Gherkin
representation of the database table data.

In this example the 'ev_disp' column in the database is used to indicate if a
an event should be displayed on the fron end of the application. By default we
would like to set this to 1 (events should be displayed). We can always override
this by including the column in our Gherkin.

Defaults are set using an array of database column names and values:

<?php 

    $event->setDefaults(array(
                        'ev_disp' => 1
                        ));

To override the default ensure a name transformation is set up (optionally
with a data transformation) and include the column in the Gherkin table.

<?php

    $event->setNameTransformations(array(
                                'Name' => 'ev_name',
                                'Description' => 'ev_description',
                                'Start Date' => 'ev_date',
                                'Displayed' => 'ev_disp'
                                ));

    $this->phabric->addDataTransformation(
                'YESNOFLAG', function($ynFlag) {
                    switch($ynFlag) {
                    case 'YES':
                        return 1;
                    case 'NO':
                        return 0;
                    default:
                        throw new \Exception('Invalid Y/N Flag. Use: YES or NO);
                    } 
                },
                'UKTOMYSQLDATE', function($date) {
                    $date = \DateTime::createFromFormat('d/m/Y H:i', $date);
                    return $date->format('Y-m-d H:i:s');
                }
        );

    $event->setDataTransformations(array(
                                'ev_date' => UKTOMYSQLDATE
                                'ev_disp' => 'YESNOFLAG'
                                ));
| Name  | Description       | Start Date      | Displayed |
| PHPNW | A hella cool gig! | 08/10/2011 9:00 | YES       |
| PHPUK | A great event!    | 26/02/2011 9:00 | NO        |

Inserting Data

With a Phabric entity set up it's now possible to translate data from a Gherkin
table into the database.

Inserting Unrelated Data (Basic Insert)

From a Behat feature file:

Scenario:
    Given The following events exist
    | Name  | Date             | Venue                  | Desc             |
    | PHPNW | 08/10/2011 09:00 | Ramada Hotel           | An awesome conf! |
    | PHPUK | 27/02/2012 09:00 | London Business Center | Quite good conf. |

And in the corresponding Behat step:

<?php 

    /**
     * @Given /^The following events exist$/
     */
    public function theFollowingEventsExist(TableNode $table) {

        // With an entity previously configured just pass it's name and
        // the table node to the 'insertFromTable' method on the Phabric 
        //object.
        $this->phabric->insertFromTable('event', $table);
    }

Relational Data

Phabric supports the entry of relational data. For example linking multiple
attendees to an event.

Internally Phabric keeps track of the database entries it makes. It maps the
last inserted ID returned from the database to the value of the left most column.

It's possible to access this id by using the 'getNamedItemId()' method on the
Phabric entity. Id's can be substituted for entity names by registering a
standard data transformation.

Note: When using relational data you should ensure the left most column is
consistently the same. In this instance 'Name' is the left most column and ID's
are mapped against this value (EG -'PHPNW' => 1).

In the following example attendees are asked to vote for their favorite session.
The vote database table looks like this:


| id | session_id | attendee_id | vote |
| 1  | 1          | 1           | 1    |

*Note: * The following example just shows relational data functionality. Other
transformations are required on names and data for the example to work.

From a Behat feature file:

Scenario: 
    Given the following sessions exist
    | Session Code | name                  | time  | description                               |
    | BDD          | BDD with behat        | 12:50 | Test driven behaviour development is cool |
    | CI           | Continuous Integration | 13:30 | Integrate this!                           |
    And the following attendees exist
    | name                  |
    | Jack The Lad          |
    | Simple Simon          |
    | Peter Pan             |
    And the following votes exist
    | Attendee     | Session | Vote | 
    | Jack The Lad | BDD     | UP   |
    | Simple Simon | BDD     | UP   |
    | Peter Pan    | BDD     | UP   |
    | Jack The Lad | CI      | UP   |
    | Simple Simon | CI      | UP   |
    | Peter Pan    | CI      | DOWN |

When setting up the Phabric object data transformations are registered for translating
Attendee names and session names with there ID's:

<?php 

$this->phabric->addDataTransformation(
        'ATTENDEELOOKUP', function($attendeeName, $bus) {
            $ent = $bus->getEntity('attendee');

            $id = $ent->getNamedItemId($attendeeName);

            return $id;
        });

$this->phabric->addDataTransformation(
        'SESSIONLOOKUP', function($sessionName, $bus) {
            $ent = $bus->getEntity('session');

            $id = $ent->getNamedItemId($sessionName);

            return $id;
        });

And the name and data transformation are registered with the vote entity:

<?php 

$vote->setNameTransformations(array(
                            'Session' => 'session_id',
                            'Attendee' => 'attendee_id'));

$vote->setDataTransformations(array(
                            'session_id' => 'SESSIONLOOKUP',
                            'attendee_id' => 'ATTENDEELOOKUP'));

The create() method is used as in the previous example:

<?php 

    /**
     * @Given /^the following votes exist$/
     */
    public function theFollowingVotesExist(TableNode $table)
    {
        $this->phabric->insertFromTable('vote', $table);
    }

Updating Data

Updating data is very similar to inserting data:

<?php 

    /**
     * @Given /^the following votes exist$/
     */
    public function theFollowingVotesExist(TableNode $table)
    {
        $this->phabric->updateFromTable('vote', $table);
    }

There are some gotcha's to watch out for when updating data.

  • The left hand column is the column Phabric uses to identify records. It should
    remain the same throughout your scenario. In this case an event is referenced
    internally by it's name (PHPNW or PHPUK).

  • If data hasn't previously been inserted then it can't be updated. Atempting
    this causes an exception to be thrown.

  • Partial updates are supported but remember the referencing column must be
    present.

A full table:

    | Name  | Date             | Venue                   | Desc             |
    | PHPNW | 08/10/2011 10:00 | Ramada Hotel MANCHESTER | An awesome conf! |
    | PHPUK | 27/02/2012 10:00 | London Business Center  | Quite good conf. |

A correct partial table:

    | Name  | Date             | Venue                   |
    | PHPNW | 08/10/2011 10:00 | Ramada Hotel MANCHESTER |
    | PHPUK | 27/02/2012 10:00 | London Business Center  |

An incorrect partial table:

    |Date              | Venue                   | Desc             |
    | 08/10/2011 10:00 | Ramada Hotel MANCHESTER | An awesome conf! |
    | 27/02/2012 10:00 | London Business Center  | Quite good conf. |

Configuration Approach

The previous examples have shown how to configure a Phabric entity
programmatically. While this is effective it's also very verbose. Phabric
configuration can be stored in a test suites 'behat.yml' file and used to
succinctly create and configure entities.

An example of a 'behat.yml' configuration file with Phabric config in:

default:
  context:
    class: 'FeatureContext'
    parameters:
      database:
        username: 'root'
        password: ''
        dbname:   'behat-demo'
        host:     '127.0.0.1'
        driver:   'pdo_mysql'
      baseurl: 'http://behat-demo.dev/'
      registry:
        baseurl: 'http://behat-demo.dev/'
        eventsResourceUri: events
        eventsResourceMethod: GET
      Phabric:
        entities:
          event:
            tableName: 'event'
            entityName: 'Event'
            nameTransformations:
              Date: datetime
              Desc: description
            dataTransformations:
              datetime: UKTOMYSQLDATE
          session:
            tableName: 'session'
            entityName: 'Session'
            nameTransformations:
              Session Code: session_code
          attendee:
            tableName: 'attendee'
            entityName: 'Attendee'
          vote:
            tableName: 'vote'
            entityName: 'Vote'
            nameTransformations:
              Attendee: attendee_id
              Session: session_id
            dataTransformations:
              attendee_id: ATTENDEELOOKUP
              session_id: SESSIONLOOKUP
              vote: UPDOWNTOINT

By putting Phabric config under the FeatureContext>Parameters section it is
available in the $parameters array of the Behat FeatureContext constructor.
This is where all the configuration of the Phabric bus and entities occurs.

As you can see name and data transformations, name of database table an entity maps to
and default values can be included in config.

In the constructor of the FeatureContext class:

<?php 

$event    = $this->phabric->createEntity('event', $parameters['Phabric']['entities']['event']);
$attendee = $this->phabric->createEntity('attendee', $parameters['Phabric']['entities']['attendee']);
$session  = $this->phabric->createEntity('session', $parameters['Phabric']['entities']['session']);
$vote     = $this->phabric->createEntity('vote', $parameters['Phabric']['entities']['vote']);

Or use the more convenient 'createEntitiesFromConfig' method to register all
your entities at once:

    $this->phabric->createEntitiesFromConfig($parameters['Phabric']['entities']);

The factor methods return the Phabric entity instances but they can also be
retrieved in step methods by using the bus:

<?php 

    $eventPh = $this->phabric->getEntity('event');

Examples

Some basic set up is required to run the examples.

  • Set up a database compatible with the database config in the 'behat.yml'