Top Ten Behat Tip

Using Behat for BDD with Symfony is always a good idea. Here are my top ten hints for getting the most out of this useful tool;

  1. The feature files should be understandable by a non-techical audience – so don’t use IDs of form elements and such like. Mink can find input boxes by label, name or placeholder. Every input on the web page must have something that identifies its function to the user so use that. What is easier to understand; When I enter “Fred” for “Username” – or – When I enter “Fred” for “#uname”
  2. Use the Behatch library to get step definitions that might be useful (https://github.com/Behatch/contexts)
  3. Tag the scenarios to group them into sensible sub-sets. Such as “@security”, “@routing”, “@profile” – then you can just run a subset of the tests if you need to
  4. Ensure the database is in a known state before the tests start to run. This is probably the most important tip – you and your tests need to know exactly what is in the database before the tests start. There is a useful Doctrine class called the Purger which can empty database tables between tests. https://github.com/doctrine/data-fixtures/blob/master/lib/Doctrine/Common/DataFixtures/Purger/ORMPurger.php
  5. The Behat tests should only test outcomes that a regular user could verify, so try to avoid testing to see if an entity is updated in the database for example. If a user updates an entity for example, there should be some way in the user interface for them to verify that the change has been made – not by poking around in the  database.
  6. The cheat sheet is useful – if you’re old school, print it out and keep it next to you. Otherwise, open it in your second screen http://blog.lepine.pro/images/2012-03-behat-cheat-sheet-en.pdf
  7. If you must insist on checking in the database for a property of an entity, bear in mind that Doctrine might have cached the entity. So, your Feature Context might have loaded the entity from the database and, in running the test, Symfony might have modified the entity and saved the change to the database. If the test then tries to verify that an attribute has changed, its default position is to check the cached version of the entity – which, of course, will be exactly the same as it was when it loaded before the test ran. You’ll have to refresh the entity before doing the assertion – this forces Doctrine to reload the entity and to ignore any cached version.
  8. Make sure each Scenario has a unique and descriptive description – so when it fails in Jenkins you know where to start looking
  9. Don’t assume that the features will be tested in a certain order – so if a test changes something in the database, another test further down the line might fail as it could be assuming that the database is in another state (see 4)
  10. Don’t waste time writing steps that already exist – don’t write steps like “I should see the text” – use “I should see” – a built-in step from Mink. do “behat -dl” to see all the steps that are already written and available to use.

Cross Browser Functionality – how our Government does it

Anyone who works in the field of web-development will be well aware of the problems of the inconsistencies between the ways different browsers work. All too often, we find ourselves saying such things as “Well, it worked fine in Firefox/Chrome/Opera, why won’t it work in Internet Explorer 6/7/8/9/10”.

The latest issue of Private Eye magazine led me to this site; http://www.dwp.gov.uk/eservice/need.asp – an online portal for claiming Disability Living Allowance. Obviously, the current Government is committed to making it as hard as possible for anyone to claim any sort of benefit, but this site takes it a little too far;

Operating systems and browsers

The service does not work properly with Macs or other Unix-based systems even though you may be able to input information.

You are likely to have problems if you use Internet Explorer 7, 8, 9 and 10, Windows Vista or a smartphone. Clearing temporary internet files may help but you may wish to claim in another way.

There is also a high risk that if you use browsers not listed below, including Chrome, Safari or Firefox, the service will not display all the questions you need to answer. This is likely to prevent you from successfully completing or submitting the form. You may wish to claim in another way.

In short – you can only claim for this benefit on-line if you haven’t updated your computer or browser for ten years.

Using multiple entity managers in Symfony2

When using a single Symfony2 install to manage multiple web-apps, each with its own database, you can set up multiple entity managers and tell each bundle which manager to use.

One thing that stumped me, and has seemed to stump others judging by a quick Google, is how to inject a non-default entity manager into a service.

Turns out, the way to do it use [name]_entity_manager, instead of entity_manager in the arguments line of services.yml. So, if you have an entity manager called ‘other’, use this:

services:
     my_bundle.selector.project_selector:
         class: Me\MyBundle\Form\Selector\ProjectSelectorType
         arguments: ["@doctrine.orm.other_entity_manager"]

 

Using PHPass with CodeIgniter

Storing passwords in a database is a necessary evil for most web-applications. No-one in their right mind would save plain-text passwords, and MD5 hashes aren’t much better. A useful library called PHPass exists to hash passwords in a more secure manner. Using this in CodeIgniter should be straightfoward, but it is a bit fiddly.

Using third-party libraries in CodeIgniter is simple – just make sure the class you wish to use is the appliction/libraries directory and then use

$this->load->library('some_library.php');

But, when CodeIgniter loads the library, it also instantiates an object using the class definition in the library file. If the class constructor requires some parameters, you can pass them by adding an array to the $this->load->library() function;

$this->load->library('some_library.php',$params);

In this case, $params must be an array.

The PHPass class definition, however, requires two parameters. So, the easy way round this is to adapt the PHPass class constructor to accept an array rather than two single parameters;

function PasswordHash($params)
{
        $iteration_count_log2 = $params[0];
        $portable_hashes = $params[1];
        $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
            $iteration_count_log2 = 8;
        $this->iteration_count_log2 = $iteration_count_log2;

        $this->portable_hashes = $portable_hashes;

        $this->random_state = microtime();
        if (function_exists('getmypid'))
            $this->random_state .= getmypid();
    }

Football Results Database – part 1

In order to store the results of the NFL games, we’re going to need some tables. My initial design is as follows;

  • a table to store details of the teams in the league. For simplicity, we’ll assume that we only want to store results for a single season (for now), so the teams won’t change divisions. The ‘teams’ table just needs to store a ‘short-name’ for each team (which might as well act as the Primary Key for the table), the full name of the team, which conference it plays in, and which division. That should do for now.
  • a table to store details of the two Conferences. We might as well use AFC and NFC as the Primary Key for this table
  • a table to store details of each game; the week number, the date, maybe the location (maybe we’ll add this later) and whether the game went into overtime
  • a table to sore the results. a result will take up two rows of the table, one row for each team. I’ve added a ‘home’ field, which will be 1 if that row contains the score of the home team.

So, here is my basic table schema for MySQL;

CREATE TABLE `conferences` (
  `short` char(3) NOT NULL,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`short`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert  into conferences values 
('AFC', 'American Football Conference'), 
('NFC', 'National Football Conference');

CREATE TABLE `games` (
  `game_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `week` int(11) NOT NULL,
  `year` int(11) NOT NULL,
  `date` date NOT NULL,
  `overtime` tinyint(4) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`game_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `results` (
  `game_id` int(11) unsigned NOT NULL,
  `team` char(2) NOT NULL,
  `points` int(11) NOT NULL,
  `home` tinyint(4) NOT NULL DEFAULT '0',
  KEY `game_id` (`game_id`),
  KEY `team_fk` (`team`),
  CONSTRAINT `game_fk` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `team_fk` FOREIGN KEY (`team`) REFERENCES `teams` (`short`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `teams` (
  `short` char(2) NOT NULL,
  `name` varchar(100) NOT NULL,
  `conference` char(3) NOT NULL,
  `division` varchar(5) NOT NULL,
  PRIMARY KEY (`short`),
  KEY `conf` (`conference`) USING BTREE,
  CONSTRAINT `conf` FOREIGN KEY (`conference`) REFERENCES `conferences` (`short`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO teams VALUES 
('AC', 'Arizona Cardinals', 'NFC', 'West'), 
('AF', 'Atlanta Falcons', 'NFC', 'South'), 
('BB', 'Buffalo Bills', 'AFC', 'East'), 
('BR', 'Baltimore Ravens', 'AFC', 'North'), 
('CB', 'Chicago Bears', 'NFC', 'North'), 
('CL', 'Cleveland Browns', 'AFC', 'North'), 
('CN', 'Cincinnati Bengals', 'AFC', 'North'), 
('CP', 'Carolina Panthers', 'NFC', 'South'), 
('DB', 'Denver Broncos', 'AFC', 'West'), 
('DC', 'Dallas Cowboys', 'NFC', 'East'), 
('DL', 'Detroit Lions', 'NFC', 'North'), 
('GB', 'Green Bay Packers', 'NFC', 'North'), 
('HT', 'Houston Texans', 'AFC', 'South'), 
('IC', 'Indianapolis Colts', 'AFC', 'South'), 
('JJ', 'Jacksonville Jaguars', 'AFC', 'South'), 
('KC', 'Kansas City Chiefs', 'AFC', 'West'), 
('MD', 'Miami Dolphins', 'AFC', 'East'), 
('MV', 'Minnesota Vikings', 'NFC', 'North'), 
('NE', 'New England Patriots', 'AFC', 'East'), 
('NG', 'New York Giants', 'NFC', 'East'), 
('NJ', 'New York Jets', 'AFC', 'East'), 
('NO', 'New Orleans Saints', 'NFC', 'South'), 
('OR', 'Oakland Raiders', 'AFC', 'West'), 
('PE', 'Philadelphia Eagles', 'NFC', 'East'), 
('PS', 'Pittsburg Steelers', 'AFC', 'North'), 
('SD', 'San Diego Chargers', 'AFC', 'West'), 
('SF', 'San Francisco 49ers', 'NFC', 'West'), 
('SL', 'St. Louise Rams', 'NFC', 'West'), 
('SS', 'Seattle Seahawks', 'NFC', 'West'), 
('TB', 'Tampa Bay Buccaneers', 'NFC', 'South'), 
('TT', 'Tennessee Titans', 'AFC', 'South'), 
('WR', 'Washington Redskins', 'NFC', 'East');