Entries in testing (3)
How I Test
Wednesday, May 2, 2012 at 5:07PM It's been over a year since I've wrote the libellous words "One of the things the PHP community has always been crap about is testing"; an annum on and change is really starting to gain momentum. Personally, I'm now programatically testing pretty much every line of code I ship, in some way or another, and I've learnt a lot over the past year about the best ways to test. Others are starting to really see the value of unit testing.
One of the things I've discovered by talking to other developers is that a lot of people lack direction in what to test and how to make it a part of their process. They get the basics, but they struggle to get into the mindset of someone who writes tests. If you don't get it chances are you're doing it wrong.
Learning the tools is easy. Grabbing PHPUnit off PEAR is a piece of cake, as is setting up a couple of files and running phpunit at your command line. Nowadays, the basic steps of setting up an environment for testing are so few and so effortless, it's child's play. For that reason I don't believe there's much more value in writing another introductory article.
This piece is about the process of testing. It's about test-driven development, how I work when I'm writing code and how writing unit tests gives me smarter, more stable and more refactorable code.
Get PHPUnit installed. We're going to write a small PHP class that takes an email request body and parses a bunch of info about it.
Create two new files, email_parser_test.php and email_parser.php. I like to work with these files side by side, so in Sublime Text 2 - a great text editor which you should all be using, incidentally - I'll hit Command + Alt + 2 to open my editor up into two columns. This means I can have my tests on the left and my implementation on the right.
Inside our test file, create a new class and a basic test method:
class Email_parser_test extends PHPUnit_Framework_TestCase
{
public function test_something()
{
$this->assertTrue(true);
}
}
Jump into Terminal and give it a run:
$ phpunit --colors email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 2.25Mb
OK (1 test, 1 assertion)
That --colors flag is going to get annoying, so we'll dump this in a phpunit.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true"></phpunit>
There's one tiny little bit more of setup we need to do before we can begin writing proper code. We'll need some test data to work with; an email we know the format of and can reproduce every time. Tests are only valuable if they're consistent. We want two emails to work with; and you'll see why in a moment. I'll use an email I got from good friend Alex Bilbie about his excellent CodeIgniter MongoDB Library. Save this in a file email.eml:
Delivered-To: jamie@jamierumbelow.net
Return-Path: <alex.bilbie@gmail.com>
Date: Wed, 2 May 2012 01:06:01 +0100
From: Alex Bilbie <alex.bilbie@gmail.com>
To: jamie@jamierumbelow.net
Message-ID: <586B7E1735704A1CB388F4434B4EAA9B@gmail.com>
Subject: =?utf8?Q?Update_to_my_Mongo_library?=
X-Mailer: sparrow 1.5 (build 1043.1)
Content-Type: text/html
MIME-Version: 1.0
I've updated my CodeIgniter MongoDB library to version 0.5.1. It mostly contains bug fixes and optimisations.
Version 2.0 of the library is almost finished – check it out at <a href="https://github.com/alexbilbie/codeigniter-mongodb-library/tree/v2">https://github.com/alexbilbie/codeigniter-mongodb-library/tree/v2</a>. I'm currently working out how PHP Unit works so that the library can be tested on Travis CI each time a new commit is made.
And another one (email2.eml):
Delivered-To: jamie@jamierumbelow.net
Return-Path: <me@jeremygimbel.net>
Date: Wed, 3 May 2012 09:25:55 +0100
From: Jeremy Gimbel <me@jeremygimbel.net>
To: praise@jamierumbelow.net
Message-ID: <109A8F7538567P7VD675E7990N8NDS7T@gmail.com>
Subject: =?utf8?Q?A_quick_note!?=
Content-Type: text/html
MIME-Version: 1.0
Jamie, I love what you're doing with <a href="https://github.com/jamierumbelow/codeigniter-base-model">MY_Model</a> and <a href="https://github.com/jamierumbelow/codeigniter-base-controller">MY_Controller</a>!
Now we know everything's working a okay and we've got our test data, we'll waste no more time and get straight on with writing our first test.
Actually, that was a lie. I just want to make three points before we begin:
- Remember that we should always write the smallest amount of code possible to get the test to pass. That'll become clearer shortly.
- We're writing unit tests, so we're testing the input -> output flow of a unit of code (and any side effects)
- Your tests should never negatively affect the API. If a parameter is only necessary for the tests, you're doing it wrong.
We'll start off by simply parsing the headers out and grabbing them. This is good place to start because we'll need to parse the headers anyway; it's therefore worth our while to test that core functionality first.
public function test_parses_headers()
{
$parsed = $this->parser->parse($this->email);
$this->assertEquals(10, count($parsed['headers']));
}
The test might be simple, and it certainly doesn't test all the functionality we need to, but that is exactly what we want.
When I test, I find it valuable to write the smallest amount of code to test and the smallest possible amount to get the test past. It doesn't matter what the system needs to do in the future, it's just about getting the test to past.
You'll spot that we're accessing $this->parser and $this->email. Let's define them:
public function setUp()
{
$this->parser = new Email_parser();
$this->email = file_get_contents('email.eml');
}
Run our tests:
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
PHP Fatal error: Class 'Email_parser' not found in /Users/jamierumbelow/Sites/email_parser/Email_parser_test.php on line 7
Fatal error: Class 'Email_parser' not found in /Users/jamierumbelow/Sites/email_parser/Email_parser_test.php on line 7
What's the smallest amount of code that can fix this error? In our Emailparser.php_ file:
class Email_parser { }
...and require it at the top of our test file:
require_once 'email_parser.php';
Run again and we get an error about missing parse() method. Define it:
public function parse($email) { }
Run again and we get an assertion error:
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
F
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) Email_parser_test::test_parses_headers
Failed asserting that 0 matches expected 10.
/Users/jamierumbelow/Sites/email_parser/email_parser_test.php:17
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
What's the smallest amount of code that we can write to make this test pass?
return array( 'headers' => array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ) );
You're probably shouting at the screen and telling me I'm an idiot right now, and honestly, I wouldn't blame you. This might seem like a total waste of time. You'll have to bare with me here; you'll soon see why this can become valuable.
Now we've got our test passing, let's add another assertion to it:
public function test_parses_headers()
{
$parsed = $this->parser->parse($this->email);
$this->assertEquals(10, count($parsed['headers']));
$this->assertEquals('jamie@jamierumbelow.net', $parsed['headers']['To']);
}
Got it yet? Nope? Okay, I'll carry on.
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
E
Time: 0 seconds, Memory: 2.50Mb
There was 1 error:
1) Email_parser_test::test_parses_headers
Undefined index: To
/Users/jamierumbelow/Sites/email_parser/email_parser_test.php:18
FAILURES!
Tests: 1, Assertions: 1, Errors: 1.
What's the smallest possible amount of code to fix it? We'll actually cheat a little bit here since we KNOW that once the To index is in the array, it'll ask for the value of jamie@jamierumbelow.net, so we might as well kill two birds with one stone.
return array( 'headers' => array( 'To' => 'jamie@jamierumbelow.net', 2, 3, 4, 5, 6, 7, 8, 9, 10 ) );
Good good. Let's add one more just to ensure we're being thorough:
$this->assertEquals('<586B7E1735704A1CB388F4434B4EAA9B@gmail.com>', $parsed['headers']['Message-ID']);
The failure:
1) Email_parser_test::test_parses_headers
Undefined index: Message-ID
Again, killing two birds with one stone:
return array(
'headers' => array( 'To' => 'jamie@jamierumbelow.net',
'Message-ID' => '<586B7E1735704A1CB388F4434B4EAA9B@gmail.com>',
3, 4, 5, 6, 7, 8, 9, 10 ) );
So WHAT IS THE POINT OF ALL THIS?
What happens when we copy + paste our assertions and run the same test on email2.eml as well?
Update the assertions so that we're checking for the appropriate number of headers and the appropriate values:
public function test_parses_headers()
{
$parsed = $this->parser->parse($this->email);
$this->assertEquals(10, count($parsed['headers']));
$this->assertEquals('jamie@jamierumbelow.net', $parsed['headers']['To']);
$this->assertEquals('<586B7E1735704A1CB388F4434B4EAA9B@gmail.com>', $parsed['headers']['Message-ID']);
$parsed = $this->parser->parse($this->email2);
$this->assertEquals(9, count($parsed['headers']));
$this->assertEquals('praise@jamierumbelow.net', $parsed['headers']['To']);
$this->assertEquals('<109A8F7538567P7VD675E7990N8NDS7T@gmail.com>', $parsed['headers']['Message-ID']);
}
And load our email in our tests' setUp():
$this->email2 = file_get_contents('email2.eml');
Run it:
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
F
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) Email_parser_test::test_parses_headers
Failed asserting that 10 matches expected 9.
/Users/jamierumbelow/Sites/email_parser/email_parser_test.php:24
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
Understand the logic yet?
Ahhhhhh. There we go.
Suddenly, we're now in the position where in order to get this test to pass, we're forced to parse the email. In order to get the test passing, we need to implement our test appropriately. Adding in some weird switch as a parameter, although would require less code, detracts from our API (that is to say, it would be pointless in production/implementation code). So the only thing we can really do is actually parse the email.
public function parse($email)
{
list($headers_list, $body) = explode(PHP_EOL . PHP_EOL, $email, 2);
$headers_list = explode(PHP_EOL, $headers_list);
$headers = array();
foreach ($headers_list as $h)
{
list($key, $value) = explode(':', $h, 2);
$headers[$key] = trim($value);
}
return array( 'headers' => $headers );
}
This does two great things. It makes our tests pass:
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
.
Time: 0 seconds, Memory: 2.50Mb
OK (1 test, 6 assertions)
...and it has the fortunate side effect of providing us with the body, parsed and separated out nice and easily!
While it's good that that's already done for us, it's currently untested, so let's fix that posthaste:
public function test_parses_body()
{
$expected_body_email = "I've updated my CodeIgniter MongoDB library to version 0.5.1. It mostly contains bug fixes and optimisations.\n\nVersion 2.0 of the library is almost finished – check it out at <a href=\"https://github.com/alexbilbie/codeigniter-mongodb-library/tree/v2\">https://github.com/alexbilbie/codeigniter-mongodb-library/tree/v2</a>. I'm currently working out how PHP Unit works so that the library can be tested on Travis CI each time a new commit is made.";
$expected_body_email2 = 'Jamie, I love what you\'re doing with <a href="https://github.com/jamierumbelow/codeigniter-base-model">MY_Model</a> and <a href="https://github.com/jamierumbelow/codeigniter-base-controller">MY_Controller</a>!';
$parsed = $this->parser->parse($this->email);
$this->assertEquals($expected_body_email, $parsed['body']);
$parsed = $this->parser->parse($this->email2);
$this->assertEquals($expected_body_email2, $parsed['body']);
}
And making this pass is now just a matter of adding the $body variable to the return line:
return array( 'headers' => $headers, 'body' => $body );
So. We have our headers and we have our body. While cool, it's not very useful. Let's try to extract the subject.
For those of you who know the MIME protocol, you'll know that the subject field is usually encoded in some way. This is conventionally Q-encoding or Base64. PHP's iconv extension provides a great little function for decoding MIME headers: iconv_mime_decode.
Let's write a test:
public function test_subject()
{
$parsed = $this->parser->parse($this->email);
$this->assertEquals('Update to my Mongo library', $parsed['subject']);
}
To get it passing:
return array( 'headers' => $headers, 'body' => $body, 'subject' => 'Update to my Mongo library' );
Expand the tests:
$parsed = $this->parser->parse($this->email2);
$this->assertEquals('A quick note!', $parsed['subject']);
And we can implement the function to get the tests to pass:
$subject = iconv_mime_decode($headers['Subject']);
return array( 'headers' => $headers, 'body' => $body, 'subject' => $subject );
And there we have it!
$ phpunit email_parser_test.php
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /Users/jamierumbelow/Sites/email_parser/phpunit.xml
...
Time: 0 seconds, Memory: 2.50Mb
OK (3 tests, 10 assertions)
It's a simple piece of code and a simple test suite, but hopefully this article has clarified how I test and why I believe this process to be truly valuable.
High-level testing PHP applications with Cucumber
Sunday, January 2, 2011 at 3:17PM One of the things that the PHP community have always been crap about is testing, and honestly, I've never really been much better at it either. This is partly due to the fact that there really aren't any good testing systems out there for PHP and partly due to the laziness of a lot of developers.
Ruby and Rails have been doing a great job of pushing test support in their communities. One of the best new solutions to have sprung up from the Ruby camp is Cucumber, a plain-text based tool that allows you to write user stories and test applications at a high-level. High-level testing is testing the application at the level that the user will see: ensuring that the appropriate text is displayed in appropriate places, ensuring that a certain change is made when the user visits a certain page, et cetera.
Cucumber can be used to test right down to a unit-test level, where individual blocks of code are tested, but this is somewhat limited to Ruby applications only.
Make sure you have the latest version of Ruby installed, jump into Terminal and install the Cucumber Rubygem. We'll also need the Webrat gem, which will give us browser simulation and a matchers API, the RSpec gem, which will give us our testing framework, and mechanize, which is an underlying framework that we'll use with Webrat.
sudo gem install cucumber
sudo gem install rspec
sudo gem install webrat
sudo gem install mechanize
Once you've installed all those gems, check they're installed and ready to use:
$ cucumber --version
0.10.0
$ spec --version
rspec 1.3.0
$ ruby -e 'require "rubygems"; print require("webrat")'
true
$ ruby -e 'require "rubygems"; print require("mechanize")'
true
Inside your project's directory, we're going to create a new directory called features. This is going to contain all of the code related to our testing. Create two new sub-directories too, features/support and features/step_definitions. The former will contain support files (principally our environment/setup file) and the latter will contain the Webrat code to do the actual testing.
Before we can start writing features, we need to set up our environment so that Webrat knows how to connect to our PHP application. Create a features/support/env.rb file.
require 'rspec/expectations'
require 'webrat'
require 'test/unit/assertions'
World(Test::Unit::Assertions)
Webrat.configure do |config|
config.mode = :mechanize
end
World do
session = Webrat::Session.new
session.extend(Webrat::Methods)
session.extend(Webrat::Matchers)
session
end
We're loading RSpec, Webrat and Test::Unit (Ruby's built-in unit tester) and we're then adding the Test::Unit::Assertions module to our Cucumber World (scope). We're configuring Webrat to use Mechanize (which will allow Webrat to simulate a web browser), and then we're making a new Webrat session and exposing it to Cucumber.
For the purposes of demonstration, I've decided to test a remote site instead of a local one, and I thought it would be fun to test the CodeIgniter website. We'll write a very simple feature that tests whether we can login and logout successfully.
Create a new feature file inside features called authentication.feature. The name of this file can be anything, as long as it ends with .feature. A feature contains a bunch of scenarios that document a specific user feature in an application. It's then the job of the step definitions to make this code execute, but a helpful by-product of this is that you've got a bunch of feature definitions that can be easily written and read by non-programmers.
First, let's just describe our feature:
Feature: Authentication
In order to use the CodeIgniter site
As a user
I want to be able to login and logout
This is a standard format for features. It doesn't really do anything, it just gives us a general overview of the specific feature we're writing. Let's add a scenario. Remember, indentation is important here.
Scenario: Login
Given I am on the home page
And I am logged out
When I click on Login
Then I should see the login page
So, this scenario details when a user clicks the "Login" link on the http://codeigniter.com site. We're saying "Given I am on the home page", which we'll use to point Webrat to the homepage. We'll also check we're logged out (and click on the logout link to ensure this if we are). When the user clicks on the Login link, we want to see the login page.
Describing this in English might not make a lot of sense yet, but stay with me. It will all become a lot clearer when we write the step definitions. Create a new file in features/step_definitions called authentication_steps.rb. We'll start off by defining the "Given I am on the home page" step.
Given /I am on the home page/ do
visit "http://codeigniter.com"
end
That was easy! We define the steps similar to the way we write them. We're visiting the homepage when that regex matches on the Given clause. Let's move on to "And I am logged out". This is a bit more tricky. "And", much like "But", is there just for readability. It takes the form of the previous clause, so we'll define it as Given.
Given /I am logged out/ do
click_link 'Logout' if not contain('<a href="http://codeigniter.com/forums/member/login/">Login</a>')
end
We're clicking the 'Logout' link if the page doesn't contain the login link. This ensures we're logged out. Now it's time to implement the "When" step. We use this step to click on the login link. See where this is going?
When /I click on (.*)/ do |link|
click_link link
end
I've used a regex here to make this a little more re-usable. Now, we can specify any link in our feature and Cucumber will know what to do with it. Finally, "Then".
Then /I should see the login page/ do
response_body.should have_xpath(%\//input[@name='username']\)
response_body.should have_xpath(%\//input[@name='password']\)
response_body.should contain("Login Required")
end
We're using the response_body method to get the page, then using RSpec's should method to ensure that we've got the two input elements - have_xpath is a Webrat method that checks for the existence of matches on XPath queries - and the message "Login Required".
Now, in the Terminal, run cucumber features, where features is the features/ directory. You should see the feature and scenario outputted to the Terminal, with the Scenario steps in green. Congratulations, you just wrote your first Cucumber scenario!
Let's write another scenario. Replace login and password with your own CodeIgniter username and password.
Scenario: Login processing
Given I am on the login page
When I fill in the username input with **login**
And I fill in the password input with **password**
And I click the button Submit
Then I should see the logged in page
And the step definitions:
Given /I am on the login page/ do
visit "http://codeigniter.com/forums/member/login/"
end
When /I fill in the (.*) input with (.*)/ do |input, value|
fill_in input, :with => value
end
When /^I click the button (.*)$/ do |button|
click_button button
end
Then /^I should see the logged in page$/ do
response_body.should contain("You are now logged in")
end
I've reduced duplication again in a few places, and made some things regexes just in case we need to re-use them. We can use the fill_in method to fill in an input name with a value. Give that a run and you should get more passes.
Finally, let's write and implement the scenario for logging out.
Scenario: Logout
Given I am logged in
And I am on the home page
When I click on Logout
Then I should see the logged out page
We only need to implement two steps here; "Given I am logged in" and "Then I should see the logged out page". The other two we've already written.
Given /I am logged in/ do
visit "http://codeigniter.com"
if contain('<a href="http://codeigniter.com/forums/member/login/">Login</a>')
click_link "Login"
fill_in 'username', :with => "**username**"
fill_in 'password', :with => "**password**"
click_button "Submit"
response_body.should contain("You are now logged in")
end
end
Then /I should see the logged out page/ do
response_body.should contain("You are now logged out")
end
Given I am logged in visits the home page, checks if we're logged out, if we are, clicks on the link, submits the form and checks we're logged in successfully. Then I should see the logged out page goes checks that we see the logged out page. It's all pretty self-explanatory.
I hope I've managed to explain how to write Cucumber features. Cucumber really is a fantastic way to do this higher level testing, and you can use it with any platform and framework, including PHP. Check out the Cucumber site if you're interested in finding out more.
codeigniter,
cucumber,
ruby,
testing 

