dagfinn | 10 June, 2008 13:40
I came across a Zend Framework (ZF) example I wanted to refactor. You really have to have unit test coverage to refactor effectively, and since there were no tests, I started trying to find out how to test it. There didn't seem to be a wealth of information available on the web, so I've tried to figure it out by myself.
Several challenges presented themselves. It might seem as if the ZF controller system is not optimally designed for testing action controllers, but there are always ways to get around obstacles.
A ZF action controller (which is really just a group of actions collected in a class) always extends Zend_Controller_Action. (For an introduction, see the official QuickStart).
The one I was playing with was an authentication controller:
class AuthController extends Zend_Controller_Action...
That's all we need to know about the specific class under test for now.
I always like to set up the basic objects for the test as instance variables in the setUp() method, so that I can write many small test methods exercising them in various ways. To even get started testing an action controller, we need to instantiate it outside its usual environment, the Zend Front Controller. It requires a request object and a response object:
class AuthControllerTest extends UnitTestCase { function setUp() { $this->request = new Zend_Controller_Request_Http(); $this->response = new Zend_Controller_Response_Cli(); $this->controller = new AuthController($this->request,$this->response); }
(I'm using SimpleTest here, but the same techniques should apply to PHPUnit with different syntax.)
The challenge increases increase when we want to use View Helpers, such as the flash messenger. When programming the action controller, it looks like this:
$flashMessenger = $this->_helper->FlashMessenger;
To test this meaningfully, we need to replace the flash messenger with a mock object. That means also having to replace $this->_helper, which is a "helper broker" object.
First, we generate the mock classes:
Mock::generate('Zend_Controller_Action_HelperBroker','MockHelperBroker'); Mock::generate('Zend_Controller_Action_Helper_FlashMessenger','MockFlashMessenger');
Unfortunately, the helper broker is a protected instance variable, with no apparent way to change it. But it's easy to fix by adding a setter method to the action controller class:
class AuthController extends Zend_Controller_Action... public function setHelperBroker($helperBroker) { $this->_helper = $helperBroker; }
This allows us to set up the mock helper broker and let it return the flash messenger:
$this->helperBroker = new MockHelperBroker; $this->controller->setHelperBroker($this->helperBroker); $this->flashMessenger = new MockFlashMessenger; $this->helperBroker->setReturnValue( '__get',$this->flashMessenger,array('FlashMessenger'));
Now we can use the mock objects to test an action which is supposed to send a flash message:
function testIdentifyActionSendsFlashMessage() { $this->flashMessenger->expectOnce( 'addMessage', array('Please provide a username and password.')); $this->controller->identifyAction(); }
That's as far as I'm going with this now. There may be more later.
dagfinn | 02 June, 2008 21:12
Redirects are useful in web programming, especially when implementing the Post-Redrect-Get pattern. But there is a problem with redirects: there is no simple way to send a message to the user across the redirect. When processing a GET request, you can display whatever messages you want. The most simplistic way is to echo them directly; or if just slightly more sophisticated, set it in the template that's about to become the web page. When processing a POST request that is to be followed by a redirect, you can't do that. The response (redirect) sent back to the browser does not have any text or HTML content. In practice, it just contains the URL of the page you're redirecting to. If you try echoing the message, it will cause the redirect to fail because you sent content before the header that signals redirect.
So how to get the message displayed? The simplest way to do it would be to include it in the URL:
header('Location: http://www.example.com/index.php?message='.urlencode($message));
It's possible, but your URLs can get very long and you might find it silly:
http://www.example.com/index.php?message=This+is+a+message+to+demonstrate+how+long+a+URL+can+get+when+you+put+long+strings+into+it
The more common choice is to keep the message in session and make sure it's removed from session after it's been displayed. That's what flash messages do. In military terminology, a flash message is defined as:
A category of precedence reserved for initial enemy contact messages or operational combat messages of extreme urgency.
I don't like a name that could be confused with Adobe Flash, so when I implemented my version of this, I looked up "flash" in the thesaurus and decided to call them flares instead.
Here are a couple of examples I've picked up.
Zend Framework:
$flashMessenger = $this->_helper->FlashMessenger; $flashMessenger->setNamespace('actionErrors'); $flashMessenger->addMessage($message);
CakePHP (from http://labs.iamkoa.net/2008/01/13/session-based-flash-messages-look-better-cakephp/):
$this->Session->setFlash('Your post has been saved.'); $this->redirect('/news/index');
In my own work, I've done it in a slightly different way by letting the redirect object hande the flash/flare and returning the redirect object from the action method, somewhat like this:
return Redirect::toAction('settings')->addFlare('Settings changed');
I don't necessarily claim that it's better than the others, but it makes sense to have alternatives.
This article was originally posted with an incorrect timestamp, and has been updated to the current time as of June 2.
dagfinn | 02 June, 2008 12:46
I keep saying that sort of thing, too. I also say it raises your IQ. But when I attended a presentation by Uncle Bob (Robert C. Martin) last summer, I was mildly shocked to see that he described TDD as tedious (but a requirement for professionalism). I confronted him politely about it, emphasizing that above all I find TDD relaxing. Why? Because bugs bug me. I get stressed when I have to search for a bug and even more so when it takes much longer than expected (I'm sure that never happens to you). With TDD, bugs are always small and easy to locate.
Uncle Bob seemed to agree with me as far as his own experience was concerned, but he believed that many developers found the non-TDD process exciting rather than stressful.
Sounds weird to me. But I know from long experience that sometimes people are different from me in ways that make reality seem stranger than fiction.
dagfinn | 11 May, 2008 00:04
I'm currently working about equally in PHP and Java. I can't say I've fallen in love with Java. But Java does have a feature or two that would be useful in PHP. One of them is the Enum (enumeration, that is), which is traditional in some languages and DBMSes (including MySQL) and was introduced in Java 1.5 (or is that 5.0? I'm sure they do that just to expose people like me as Java amateurs).
Enumerations are useful when a variable can have one of a given number of values. Actually useful examples I've encountered in web programming are states or stages in a process and user roles. Another kind of example is one I used in PHP In Action: an authorization system with three fixed roles or categories of user: regular, webmaster and administrator.
If we represent the roles as text strings, we risk getting our tests wrong:if ($role == 'amdinistrator')...
The only problem is that the word “administrator” is misspelled, so the test won't work.
This can be solve by representing the values with named constanst instead. Using class constants in PHP 5:class Role const REGULAR = 1; const WEBMASTER = 2; const ADMINISTRATOR = 3; ...
Now we can do this instead:
We won't get away with any misspellings here; using an undefined class constant is a fatal error. Compared to global constants, this may be easier to figure out, not least because we know where the constant is defined (inside the Role class) just by looking at it.
This is called the int Enum pattern in the official description of Java Enums. The documentation also lists some problems with this, starting with type safety, which we're not that concerned with in PHP anyway. A more relevant problem is the fact that when you print the value, you just get the number.
But I don't see why you shouldn't use strings for the values:class Role const REGULAR = 'REGULAR'; const WEBMASTER = 'WEBMASTER'; const ADMINISTRATOR = 'ADMINISTRATOR'; ...
One PHP-specific problem I didn't mention in the book is the problem that happens with long class names. Since we still don't officially have namespaces in PHP, we will easily end up with nauseatingly long constant names, like this:
It's quite depressing to have to do that a lot. I've tried putting constants in their own class with a shorter name, but I didn't like it much. One thing I tried recently was creating instance methods to return the constant value:
class MyProject_Authorization_RBAC_Role... public function REGULAR { return self::REGULAR } public function WEBMASTER { return self::WEBMASTER } public function ADMINISTRATOR { return self::ADMINISTRATOR }
Now we can create an instance with a short name and get the values from that:
$roles = new MyProject_Authorization_RBAC_Role; if ($role == $roles->ADMINISTRATOR())...
All of this just to get shorter names? I little desperate perhaps. So yes, I would like Enums in PHP.
dagfinn | 02 May, 2008 23:05
Everybody who writes object-oriented code knows about constructors. You need them so the program knows how to instantiate objects, right? And you especially need them when a lot of things have to be done while instantiating an object. And personally, I've never considered visibility restrictions important enough to be a major argument against those languages that have lacked them (PHP 4). So why would I be skeptical of public constructors?
I got the idea after reading Joshua Kerievsky's book Refactoring to Patterns. One of his refactorings is called Replace Constructors with Creation Methods. In Java, unlike PHP, you can have multiple constructors that are distinguished only by the number and type of arguments. That may be practical sometimes, but as Kerievsky's example shows, it be more readable to have creation methods with different names instead. Which is what you have to anyway in PHP.
But why not take this one step further and do it even when there is only one constructor? For example, here's an ultra-basic Redirect class:
class Redirect { private $url; public function __construct($url) { $this->url = $url; } public function execute() { header("Location: ".$this->url); exit; } }
This may seem simplistic, but it is the kind of class I might actually use at an early stage in the development of a web application. Anyway, we can apply what is essentially the same process as Kerievsky's by adding a creation method whose name is somewhat more telling than __construct().
class Redirect { private $url; public function __construct($url) { $this->url = $url; } public static function withUrl($url) { return new self($url); } public function execute() { header("Location: ".$this->url); exit; } }
Now all that remains is to make the constructor private.
private function __construct($url) { $this->url = $url; }
Is this an improvement? The additional code might be considered unnecessary complexity, or in a word: clutter. On the other hand, our client code might seem more readable when it looks like this:
$redirect = Redirect::withUrl("http://www.example.com/index.php"); $redirect->execute();
Instead of:
$redirect = new Redirect("http://www.example.com/index.php"); $redirect->execute();
You could object that it's fairly obvious anyway in this example that we're inputting a URL. But if the URL string is supplied as a variable or method call instead of a plain string, it would be less obvious.
No doubt there are many cases in which a plain constructor is the best choice, but I think this is worth considering.
dagfinn | 06 April, 2008 07:21
Never. Always. Start dirt simple and refactor to eliminate duplication. When you need a specific feature, study how it's done in existing frameworks and implement it. That will get you a "framework" that has exactly what you need.
This is an approach that is facilitated by the immediacy of PHP. Is this approach slower, long-term, than adopting a framework? That probably depends on a lot of variables. If an existing framework does everything your application
needs, it may be a great idea to use it. On the other hand, if you need to do things that are significantly different from what the framework supports, you need to start mucking about with the framework itself. In that case, it may be more
effective to have you own "framework" that fits the application like a glove, without a lot of features you will never need, and without features that are almost, but not quite what you need.
I love Ruby, but I've never actually tried Rails. Reading Rails tutorials, I'm not quite as impressed as some folks I know. Rails certainly represents the state of the art in web programming. So do the leading PHP frameworks, some of which are heavily inspired by Rails. The difference is not that striking. But I don't want the state of the art, I want the web framework of the future. And can that be built on the existing frameworks, or do we need to start from scratch? There are some interesting possible beginnings out there, such as Phaux.
dagfinn | 28 January, 2008 13:53
I follow the principle that you should test everything that could possibly fail. And I would like to have unit tests for everything; tests that exercise each small behavior in isolation. The components that are easiest to unit test are typically class and methods in plain object-oriented code. Not everything falls into that category, though. Page templates, XSLT and other XML files are some counterexamples.
As I mentioned in my blog post on Paparrazzi testing, Uncle Bob (Robert C. Martin) has discussed how to test web templates or server pages. Since I'm currently working with Smarty templates, I wanted a simple way to run tests on them without needing to deal with a web server and the page navigation in a full web application.
Simpletest's web tester has a simplified web browser that's capable of running HTTP requests to test an actual running web application. To test a Smarty template in isolation, we can replace it with something even simpler that talks directly to the Smarty template engine instead of the web server. It's this simple:
class SmartyBrowser {
private $smarty;
private $template;
function __construct($smarty,$template) {
$this->smarty = $smarty;
$this->template = $template;
}
function getContent() {
return $this->smarty->fetch($this->template);
}
}
getContent() can replace the getContent() method of Simpletest's built-in browser (SimpleBrowser), enabling SimpleTest to run all the usual assertions on the generated HTML page. To make this happen we replace the SimpleBrowser instance with the SmartyBrowser: <?php
class SomeTemplateTest extends UnitTestCase {
function setUp() {
$this->smarty = new Smarty;
$this->_browser = new SmartyBrowser($smarty,'page.tpl');
}
Also, we need the usual Smarty directory setup.
Now we can test Smarty output. What would be really interesting, but much more complex, would be to extend the concept to actually testing two-way interaction between the server / template and the client / browser.
dagfinn | 09 October, 2007 02:15
My conclusion so far is that Uncle Bob's articles is an example of what we may call the paparazzi principle.
Paparazzi try get as close as possible to celebrities they prey upon. The fewer walls or windows between the camera and the victim, the better. Pictures should be as revealing as possible; naked is or half-naked is good.
The paparrazzi principle for web testing is this: run the test from “above” as close as possible to the component you want to test. Inject mock objects from “below” as close as possible to the component you want to test. Even in ordinary unit testing, this can be far from trivial. Where the web interface itself is involved, it is much harder.
It follows that you don't want to use a real web browser or web server unless you must. It also follows that a realtively simplistic web client (SimpleTest's web browser is a good example) without a user interface is preferable to a web browser for human use. Unless you absolutely need it, that is. Therefore, Selenium is probably a step in the wrong direction. I want test tools that enable me to test user interaction without having to fire up these voluminous masses of software.
dagfinn | 03 August, 2007 13:44
I've been using mock objects when testing code that uses files and other stuff that's hard to test because there are too many effects on the code's surroundings and you have to test . I've been doing that on an ad hoc basis. Now Mike Naberezny has an interesting generalized class for wrapping PHP modules to make it possible to replace them with mocks.
That solves some of those challenges, but there are some functions that are not amenable to that approach. exit() is one of them. Let's try using anonymous functions. exit() is a particularly hot candidate, since it plays havoc with tests. If the code under test exits before the test run is finished, it's like hitting the poor test framework over the head with a brick. It's out cold and has no chance to report the results from the test.
An anonymous function to replace exit() can be created with create_function(), as in the constructor of this class:
<?php
class Suicidal {
private $exitfunc;
function __construct($exitfunc) {
$this->exitfunc = $exitfunc ? $exitfunc
: create_function('','exit();');
}
function goodbyeCruelWorld() {
call_user_func($this->exitfunc);
}
}
The goodbyeCruelWorld() method exits nicely when it's run. But we have a back door that allows us to do something else. We can pass a different anonymous function in the constructor, as in this test case:
class ExitTest extends UnitTestCase {
function setUp() {
$GLOBALS["exit_called"] = FALSE;
}
function testMockExit() {
$exitfunc = create_function(
'','$GLOBALS["exit_called"] = TRUE;');
$suicidal = new Suicidal($exitfunc);
$suicidal->goodbyeCruelWorld();
$this->assertTrue($GLOBALS["exit_called"]);
}
}
We're replacing the exit function with one that just sets a global variable that lets us check that the function has actually been called. The pattern we're using here is an alternative to a mock object, called a Test Spy.
Globals are often frowned upon, and for good reasons, but in this context, they are perfectly innocuous. It's a small restricted test environment; the risk of getting lost and not knowing which variable was set where is negligible.
We just have to be careful to avoid interference between tests. The way to do that is to reset all globals appropriately in setUp() and/or tearDown(). By the way, this applies equally to superglobals such as $_GET and $_POST.
dagfinn | 25 July, 2007 10:38
Again a somewhat belated announcement: PHP in Action is in print. My copies of it arrived at my doorstep while I was in Crete, with no clear indication that would have allowed my brother, who was hovering helpfully in the vicinity, to guess what it was. I guessed it, though.
But DHL couldn't give any definite answers, and their Norwegian branch have a menu on they're telephone answering machine that gives a whole new dimension to the concept of a poor user interface. My colleague had a worse time, though. He was the victim of identity theft. His shipment of Java books, also delivered by DHL, was hijacked by someone who forged his signature.
Oh, my dear, I've just had a book published, and here I am chatting about everything else. Strenuously objective as always, I have to say that it's a pretty good book.
I've had to read it about fifteen times already, and I still sort of enjoyed it the last time.
dagfinn | 15 July, 2007 01:24
In a blog post by Paul M. Jones, he quotes comments from slashdot claiming that TDD leads to "ravioli code":
The problem is that it [Ravioli Code] tends to lead to functions (methods, etc.) without true coherence, and it often leaves the code to implement even something fairly simple scattered over a very large number of functions.
The problem here is the phrase "tends to". Any reasonable comparison of test-first with test-later has to be between code examples written by the same programmer using the two approaches. Those who write messy code might well continue to do so even with TDD, but in a slightly different way. Even though I do believe TDD helps, it's not "the cure for all diseases".
There is another point which these commenters have missed: when you write the tests first, they are useful to you right from the start. Test-later means something like: write code for a while, then run an ad-hoc test. At that point, you've probably created two or more bugs, and you have to spend time figuring out which is which and where they're located. After you've fixed the bugs, you write some more code, and then you run another ad-hoc test, perhaps re-implementing the code you needed to do the first ad-hoc test. With TDD, you can run tests over and over; you get immediate feedback about bugs and you can fix them quickly
The diagram, which I used in the book, illustrates my point. First, there is TDD, using the tests from the start, then test-later development.
dagfinn | 14 July, 2007 14:15
I created my own test runner to run tests inside Vim, I can't remember how long ago. Must be a year or two. I never shared it with the world. It was too primitive for anyone else to use. Then, recently, I decided to try re-implementing it in a more serious way. That meant using a more sophisticated programming language. Vim script has seen some improvements and is pretty useful for simple tasks, but I want something that's object-oriented and has a test framework. That means Perl, Python or Ruby, all of which are available from inside Vim. So I've started doing it in Ruby.
It's not ready for public consumption yet, but I think it will be eventually. Here's what it looks like at the moment:
This is how I program about 90 per cent of the time: the test case and the class I'm testing side by side and a small extra window to show the test results. I can run the test by pressing F11 (which just happened to be the key that was available at the time). I work test-first, so before implementing something on the left, I have to write another test on the right. It's less tempting to start implementing without tests when there's an easy way to run the tests.
dagfinn | 23 June, 2007 03:03
Manning Publications has released the book PHP In Action as ebook in PDF format. I wrote it with help from my co-authors Marcus Baker and Chris Shiflett. The print version is due out June 30.
A few of you might be yawning, shaking your heads, rolling your eyes, saying this is old news. It's actually 11 days old. The reason I haven't announced it on my blog before is that I've been moving my blog to a professional web hosting company and trying to make sure sure people can comment.
dagfinn | 25 February, 2007 10:11
I went to the UK PHP conference, and now finally I get to blog about it. There was actually a "blogathon" following the conference, but I had to leave early to get the blissful experience of returning to snowed-down Norway half asleep in the middle of the night.
Where are all the other blog posts about the conference? I don't know; I guess I'll have to Google for them in the coming weeks. The organizers should have put up some space for links on the conference web site. And strongly encourage people to use it. Last year's PHP Vikinger unconference had a blog of its own, but there's practically no post-conference material there.
Anyway, that's all I have to complain about. The conference itself was wonderful. All the speakers had excellent content and presentation. And everything was about subjects I'm currently interested in.
I'll have to come back later to what the speakers talked about. Lots of interesting points to discuss. It could take me months to finish processing all that.
dagfinn | 09 February, 2007 15:31
This is embarrasing: I just deleted all the comments in this blog, and I didn't have a recent enough backup to recover them. I was deleting spam and pressed the delete button too fast.
There we'rent many comments, but I apologize to those who had commented. I promise not to do it again.
| « | July 2008 | » | ||||
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | 31 | ||