We've been looking at unit testing for WordPress development. Through the use of practical examples, we've reviewed what unit testing looks like for both plugins and for themes; however, we haven't really talked about the the theory behind unit testing.
That is to say that we've not taken a high-level look at what it is, why it's beneficial, and how we can incorporate it into our workflow. In the next series of articles, we're going to define unit testing, cover its key benefits, why we should bother doing it, and some of the resources that are available for us to use in our work.
Although there's no code to work through for this particular tutorial, it should provide a solid foundation on which the practical application of the previous articles rely.
On Unit Testing Themes and Plugins
Before diving into the article, I'd think it's important to consider the reasons why one should bother testing themes and plugins at all.
Up until recently, WordPress has been seen primarily as a blogging platform and as a content management system. It does both of these very well, but as developers are discovering its rich API, it's becoming more popular to build certain types of applications using WordPress as the underlying platform.
Additionally, themes are more than skins for a website – they are more than a stylesheet and a few template files for displaying data. In fact, now, perhaps more than ever, entire teams are dedicating time (and even making a living off of) producing themes for WordPress. So don't let the word "theme" misguide you – themes are like applications for WordPress. They add functionality, they pre-process and post-process data, and often introduce significant functionality into WordPress far beyond simply giving your website a facelift.
Finally, plugins are almost more application-like in nature than themes. Consider it this way: plugins extend the core of WordPress and they often work independently from themes. Instead, they add brand new features to the core of WordPress from which all themes may benefit.
I say all of this to give some perspective as to why unit testing – as we're about to discover – can pay off many times over given that it's used in the context of more complex themes and plugins.
What Is Unit Testing?
We briefly touched on this in the first article in the series. I don't want to provide a total rehash of what's already been written, so I'll summarize the points here:
- It's the practice of making sure functional units of code work as expected
- It affords us the ability to find failures in our code prior to releasing it into the wild
- Code becomes more resilient against change because it's being continually tested
But that's just scratching the surface.
To truly understand the units of your theme or plugin, you have to think of what composes a theme. Typically, it's a combination of:
- Stylesheets that give the theme its presentation
- PHP that handles server-side processing
With that said, there are multiple ways to define a unit but I'd bet that most people define a unit as a function. For the most part, that's right, but it doesn't mean our functions are the most optimal units to test. For example, functions often consist of multiple blocks – perhaps loops, perhaps conditionals, or perhaps calls to other functions.
Whatever the case may be, there are often smaller units of functionality contained within methods.
So is it truly accurate to say that unit testing involves testing each function that makes up a theme? Not really. Since a single block of code – or, in some cases, a single statement – is responsible for achieving a specific task, these would make up true units of code.
And if we're supposed to write unit tests, how do we write tests for the units that exist within our functions?
More Tests, More Methods
Recall that earlier in the series, I said:
- Write your tests first, then run them (of course, they will fail)
- Write code to attempt to make the tests pass
- Run the tests. If they pass, you're good; otherwise, continue working on the function
This is where one of the greatest shifts in writing unit testable code occurs: you're breaking out units of functionality into the smallest atomic piece possible. Sure, this often results in an a high number of very small functions, but is that a bad thing?
In doing this, each function truly has a single purpose. And assuming that your function does a single thing, there are only so many ways to name the function which can ultimately result in much more readable code.
Of course, there is a trade off as you may actually be introducing a few more lines of code while writing these additional functions, but when you're working on a product with a team of other people that will be used by thousands of customers, you have to ask yourself if the additional code is worth the quality that you can gain in properly testing your work. Done right, your team should be able to more easily follow the code's logic and the product will have a level of quality that's often difficult to achieve without writing unit tests.
Units and Suites
One key thing to recognize about unit tests is that they aren't dependent on one another. A single unit test should test one and only one thing. Furthermore, this means that a unit test should really only include a single assertion (but there are exceptions that are demonstrated here).
Remember: The purpose of a unit test is to evaluate the quality of a single unit of code. Some units will accept arguments, some units will not, some units will return values, others will not. Whatever the case may be, your unit test should evaluate all cases. A single unit of code will rarely have a single test.
Instead, I'd say that a good rule of thumb is that the number of tests associated with a unit of code scales exponentially based on the number of inputs it accepts.
- If your unit accepts no arguments, there will likely be one test
- If your unit accepts one argument, there will likely be two tests – one for the expected argument and one for the unexpected
- If your unit accepts two arguments, there will likely be four tests – one for each combination of arguments: expected, expected; expected, unexpected; unexpected, expected; and unexpected, unexpected
Make sense? The key take away here is that you're going to have significantly more tests than there are units; there isn't a 1:1 ratio.
Finally, when performing unit testing, you'll often see the word "test suite" or "suite" used when discussing the tests. This may vary based on the context in which the testing is occurring; however, when working with WordPress, this usually refers to a collection of unit tests.
So let's say that you have a file that's responsible for testing all of the functionality around writing and publishing a post – this file would be the test suite for posts. Easy enough, right? It's important to understand this terminology should you come across this in further reading as it can differ slightly if you're working in, say, Rails or .NET.
Finally, unit testing is not a new methodology and it's not strictly limited to PHP. In fact, unit testing (or test driven development as a whole) has been around for well over a decade.
But its adoption has been mixed – some people live and die by it, others hate it, and then there are those of us that are looking to begin doing so but have to balance the practical application of bringing it into an existing project and understanding the amount of refactoring this may require.
As far as theory is concerned, we're just getting started.
In the next article, we'll take a look specifically at the key benefits of unit testing and how it can pay off in our WordPress-based development. We'll also examine the advantages that unit testing brings to architecture, design, and documentation.
Of course, unit testing is not without its disadvantages so we'll be looking at those, as well.