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 workingwithout manually testing or worse, just hoping it works.
So now that we’ve touched on the why, let’s talk about how.
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.
I have just made my first release to GitHub, majaxPheanstalkPlugin for symfony 1.4.x (possibly will work on older ones). It’s a set of tools that help to access Beanstalkd and make building workers much simpler.
The goal is to provide you with simple tools for managing your Beanstalkd integration. Right now it’s just worker thread tools and a simple factory, but I hope to grow it into a more complete library as time goes on.
We’re going to step things up a notch with this one, so hang on for the ride, because for those of you not already well versed in symfony and jQuery, there will be a lot of new information, and it will go pretty quickly, but I will try to make sure to keep everyone caught up. This is another post I hinted towards back when I first wrote about symfony widgets. Right, so, on to the fun!
First things first, let’s talk about requirements. My only real requirement is that it must be seamless. We must be able to only implement the widget, and have everything else work exactly as intended. For a date widget, this means when the user clicks that submit button, an array with ‘month’, ‘day’, and ‘year’ must be sent back. Not a string, not JSON, just that basic array.
It’s for the above reason that we will need to make a widget on top of the basic jQuery UI Datepicker Widget. The Datepicker alone would just return a string when we submitted the form, which would then require at least a custom validator, if not much more. So to simplify things later, we’ll put in more effort now.
There are four things we will be doing in this tutorial to make this happen. Here’s a quick overview:
Set up a new module for us to use with the widget tutorials
Create our widget class
Create our jQuery UI widget that goes along with our widget class
1. Setup majaxJqueryPlugin
We’re using my plugin for two reasons:
It’s my blog, so of course, I’m using my plugin
We need jQuery UI, both the JavaScript and the CSS, and majaxJqueryPlugin provides that right away
Setting it up is easy:
cd plugins
wget-O majaxJqueryPlugin.zip http://jmather.com/wp-content/plugins/download-monitor/download.php?id=4
Now we add the plugin to our Project Configuration. Open up your config/ProjectConfiguration.class.php file, and place this line somewhere within your setup() function:
$this->enablePlugins('majaxJqueryPlugin');
And one last step, we need to publish the assets:
./symfony plugin:publish-assets
Alright, now we’re ready to move on to the good stuff.
2. Setting up our plugin
Now we need to make a new plugin, and set up a few directories for future use. It’s pretty self-explanatory so we’ll cut right to the code, and come back afterwards to talk about some of the extras.
We made our plugin, then we made the directory our jQuery plugins will go in, and the directory where we will store our widgets. The -p flag means “no error if existing, make parent directories as needed” which saved us a few mkdir commands.
3. Making our symfony widget
Now open up plugins/majaxWidgetPlugin/lib/widget/majaxWidgetFormDate.class.php and here’s our code:
So, to start out, we extend sfWidgetFormDate, as that will make the controls we need to manage a date submission. The only change we need to do, is to override the render function, which will let us add some custom code around the standard output, to allow us to more easily control the display.
The first thing we do in the render function is to include the required JavaScript. Some will say the widget shouldn’t know about the JavaScript, but I say, if the widget shouldn’t know… who should? We want to make this as transparent as possible, so why should we have to also remember to add a JavaScript include to use this widget?
Next, we grab our generated ID, and figure out our options (i.e. can the date be null) that we can pass along to Datepicker. After that we wrap the whole deal in a containing div, and then wrap the native controls in another specific div. You will see why we did this in a minute. Lastly, we write the JavaScript to activate the widget.
4. Making our jQuery Plugin
As is the standard, I’ll show you the code, then highlight portions that are interesting. This file goes in plugins/majaxWidgetPlugin/web/js/jquery.majax.dateselector.js
You know, looking over, it’s pretty clear, I feel, what most parts do. The functions “_hide_real_ctrls”, and “_show_real_ctrls” hide and show that wrapping div we built around the original controls, so we can keep them in play, but not have to worry about controlling them. The “_build_facade” function builds our fake interactive Datepicker object, and optionally our ‘Clear’ button, if our date is allowed to be empty. Functions “_clear_display” and “_update_ctrls” to exactly as you would expect. Cure functions “_create” and “destroy” are from the jQuery UI Widget framework, and are called … can you guess when? 🙂
We’re done!
Once you’re done, you can replace any sfWidgetFormDate instance with a majaxWidgetFormDate instance, and everything else is handled. A little bit of effort up front, and many rewards down the road!
In part 1 we explored why exactly we may want to build our own jQuery plugin, so now in part two, we will cover exactly how to do so.
Note: these examples are through a bash command line, as I am fairly certain that is the most common way one interacts with Symfony. If you’re using Windows, you’ll get the gist of what to do, however.
Let’s start with initializing the plugin:
./symfony generate:plugin majaxJqueryPlugin
Now we will switch to our plugin directory:
cd plugins/majaxJqueryPlugin
We’re going to be providing web resources, so we will want to make web, js, and css directories. We will also make a temp directory to download files to:
mkdir web
mkdir web/js
mkdir web/css
mkdir temp
Prepare a place to download what we need:
cd temp
Here is our download and copy code. I have decided to host it from here, as jQuery UI doesn’t seem to provide direct download links.
Now for the fun part, to make it work. Since I like things to ‘just work’, here’s how I will do it. I will take a page from jQuery Reloaded and use a helper to actually load the files into the response, but instead of making people manually add it where they want it, we will just automatically shove it in if the plugin is enabled!
It’s time to make our helper directory:
mkdir lib/helper
Here is the contents of lib/helper/MajaxjQueryHelper.php:
For the last little trick before we enable the plugin, open up config/majaxJqueryPluginConfiguration.class.php and add the following code to the initialize() function:
You’re done! Now you just have to go back to your project root, and edit your project configuration to add the plugin, and publish it’s assets.
Here is the line to add to your ProjectConfiguration’s setup() function, just in case you need it:
$this->enablePlugins('majaxJqueryPlugin');
To publish it’s assets:
./symfony plugin:publish
And now you’re ready to use jQuery throughout your Symfony project!
To download a copy of the majaxJqueryPlugin I have made (and save yourself some work!), use the link below, and you just have to follow the last step to enable the plugin in your project: