So, in the never ending endeavor to make things as easy as possible for people to work with, I have a new mini project (actually two!).
majaxInstallerPlugin for symfony (only tested on 1.4.x)
MajaxInstaller for everyone else!
MajaxInstaller takes an XML configuration and prompts users interactive questions about the files you describe, to get easy configuration details.
The symfony plugin is just designed to be better integrated into symfony, accepting a yaml config file, and adding a majax:install task
There are no phpunit tests yet in the repository for MajaxInstaller, though I have one or two in majaxInstallerPlugin that actually test the base framework.
I look forward to hearing what people think!
Today we’re going to talk about something I know next to nothing about, but am working on.
Recently I came to the realization that it is time to “Code Up” and learn how to be proficient in building tests, and writing testable code. Testing is a discipline that you will be able to cross code boundaries, something you will be able to carry with you to any language, once you pick up the mindset. More importantly than that, testing allows you to verify your code is working without manually testing or worse, just hoping it works.
So now that we’ve touched on the why, let’s talk about how.
If you’re going to do it, do it right!
PHPUnit (by Sebastian Bergmann) is quite literally the gold standard of testing in PHP. It probably helps that Sebastian has taken code metrics in PHP from zero to hero. So it stands to reason that PHPUnit is where you start to look.
A lot of what I write are plugins, because I use things here, there, and everywhere. So one of the most important things to me, was that whatever I used had at least some plugin support. The two plugins that fit what I was looking for closely enough are sfPHPUnitPlugin and sfPHPUnit2Plugin. I chose sfPHPUnit2Plugin simply because I saw it had a github repository, which meant that contributing would be easy, and worst case scenario, keeping my customizations up to date with the core would be a snap.
Note: Just to clarify, both sfPHPUnitPlugin and sfPHPUnit2Plugin work with PHPUnit 3.5.x.
Long story short, I made a pull request on March 11th to add support for plugins holding on to phpunit tests, along with some other goodies. A little while later, it was incorporated! Hurrah! Power to the people.
Anyway! For the purpose of this, we’re going to assume you’re using sfPHPUnit2Plugin, as that’s what I use. It’s easy. Install the plugin. Run ./symfony phpunit:generate-config, and then run phpunit. If you already have a phpunit.xml file in your root, copy phpunit.xml.dist over it, as it will be updated with the latest path set.
To make your first unit test, it’s easiest to just use the plugin’s tasks to build them, and work from there. This is a simple deal!
./symfony phpunit:generate-unit MyTestName
That will set up the phpunit basics, and put your unit test in test/phpunit/unit/MyTestNameTest.php. See? EASY!
To run your test, either just run ‘phpunit’, or run ‘phpunit test/phpunit/unit/MyTestNameTest.php’. EASY!
Now you know HOW to test, so let’s look at WHAT to test. Because you can test everything but if you’re not doing it in a way that makes sense, you’re just going to get frustrated, and give up. Or worse, you won’t realize you’re testing the wrong things until disaster strikes.
JMather’s Novice Testing Rules
1. Don’t test things that are already being tested
Doctrine and Propel both have tons of tests to ensure they’re working. You don’t have to confirm that when you set something, it gets set, unless you are overriding the setter. You don’t have to confirm that when you save, it is added to the database unless you are testing a database connection.
2. Test functionality, not objects
The most important thing to test is your business logic. Not every line deserves a test. Focus on complex business logic. Though, likely, you will end up testing objects if you build them to only contain specific domain logic, but short of that, test what matters. It’s ten times better to have 50% code coverage testing all the really important logic than to have 20% code coverage because you’re writing massive amounts of silly tests making sure that a + b = c.
3. Just start testing
Your first tests will be bad. No, scratch that, they will be horrible. Why? Because you’ll be testing code that wasn’t written to be tested. Want an example? Look at this little ditty from majaxDoctrineMediaPlugin. We’ll do two clips of the same function, before and after.
And then the “work in progress”…
The first thing you will notice is that the new one is ~100 lines, as opposed to ~250. It is still quite long, but it has come a long way. The second is that it has been moved into it’s own class. Why? So we can test it! All of the referenced builders (i.e. $this->path_builder, $this->filename_builder and the like) can be swapped out for mock objects with predictable behaviors so we can test this function in near isolation. Isolation is important because it limits your test’s culpability for outside interference itself. Over time all of your pieces will be validated by tests, so you can ensure the entire system is “correct.”
Take, for example, the majaxMediaFilenameBuilder. I tested it! Why? To make sure I can trust it! It should work as expected. Along the way to ensuring it worked as expected, I realized while the code worked fine, the implementation was hosed.
Now, why did I make it a separate class? It’s just a filename you say? HAH! Fat chance! For many instances, sure, it’s just a file name. But what if you wanted to make the filename harder to guess? Well, if that part of the code wasn’t replaceable, you would be out of luck. Now you just have to extend majaxMediaFilenameBuilder, override render() to return md5(parent::render(args)).’.’.$extension; and you’re golden! Don’t you just love OOP?
3. Just keep working at it
1% code coverage becomes 5%, which becomes 10%, 20%, 40%, 80%. Soon enough, you’ll find yourself checking your tests to make sure you haven’t mucked anything up, and that’s when you’ll get it. That’s when it will really hit home. It found a show-stopping bug you wouldn’t have noticed in some other part of the system that was not really related at all to what you were working on. It saved your bacon.
And that’s when it gets real.
Alright, I understand that in order to keep people checking back regularly, I have to post regularly.
I get that.
I really do!
Sometimes, though, life just takes over. I’m working on making that better.
To that end: Did anyone else attend the Day Camp 4 Developers session last weekend? If not, you missed a real riot. The speakers were excellent, and the crowd in IRC was just awesome.
Here’s just a quick note for those of you working with caching solutions for dynamic data in Symfony.
Update: If testability is a concern, please read the follow up to this article.
One of the challenges with caching is deciding where the limit is. What’s worth caching, and what isn’t. Because cleaning up that old data is hard… isn’t it?
Not so much!
You just need to make a helper and a checks, and you’ll be expertly picking out the templates to invalidate!
The first hurdle with clearing out invalid data, is that usually your backend is separate from the frontend, so it’s a non-trivial reach to try and invalidate cache files of other applications. Or is it?
public static function clearCachePattern($pattern)
$envs = array('prod', 'dev');
$apps = array('backend', 'frontend');
foreach($envs as $env)
foreach($apps as $app)
$app_cache_dir = sfConfig::get('sf_cache_dir').DIRECTORY_SEPARATOR.$app. DIRECTORY_SEPARATOR.$env.DIRECTORY_SEPARATOR.'template';
$cache_vars = array(
'cache_dir' => $app_cache_dir,
'cache_key_use_vary_headers' => true,
'cache_key_use_host_name' => true,
$cache = new sfFileCache($cache_vars);
Your $cache_vars will differ depending on your cache settings, but essentially, that’s your main helper setup.
Now here’s what I use in my objects for cache clear detection:
class MyObject extends Doctrine_Record
private $pendingClearCacheEntries = false;
public function preSave($event)
$this->pendingClearCacheEntries = true;
public function postSave($event)
$this->pendingClearCacheEntries = false;
public function clearCacheEntries()
Of course, your patterns will change depending on your cache options (I.e. I use the **/** because I have enabled cache_key_use_host_name, other settings may not accept this pattern.)
Happy caching, and if you have any questions, feel free to let me know!
Quite a while back, SEO blog SEO 2.0 had a post entitled “Hey Freelancer: Are You a Worker or an Entrepreneur?” which really made me think.
Back when I was freelancing as my sole source of income, I was so caught up in being a worker, that I didn’t even realize I wasn’t working towards being an entrepreneur. This article woke me up a bit… too bad it was many months too late!
So how about the rest of you out there? Are you worker bees, or are you entrepreneurs?