preg_replace and calling out to MySQL with SELECT CONCAT.
Here’s an approach that uses filesystem functions. When combined with some lesser-known PHP streams functionality, it has several practical applications.
There are several modes for opening a file that will allow you to seek to arbitrary positions in the file and read or write at those positions. One of the most frequently used of these modes is w+, which the tmpfile function uses automatically.
We can repeatedly write, then rewind the pointer and read:
$f = tmpfile(); fwrite($f, 'foo'); fwrite($f, 'bar'); rewind($f); $contents = stream_get_contents($f); //=> "foobar" fclose($f);
When writing to the filesystem, the above provides yet another inefficient solution to David’s exercise. Now let’s take it a bit further to see how this can be useful.
PHP 5.1 introduced two new in-memory streams: php://memory and php://temp. The php://memory stream operates entirely in memory. The php://temp stream operates in memory until it reaches a given size, then transparently switches to the filesystem.
We can modify the above example to use the php://memory stream instead of hitting the filesystem:
$f = fopen('php://memory', 'w+'); fwrite($f, 'foo'); fwrite($f, 'bar'); rewind($f); $contents = stream_get_contents($f); //=> "foobar" fclose($f);
Putting a string inside a fast temporary stream can be very useful. For example, we can then attach filters to that stream.
Temporary streams are also handy for testing. There are some rather elaborate virtual file system libraries out there but many times a stream is all you need.
Zend_Log has a log handler for streams that accepts either a filename or a stream resource. We can configure it with a php://memory stream for testing:
$f = fopen('php://memory', 'w+'); $writer = new Zend_Log_Writer_Stream($f); $logger = new Zend_Log($writer);
Assuming your well-designed application has a convenient injection point for the logger instance, your test can pass it in before your test activates some action which should result in logging:
$logger->crit('critical message worth testing');
When the action has completed, the test can rewind $f and use it as a test spy.
rewind($f); $contents = stream_get_contents($f); $this->assertRegExp('/message worth testing/i', $contents); // PHPUnit
Not surprisingly, my unit tests for Zend_Log use this same technique.
Not long ago, we built a custom document storage system for a client. One of its more interesting features is that it integrates with an internet fax service so users can select documents in the system and then fax them. For each fax, the software automatically generates a cover page.
To make the cover page, I first made a nice template using a drawing tool and then saved it in PDF format. I then used Zend_Pdf to write over the template with dynamic content. I first demonstrated this technique in this article.
Since the cover page is only used once (during transmission) and easy to regenerate, I don’t save the output to the filesystem. Instead, I create a php://temp stream. The instance method Zend_Pdf->render() writes the PDF output only to that stream, which is then rewound. Functions like stream_copy_to_stream or fpassthru can then be used to send the final output where it needs to go, and the whole process normally never needs to use the disk.
The slides are now available in PDF format.
We were located in Hall B of the Santa Clara Convention Center, which is a very large room that’s also used for the keynotes. Andi told us the room was selected based on the number of people who registered for our session. We initially had doubts but the attendance was greater than any previous year and the room worked out quite well. We were thankful that unlike last year, everyone was able to get a seat.
Thank you to all who attended. We enjoyed meeting many of you during the breaks and hope that you found our session helpful.
]]>Both talks were well attended had great audience participation. Thanks to everyone that attended and I hope you enjoyed them.
About seven people came up after the Integration Testing talk with good questions and feedback. I think that 45 minutes was enough to provide good starting points for the topics covered but it was clear that these users were engaged and ready to dig into it more. If I give this talk again, it is going to be in a tutorial format so we can get much deeper into the material and have some exercises.
After my Supervisor talk at the end of the day, I had the pleasure of going out to dinner with Roger Hoover and some other Supervisor users. It’s been very exciting to see Supervisor picking up traction over the past year. Besides the work that Chris McDonough and I are doing, Supervisor is being used in several large architectures of companies whose names you know. If you’re using Supervisor and wouldn’t mind us telling others about it, please contact me.
]]>
The OSCON 2008 website has published its talk schedule. I’ll be giving two talks at OSCON this year; one on the Python track and one on the PHP track.
I will quickly introduce you to Supervisor and the immediate benefits of running your server processes under it. We will then dive into how applications written specifically for Supervisor can take advantage of it as a platform — writing your own event listeners to observe Supervisor and the process lifecycle, controlling with XML-RPC, and extending the Supervisor core with your own Python extensions.
This will be an expanded version of the talk I gave with Chris McDonough at PyCon 2008. Since PyCon, there’s been quite a few interesting developments in Supervisor like the ability to extend supervisorctl and progress made on configuration reloading. We’ll touch on these as well, so if you attended the PyCon talk there will still be new and interesting material in this talk for you.
While more PHP developers are recognizing the importance and benefits of unit testing, the uptake of PHP developers using automated integration or acceptance testing is relatively slow. This testing is equally crucial to maintaining the integrity of applications.
I’m going to help get you started testing at the application level with practical tips and source code. We’ll look at how to structure your HTML markup so it’s more easily testable, making tests easier to write and maintain with CSS selectors, organizing your tests, and testing with or without a browser.
We write a lot of PHP applications with this kind of testing at Maintainable. Before the conference, we plan to release some open source PHP code to help you test that we’ll cover in the talk as well. I’d also suggest you check out Sebastian Bergmann’s tutorial session on PHPUnit’s integration with Selenium RC.
]]>@group annotation.
At my company, we typically organize our test case classes into high-level groups such as unit and functional. Method-level group annotations are inconvenient for us because we’d need to annotate every method of every test case class.
I was discussing this with Sebastian and not long after he had committed changesets 1293 and 1294 to the PHPUnit repository.
Now, @group annotations on the class inherit to all the methods of that class. When all the test methods of a class belong to the same group, just annotate the class:
/** * @group functional */ class FooTest extends PHPUnit_Framework_TestCase { ...
With that one annotation to the class, a command like this one will run all the tests in the class above and any other tests annotated with @group functional:
$ phpunit --group functional AllTests.php
That’s a great shortcut. It shouldn’t take long to put @group annotations on the test case classes of a project.
Having the ability to annotate individual test methods is still very useful because test methods (and even classes) may belong to multiple groups.
Sebastian pointed out a great use case for this in the form of testing bugs that span multiple test case classes. I was already commenting my tests with bug tracker numbers, now I’ve just converted them to @group annotations.
Whenever we receive a bug report from a client, we will create tests to reproduce the bug and then fix the implementation so the tests pass. This ensures that the bug can be reproduced and that we won’t repeat the same mistake again later.
Now, the test methods associated with a bug can be annotated like @group bug42. Running phpunit --group bug42 AllTests.php will run only the tests associated with bug #42, regardless of what files and groups those test methods span.
PHPUnit 3.2 is looking great.
]]>The only problem with Autotest is that it is specific to Ruby. At work, I do a mix of different kinds of programming including Ruby, PHP, Python, and C. I’d like my TDD to be accelerated for all of these languages.
Thanks to Geoffrey Grossenbach, last week I came across stakeout.rb from Mike Clark. This is a tiny, dead simple Ruby script that runs an arbitrary command when certain files change. This is a stripped-down Autotest for everybody else. I’m sure it has all kinds of other uses as well.
To get started testing with stakeout.rb, you’ll need Ruby installed. Any recent version is fine and you might already have it installed. Next, grab the stakeout.rb script and add the shebang line to the top (Unix-like OS assumed):
#!/usr/bin/env ruby -w if ARGV.size < 2 puts "Usage: stakeout.rb <command> [files to watch]+" ...
Make the file executable and put it somewhere in your PATH. You can test it out by typing stakeout.rb from an arbitrary directory and you should see the help message.
Next, change over to a project directory where you have some test files. Most of the projects that I am involved with tend to use some directory structure similar to this:
/project_name /lib /test ...
To test such a project, run stakeout.rb from the /project_name directory. Most PHP projects using PHPUnit tend to have an AllTests.php file or equivalent to run all the tests, so we’ll assume this for the example:
project_name$ stakeout.rb "php test/AllTests.php" **/*
The first argument is what command to run when the tests change. The second argument, and any subsequent arguments, are the files to watch for changes. These can use a Ruby globbing pattern. The pattern **/* will watch all files under project_name recursively, which includes lib/ and test/.
Once stakeout.rb is run, it will show no output but will sit and wait for changes. As soon as you change a watched file, stakeout.rb will automatically rerun you tests and will continue to do so until you exit with Control-C.
$res = ldap_connect($host, $port); if (! $res) { // error logging return false; }
There are two code paths shown above: the connection succeeding, and it failing. Both of them are very difficult to test because of the coupling to the global function ldap_connect() provided by the LDAP extension.
To make it succeed, you’d need an LDAP server. Causing it to fail is easier but the could take a very long time until the connection timeout occurs. Also, the code can’t be tested at all without the LDAP extension. All of these problems are unacceptable.
The solution is to use to the extension through an object instead of calling the extension function directly. This way, we can inject either the extension wrapper or a mock object for testing.
However, writing these wrappers and maintaining them can be a pain and this is often the rationale given for not using them. There’s an easy answer to this excuse:
class ExtensionProxy { protected $ext; public function __construct($ext) { $this->ext = $ext; } public function __call($method, $args) { return call_user_func_array("{$this->ext}_{$method}", $args); } }
Since most PHP extensions prefix all of their functions with the name followed by an underscore, it’s easy to wrap them with something like the class above.
There’s some performance penalty from call_user_func_array() in the above example but you can always write out a class later if that ever actually becomes a problem. Meanwhile, it can get you going very going quickly.
Our connection example then simply becomes:
$ldap = new ExtensionProxy('ldap'); ... $res = $ldap->connect($host, $port); if (! $res) { // error logging return false; }
The difference in usage is trivial but this version is easily testable. It now depends only on an $ldap instance, which the class needing LDAP can receive in its constructor. To test, now just pass a mock object for $ldap.
The technique of putting lightweight wrappers around PHP extension functions has been around for a long time. For example, Horde has a small wrapper around the IMAP extension for testing.
The continued improvements in PHP 5 allow for simple tricks like the ExtensionProxy above, and advances in tools like PHPUnit are making tests increasingly convenient and practical.
Whatever methods you choose, there really is no excuse for untested (or untestable) PHP code these days. I consider anything without good tests to be broken and you should also.
]]>