Spec BDD PHPUnit Flow: Symfony2 Behat aftermath

What to do right after a Spec-type BDD feature was written in behat and it is on red failing state?

It is certainly time to connect it with the Spec-type BDD counterpart on PHPUnit so that we can start dumping our specification on a phpunit class.

I will give you a tip on how to get a quick start-up on writing the your first PHPUnit class by writing a first design class. Is it really going back again to write the design first? No. So don’t worry.

But we will write firstly the design in a BDD oriented way according to a set of two specifications like this:

<?php
class Example {
    public function applyPenaltyWhenMissingPapers() {
        return true;
    }
 
    public function applyPenaltyWhenDamageIsPresent() {
        return true;
    }
}

This is not the final class that I am aiming at designing. Rather this is a step towards getting quickly up to speed with the specification into php code.

So I run this command to generate my first phpunit class:

phpunit --skeleton-test Example Calculator/Example.php

Were it not for the “test” word the command below would have generated the phpunit class that I wanted:

<?php
require_once '/home/cordoval/sites-2/FormModelProjectBundle/vendor/bundles/Cordova/Bundle/FormModelBundle/Calculator/Example.php';
/**
 * Test class for Example.
 * Generated by PHPUnit on 2011-07-27 at 12:59:50.
 */
class ExampleTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var Example
     */
    protected $object;
 
    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $this->object = new Example;
    }
 
    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
    }
 
    /**
     * @todo Implement testApplyPenaltyWhenMissingPapers().
     */
    public function testApplyPenaltyWhenMissingPapers()
    {
        // Remove the following lines when you implement this test.
        $this->markTestIncomplete(
          'This test has not been implemented yet.'
        );
    }
 
    /**
     * @todo Implement testApplyPenaltyWhenDamageIsPresent().
     */
    public function testApplyPenaltyWhenDamageIsPresent()
    {
        // Remove the following lines when you implement this test.
        $this->markTestIncomplete(
          'This test has not been implemented yet.'
        );
    }
}
?>

Then one can run a find replace using a regexp (If you know a good one to replace all please let me know) and change the word test for should:

<?php
 
require_once '/home/cordoval/sites-2/FormModelProjectBundle/vendor/bundles/Cordova/Bundle/FormModelBundle/Calculator/Example.php';
 
/**
 * Test class for Example.
 * Generated by PHPUnit on 2011-07-27 at 12:59:50.
 */
class ExampleTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var Example
     */
    protected $object;
 
    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $this->object = new Example;
    }
 
    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
    }
 
    /**
     * @test
     * @todo ImplementshouldApplyPenaltyWhenMissingPapers().
     */
    public function shouldApplyPenaltyWhenMissingPapers()
    {
        // Remove the following lines when you implement this spec.
        $this->markTestIncomplete(
          'This spec has not been implemented yet.'
        );
    }
 
    /**
     * @test
     * @todo ImplementshouldApplyPenaltyWhenDamageIsPresent().
     */
    public function shouldApplyPenaltyWhenDamageIsPresent()
    {
        // Remove the following lines when you implement this spec.
        $this->markTestIncomplete(
          'This spec has not been implemented yet.'
        );
    }
}

Note that in addition to our changes we have also added the @test tag to identify methods as tests since we are not prefixing the methods with “test” word anymore.

And now we are ready to implement the more inner parts of the specification. We also have our starting point of the Example class in which we should get rid of the initial methods and try to define them according to the requirements pass/fail of the new phpunit specification class.

$ phpunit Calculator/ExampleTest.php 
PHPUnit 3.5.14 by Sebastian Bergmann.
 
II
 
Time: 0 seconds, Memory: 5.75Mb
 
OK, but incomplete or skipped tests!
Tests: 2, Assertions: 0, Incomplete: 2.

Notice the I is for incomplete. And our design process continues. Until we get:

PHPUnit 3.5.14 by Sebastian Bergmann.
 
..
 
Time: 0 seconds, Memory: 5.75Mb
 
OK (2 tests, 2 assertions)

And of course a nice formatted output like:

phpunit --testdox Calculator/ExampleTest.php 
PHPUnit 3.5.14 by Sebastian Bergmann.
 
Example
 [x] Should apply penalty when missing papers
 [x] Should apply penalty when damage is present

After we have finished designing we can run the command above again and save our Test class with another name since that will work with the Spec BDD features of behat. The new Test class will be a more thorough test class which will ensure regular unit testing. But it is encouraged that these two classes end up being similar so to save work and provide a good design practice.

Hope this helps!

First Symfony2 CMF based CMS: PagesBundle & CMF-Sandbox!

Update note:

There is the doctrine phpcr-odm layer that maps phpcr repositories to documents. The phpcr layer itself will be mostly relevant to people working on improving the doctrine layer, and maybe for advanced features in the cmf bundles. But for users of the cmf, the phpcr layer is like the sql api – you don’t need to touch it.

Here i will record my experience trying the first Symfony2 CMF based CMS PagesBundle from here:
http://melp.nl/2011/07/symfony2-phpcr-doctrine2-jackalope-recipe

So before I proceed further I was mentored by dbu some and I decided to first focus on the installation of the jackalope, jackrabbit cr server and phpcrbrowser.
The only issue I had with jackrabbit cr was that i had to run it with a port defined as I was already using jenkins on the same port:

java -jar jackrabbit-standalone-*.jar --port 8888

Then i ran into an error for phpcrbrowser but it was because that had not been updated or synced with the latest developments for jackalope so I had to:

cd ext/jackalope
git reset --hard 914c14ade72c9c94c02f269286006c26c98273c9
git submodule update
Submodule path 'api-test/suite': checked out '7c0c8d595e2c7247eb2d062fa15c4b5e03f35683'
Submodule path 'lib/phpcr': checked out '2eab6944842ddbaa94f9851557001a90e4c79a98'

Then phpcrbrowser was wrought back to life. In addition to this of course i did adjustments for the port and domain for my jackrabbit installation on www/index.php.

Once you are done with jackrabbit and the phpcrbrowser then do not move into tests as it is shown here as the tests for jackalope are not interesting for the cmf and the ones in sandbox are currently broken.

So for our first encounter with phpcr here we have a diagram of a use case:

The repository in phpcr is the entry point to get a session as shown in the diagram, the representation of the backend, in phpcr lingo, is the workspace. First we get the repository from the factory configured according certain parameters. Then we use the repository thus created to authenticate by login into the repository. The repository in turn returns to us a session from which we can operate thereby creating nodes, setting its properties, persisting them, and fetching them again for further processing.

1
2
3
4
5
6
7
8
9
10
11
12
$factoryclass = '\Jackalope\RepositoryFactoryJackrabbit';
$parameters = array('jackalope.jackrabbit_uri' => 'http://localhost:8080/server'); //end of implementation specific configuration
$factory = new $factoryclass();
$repository = $factory->getRepository($parameters);
$credentials = new \PHPCR\SimpleCredentials('admin','admin');
$session = $repository->login($credentials, 'default);
$node = $session->addNode('test', 'nt:unstructured');
$node->setProperty('prop', 'value');
$session->save();
...
$node = $session->getNode('/test');
echo $node->getPropertyValue('prop');

Ok so after a rapid intro to some phpcr code, we now turn to start with the cmf-sandbox. The cmf-sandbox is the test project that tries to demo the integration for all the phpcr components for a symfony2 project.

We now setup the project, register node types for phpcr and load fixtures:

git clone git://github.com/symfony-cmf/cmf-sandbox.git
cd cmf-sandbox
bin/vendors install
cp app/config/parameters.ini.dist app/config/parameters.ini
gedit app/config/parameters.ini
bin/vendors update
app/console doctrine:phpcr:register-system-node-types
app/console -v phpcr:fixtures:load --path=src/Sandbox/MainBundle/Resources/data/fixtures/ --purge=true

Because the documentation I saw on github told me to play adding or populating some info on my jackrabbit server I did and now I am getting this error while loading the fixtures:

[PHPCR\PathNotFoundException] 
HTTP 404 Path Not Found: DELETE array ( 
'/edu[2]' => 'http://localhost:8888/server/default/jcr:root/edu[2]', 
)

and now trying to do a purge stuck on this:

  [ErrorException]                                                                                                                                                                                                                          
  Notice: Undefined property: Symfony\Cmf\Bundle\PhpcrCommandsBundle\Command\PurgeCommand::$container in /home/cordoval/sites-2/cmf-sandbox/vendor/symfony-cmf/src/Symfony/Cmf/Bundle/PhpcrCommandsBundle/Command/PhpcrCommand.php line 38

There was another problem that I resolved by shutting down the java process and removing the jackrabbit subfolder that gets generated by the server.

Also make sure you have to place your vhost folder to be served starting at the cmf-symfony/web level, else you will have problems with loading the css and other assets files.

What is the next step?

Try to work on this issues here and help us with the symfony2 cmf effort:
https://github.com/symfony-cmf/symfony-cmf/issues

Symfony2 PHPStorm Repository of PHP Templates

In PHPStorm there is what is called Live Templates for PHP. In our case we would like to make a call to gather all templates available or to promote the development of templates that can be useful to someone developing on sf2.

To use the templates go to Settings and search for Live Templates and you will find it.

In order to use the templates you should do the following:

cd .WebIde10/config/templates/
git clone https://github.com/cordoval/PHPStorm-Live-Templates-for-Symfony2.git .

Then restart PHPStorm and you are ready to use it. Type fn and hit TAB key right after for expandable text feature.

Contribution

You should create an account on github.com and then fork the main repo above. Then clone your own repo into your templates folder. Create a new branch for contribution. then add the contribution changes/additions and push back to your own repo. Then click on pull request button to let everybody else know of your great additions. We will merge all contributions. So you want to keep your templates up to date often with a git pull upstream command.

Your submissions are greatly appreciated as it would help everybody to code on sf2 faster and without typing too much.

Behat BDD Some Tricks

Use of assertions for scenario oriented BDD

Very bad idea to do assertions with behat on actions or controllers:
http://programmersgoodies.com/zend-framework-integration-with-behat-bdd

Setting Backgrounds with Persisted Values

https://github.com/knplabs/KnpIpsum-for-symfony/issues/11

  Given I have the following Categories:
        | name              |
        | Animal            |
        | Developer         |
    And I have the following Things:
        | name              | category    |
        | Cat               | Animal      |
        | Dog               | Animal      |

Or perhaps a more generic:

    And I have the following <entityname>s [afresh]:
        | name              | category    |
        | Cat               | Animal      |
        | Dog               | Animal      |

And the entityname and logical name for the repo will be extracted and also wiped out depending on the argument afresh.

<?php
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$em->getRepository('KnpIpsumBundle:Things')->createQueryBuilder('t')->delete()->getQuery()->execute();
 
foreach ($table->getHash() as $hash) {
    $thing = new Things();
    foreach ($hash as $key => $val) {
        $setter = 'set'.ucfirst($key);
        $thing->$setter($val);
    }
    $em->persist($thing);
}
$em->flush();

Rules for Spec BDD

1) Form sentenses from your tests
2) Form noun with your test cases (test case and test should form a sentense)
3) Describe expectations instead of assertions (this is not currently possible in a clean way with phpspec)

In other words, just make your test cases nouns and tests sentenses, think about what you’re trying to achieve first same as you do with Behat and features but more low level.

This should be our flow: “Talk with your code in pair programming”

1) Write feature
2) Watch it fails
3.1) Write spec (unit-test)
3.2) Watch it fails
3.3) Write code to make spec (unit-test) pass
3.4) Write new spec
3.5) and so one until your FEATURE does pass
4) iterate on 3 over every scenario and feature
5) done

Source: Adapted discussion in channel by stof everzet and me

Now we can do another example besides the calculator done on the repo under the folder Calculator. We will work on a Symfony2 CLI wrapper for ls command.

Objective is to start from a feature describing input/output for CLI console app and to make it pass – we need a wrapper-class in code and symfony command, that will generate UNIX ls command with proper parameters. This is what we are to test with specs phpunit tests.

The spec test would test that the wrapper class generates “ls /home/cordoval” as command, whereas the feature would test the output when running the command on the directory.

So we first start to make the feature:

php app/console --env=test behat @CordovaFormModelBundle/ls.feature
# Features/ls.feature
Feature: listing feature
  In order to know what I have on file
  As an ubuntu beginner
  I want to be told the files and folders on directory
 
  Scenario: listing files and folders
    Given I am in a directory "/home/cordoval/sites-2/FormModelProjectBundle"
    When I run "app/console ls /home/cordoval/sites-2/FormModelProjectBundle"
    Then I should see:
    """
    app
    bin
    deps
    deps.lock
    LICENSE
    README
    README.md
    src
    vendor
    web
    """

The fail state should be:

 Actual output is:
      [InvalidArgumentException]
        Command "ls" is not defined.

And adding this to FeatureContext.php:

/**
     * @Given /^I am in a directory "([^"]*)"$/
     */
    public function iAmInADirectory($dir)
    {
        //if (!file_exists($dir)) {
        //    mkdir($dir);
        //}
        chdir($dir);
    }
 
    /**
     * @When /^I run "([^"]*)"$/
     */
    public function iRun($command)
    {
        exec($command, $output);
        $this->output = trim(implode("\n", $output));
    }
 
    /**
     * @Then /^I should see:$/
     */
    public function iShouldSee(PyStringNode $string)
    {
        if ($string->getRaw() !== $this->output) {
            throw new \Exception(
                "Actual output is:\n" . $this->output
            );
        }
    }
phpunit -c app vendor/bundles/Cordova/Bundle/FormModelBundle/Tests/Command/LsCommandTest.php
class LsCommandTest extends \PHPUnit_Framework_TestCase
{
    // who: "php app/console ls" --> LsCommand.php
    // how: write methods for spec bdd
 
    protected $lsCommand;
 
    protected function setUp()
    {
        $this->lsCommand = new LsCommand();
    }
 
    /**
     * @test
     */
    public function shouldParseWithCordovaColonLs() {
        $this->assertEquals($this->lsCommand->getName(), 'cordova:ls');
    }
 
    /**
     * @test
     */
    public function shouldHaveDescriptionAs() {
        $this->assertEquals($this->lsCommand->getDescription(), 'Lists files and folders');
    }
 
//// and the final class is
 
class LsCommand extends ContainerAwareCommand {
 
    /**
     * @see Command
     */
    protected function configure()
    {
        $this
            ->setName('cordova:ls')
            ->setDescription('Lists files and folders')
            ->setDefinition(array(
                new InputArgument('input', InputArgument::REQUIRED, 'The input directory or file'),
            ))
            ->setHelp(<<<EOT
The <info>cordova:ls</info> command lists files and folders for a given input directory or file
 
  <info>php app/console cordova:ls myProjectDir/subfolder</info>
EOT
            );
    }
 
    /**
     * @see Command
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $dirfile = $input->getArgument('input');
 
        if (null == $dirfile) {
            throw new \InvalidArgumentException('You need to specify a file or directory as argument.');
        }
 
        exec('ls '.$dirfile, $text);
        $output_text = trim(implode("\n", $text));
        $output->writeln($output_text);
    }
 
}
 
// passing test
PHPUnit 3.5.14 by Sebastian Bergmann.
 
..
 
Time: 0 seconds, Memory: 7.75Mb
 
OK (2 tests, 2 assertions)
 
// passing feature
Feature: listing feature
  In order to know what I have on file
  As an ubuntu beginner
  I want to be told the files and folders on directory
 
  Scenario: listing files and folders                                                 # vendor/bundles/Cordova/Bundle/FormModelBundle/Features/ls.feature:8
    Given I am in a directory "/home/cordoval/sites-2/FormModelProjectBundle"         # Cordova\Bundle\FormModelBundle\Features\Context\FeatureContext::iAmInADirectory()
    When I run "app/console cordova:ls /home/cordoval/sites-2/FormModelProjectBundle" # Cordova\Bundle\FormModelBundle\Features\Context\FeatureContext::iRun()
    Then I should see:                                                                # Cordova\Bundle\FormModelBundle\Features\Context\FeatureContext::iShouldSee()
      """
      app
      bin
      deps
      deps.lock
      LICENSE
      README
      README.md
      src
      vendor
      web
      """
 
1 scenario (1 passed)
3 steps (3 passed)
0m0.125s

Another spec bdd feature we can use to track our advancement is this:

$ phpunit --testdox -c app vendor/bundles/Cordova/Bundle/FormModelBundle/Tests/Command/LsCommandTest.php
PHPUnit 3.5.14 by Sebastian Bergmann.
 
Cordova\Bundle\FormModelBundle\Tests\Command\LsCommand
 [x] Should parse with cordova colon ls
 [x] Should have description as

See how nice it gets formatted.

And yet here is another tip to create your skeletons for when you start from the phpunit test or backwards:

phpunit --skeleton-test ClassName
phpunit --skeleton-class TestClassName

As in here: http://www.hashbangcode.com/blog/phpunit-skeleton-classes-588.html