Writing Stupid Simple Unit Tests

In this post, we'll cover the basics of unit tests and walk through the process determining what types of tests you should write for your code. We'll also write a few tests to show just how simple most unit tests are.



The aim here is to make tests a bit more familiar and less daunting and mysterious. Unit tests are good. I'm by no means an expert on software testing. I'm just someone that's discovered the joy and freedom that writing tests provides.



If you are an expert on software testing and notice I'm wrong, please tell me!

Onward!

What are unit tests?

If you’re not familiar with unit tests, they’re essentially software written to test discrete units of functionality in a system.

Unit tests test the expected behavior of your software as it exists (or existed) at a specific point in time. They make sure that everything works as expected based on the rules established for the software at the time it was written.

If those rules change -- and your software along with them -- your tests will fail. When they fail, you'll gain insight into how the change affects the rest of the system.

For example, if a change is made in code for your site’s user profile management, you'll want to make sure that the change does not affect the behavior of your site’s payment processing code.

Tests can save time and money by acting as the first line of defense in catching problems before they result in a late night emergency or catastrophic failure. When things go wrong, they can also serve as the first response team. You can run your unit tests to see if any rules/expectations have been broken by some recent change, either in your code or some external API dependency, and fix the bug introduced by the change.

Sometimes the tests become outdated. Again, the tests will fail, letting you know that you’re no longer testing for the right thing. When this happens, you should update your tests to make sure they are in line with the new rules for your software.

Getting started

Unit tests are rarely difficult to write or run if you’re using a testing framework (e.g., phpunit).

You can write your tests before you write your business code if Test-driven Development (TDD) is your thing or you can write them after.

It doesn’t matter much when you write them as long as you have them.

The brilliant thing is that almost all tests you write will be stupid simple.

Tests usually assert some condition is true (or false, depending on what outcome you expect).

On top of that, most testing frameworks have convenience functions built in for performing these types of tests and several others, including testing the expected output of a function (i.e., what the function prints to the screen).

The three test functions we've used most frequently when writing tests for Largo are assertEquals, assertTrue and expectOutputString.

Our stupid simple example plugin

So, let’s say that we want to write tests for a WordPress plugin. Our plugin simply prints the message “Hello world!” in the top corner of the WordPress dashboard (sound familiar?).

This is the extent of the code:

<?php
/**
 * @package Hello_World
 * @version 0.1
 *
 * Plugin Name: Hello World!
 * Plugin URI: https://gist.github.com/rnagle/d561bd58504a644e9657
 * Description: Just a simple WordPress plugin that prints "Hello world!" in the top corner of the WordPress dashboard.
 * Author: Ryan Nagle
 * Version: 0.1
 **/

function hello_world() {
  return "Hello world!";
}

function hello_world_markup() {
	echo "<p id='hello-world'>" . hello_world() . "</p>";
}
add_action('admin_notices', 'hello_world_markup');

function hello_world_css() {
	$x = is_rtl() ? 'left' : 'right';

	echo "
	<style type='text/css'>
	#hello-world {
		float: $x;
		padding-$x: 15px;
		padding-top: 5px;
		margin: 0;
		font-size: 11px;
	}
	</style>
	";
}
add_action('admin_head', 'hello_world_css');

Couple of things to note:

  1. We have one function -- hello_world -- that returns a string.
  2. Two other functions -- hello_world_markup and hello_world_css -- that echo strings but have no return statements. This means we'll have to check the output of these functions rather than the return value to properly test them.
  3. Note that hello_world_markup relies on hello_world to provide the the “Hello world!” message.

So, how do we test this plugin? Well, if you don’t have your test framework installed and configured, you’ll want to get that ready and raring. The details are beyond the scope of what we'll cover here, but for an overview of setting up unit tests for WordPress plugins and themes, see Will Haynes’ Unit Testing Themes and Plugins in WordPress.

The INN Nerds also have a collection of deploy tools to make developing WordPress sites and writing tests much easier. You can read about using the deploy tools for testing themes and plugins here: Updates to INN’s Deploy Tools.

Disclaimer: getting unit tests working WordPress can be daunting at first. It’s a lot to digest and may take some time to set up if you’re coming to this cold, so be patient. It’ll be worth it.

If you’re really dedicated to the cause and need a hand, reach out to us.

How do I write a unit test?

With that out of the way, let’s design some unit tests for our “Hello World!” plugin.

The first thing we’ll want to do is enumerate the features of the code which we need to test.

There are many ways you can approach this. My preference is to create a single test file per file in my source code and have my tests directory mirror the structure of my source directory.

Each test file has tests for each function (or member functions of classes) in the corresponding source file.

So, the directory structure for our plugin would look like:

hello-world/
   hello-world.php
   phpunit.xml
   tests/
       bootstrap.php
       test-hello-world.php

We’ll be doing our work in test-hello-world.php where we’ll set up the skeleton of our test case, which is as simple as extending WP_UnitTestCase:

<?php

class HelloWorldTest extends WP_UnitTestCase {}

We can then stub out test functions for each function:

 class HelloWorldTest extends WP_UnitTestCase {
  function test_hello_world() {
    // Test hello_world()
  }

  function test_hello_world_markup() {
    // Test hello_world_markup()
  }

  function test_hello_world_css() {
    // Test hello_world_css()
  }
} 

Now, let's look at each function and consider what we're testing:

1. For hello_world, we want to verify that the string returned is "Hello World!":

function test_hello_world() {
  $this->assertEquals("Hello World!" == hello_world());
}

Easy enough. Now if for some reason someone changes the return value to "Hello world!" -- capitalization be damned -- the test will fail.

I can hear you now, "This is stupid, no one writes code like this." Yes, the example is stupid and that's the point. Don't get caught up focusing on the wrong thing.

It makes no difference how complex the function is, the only concern is verifying that the return value or the outcome is what is expected.

So, if instead you're testing some_made_up_function which returns an object, you may want to verify that it is actually a PHP Object:

$this->assertEquals(gettype(some_made_up_function()), "object");

Or that the object has a specific member attribute:

$test_obj = some_made_up_function();
$this->assertTrue(property_exists($test_obj, 'name_of_attribute_goes_here'));

2. For hello_world_markup, we want to verify that the function prints "<p id='hello-world'>Hello World!</p>":

function test_hello_world_markup() {
  $this->expectOutputString("<p id='hello-world'>Hello World!</p>");
  hello_world_markup();
}

Notice that we're expecting "Hello World!" to be part of the output. This might not be a good thing. If the return value of hello_world changes, this test will fail, too.

For the sake of example, let's say we only care about testing the markup and not the message. We can take this test a step further and decouple the two so that we're only testing the markup by changing it to read:

function test_hello_world_markup() {
  ob_start();
  hello_world_markup();
  $result = ob_get_contents();
  ob_end_clean();

  $this->assertTrue(!empty(preg_match("/^<p\s+id='hello-world'>.*<\/p>$/", $result)));
}

Essentially what we're saying is, we don't care what the message is as long as it is wrapped in <p id="hello-world" /> tag.

Simple, right?

3. For hello_world_css, we want to verify that the function prints the CSS rules for our "Hello World!" markup:

function test_hello_world_css() {
  ob_start();
  hello_world_css();
  $result = ob_get_contents();
  ob_end_clean();

  // Make sure there are style tags being printed. Duh.
  $this->assertTrue(!empty(preg_match("/.*?.*<\/style>/s", $result)));

  // Make sure we're using the right selector
  $this->assertTrue((bool) strpos('#hello-world', $result)));
}

And with that, we're done! You can see the entirety of test-hello-world.php here.

When to write tests

As mentioned earlier, you may want to write your tests first. This is called Test-driven Development.

Writing your tests first has lots of awesome benefits. When you write tests first you are forced to think about the design of your system and how best to structure the software so that it is actually testable. It’s a good practice and will help increase the orthogonality of your code.

However, it's never too late to start writing tests. When you write tests for existing code, you'll find places where things need to be refactored (sometimes completely rewritten or redesigned) to clean up the spaghetti code.

If you really can't afford the time it takes to write tests for all your code (or aren't being allotted the time to do so), you might still be able to lobby for time.

It's extremely important that you can verify your code works, especially in cases where you're using code to make an assertion about the world. For example, if you're writing code that processes a data set which will later be used in a story or investigation, it's crucial that your code produces results that are precise and correct.

The other case where you should absolutely be writing tests is for critical customer interactions. Saving sensitive information or processing payment information are things that you don't want to get wrong.

Updates to INN’s Deploy Tools

We've made a lot of changes and added a bunch of new commands to our deploy tools since the last time we checked in.

Most notably, we've added commands to help scaffold and run unit tests for your plugins AND your themes. Behind the scenes, our scaffolding and tests works as described by Will Haynes in this post.

Other changes include migration to Fabric's new-style task declaration and updated documentation.

Unit testing tools

To scaffold/set up tests for your theme:

$ fab dev wp.tests.setup:largo-dev

This command:

  • Drops and recreates your testing database (which should always be throw-away, ephemeral)
  • Copies essential test files (e.g. phpunit.xml and a tests directory with test-sample.php and bootstrap.php files) to your theme or plugin directory if they are not already present
  • Installs the WordPress test framework to /tmp/wordpress-tests-lib/includes
  • Configure a wp-tests-config.php file and place it in /tmp/wordpress-tests-lib

With those files in place, you can run tests for your theme or plugin:

$ fab dev wp.tests.run:largo-dev

Example output:

Screen Shot 2014-11-25 at 9.45.54 AM

Namespaces and aliases

The other big change we made was moving all commands to Fabric's new-style task declaration. Using new-style tasks gives us all the benefits described in Fabric's documentation on the subject.

While the deploy tools are not taking advantage of every feature of new-style tasks, they are better organized thanks to namespacing and aliases.

Here's some example output from fab -l:

Screen Shot 2014-11-25 at 10.00.07 AM

Here you can see each module (and submodules) along with defined tasks. Tasks use dot-syntax, which should feel more explicit and intuitive if you're familiar with Python (which is what Fabric is built on).

Also note that the command wp.migrations.s2m is an alias for the much-longer, sometimes-harder-to-remember command wp.migrations.single_to_multisite.

I'm very lazy, so anytime I can save some mental energy and type a bit less, I consider it a win. This is as true for commands I use very frequently as it is for commands I use once in a great while.

Documentation

We also expanded the deploy tools' documentation to include all commands. You can find the documentation on Github.

Notes

Remember, the deploy tools were built with our situation in mind. We deploy to WP Engine, so some stuff is very WP Engine specific.

For example, the wp.fetch_sql_dump command only knows where WP Engine stores recent SQL dumps. The wp.maintenance.start and stop commands assume you are deploying to a host that uses Apache and that you have a .htaccess file in the root of your FTP server.

With that said, much of toolkit will work with any host provided you have (S)FTP access, including the push-button deployment rig I wrote about previously.

If you are using or thinking about using the deploy tools, have questions or suggestions, let us know.

How To Migrate A Standalone WordPress Blog To A Multisite Install

Sigh. Database migrations — often nerve-wracking, always tedious, never a task I’ve been fond of. However, as you know, they’re often necessary. For us, they come up frequently.

One of the benefits available to INN members is the option to have their Largo-powered WordPress website hosted on our platform, largoproject.org. As such, we often find we need to migrate their standalone blog to our multisite rig.

To ease the process, I wrote a single_to_multisite_migration command that’s included in our deploy tools repo.

Note: if you’re using WP Engine, our deploy tools include a bunch of helpful goodies. You can read more about them here.

Assumptions

  • You have mysql installed on your computer
  • You’re using INN’s deploy tools with your multisite project
  • The standalone blog uses the standard "wp_" WordPress database prefix

What it takes to migrate a standalone blog

My teammate Adam Schweigert put together this gist that describes the changes required to prepare a standalone blog’s database tables for a new home amongst other blogs in a multisite install.

The process involves renaming all of the blog’s tables from wp_[tablename] to wp_[newblogid]_[tablename]. For example, wp_posts might become wp_53_posts.

This is true for all tables except for wp_users and wp_usermeta. More on this later.

This brings up the question of where the value for new_blog_id comes from. The easiest way to get this value is to create a new blog in your multisite install. By doing this, you’re creating a skeleton of database tables that your standalone blog will fill in.

Step one: create a new blog in your multisite install

After creating a new blog, find it in your Network > Sites list. Hover the site’s link and you’ll see an url in your status bar.

Screen Shot 2014-08-18 at 4.19.28 PM


The “id=53” is the part you want. Make note of the site ID as you’ll need it later.

Step two: retrieving a database dump and running the single_to_multisite_migration command

Let’s look at the signature of the single_to_multisite_migration command:

def single_to_multisite_migration(name=None, new_blog_id=None, ftp_host=None, ftp_user=None, ftp_pass=None):
    ...

The name and new_blog_id parameters are required. The name will be used when creating a database in your local mysql server. This is where we’ll load the single blog’s database dump. It doesn’t matter much what name is, but it should conform to mysql’s rules for identifiers.

The new_blog_id is the ID that you made note of earlier.

If you’re using WP Engine to host the standalone blog, deploy tools can retrieve a recent database dump for you automatically. For this to work, you’ll need to provide your FTP credentials when running single_to_multisite_migration.

Here’s an example:

$ fab single_to_multisite_migration:blogname,53,myinstallname.wpengine.com,ftpusername,ftppassword

If you’re not using WP Engine, you’ll need to get a database dump by some other means. Once you have it, place it in the root directory of your multisite project repo. The single_to_multisite_migration command expects a mysql.sql file in this location, so you may need to rename your dump file to meet this expectation.

After you have the mysql.sql dump in the root of your multisite project repo:

$ fab single_to_multisite_migration:blogname,53
Example of the output from the single_to_multisite_migration command
Example output from the single_to_multisite_migration command

Step three: wait… rejoice! Your multisite_migration.sql file is ready!

Depending how big your standalone blog’s database is, it may take a while for the command to finish.

Message indicating the single_to_multisite_migration command finished properly.
Message indicating the single_to_multisite_migration command finished properly

Step four: apply the multisite_migration.sql file to your multisite database

I leave it up to you to decide how to best to apply the migration file to your database. You may be able to import the sql using phpMyAdmin, or, if you’re using WP Engine, you might contact their lovely support staff and ask them to apply it for you. Be clear that you DO NOT want to drop all tables in your multisite database before importing the multisite_migration.sql file.

Aside from renaming the standalone blog’s tables, what does single_to_multisite_migration do?

Great question. Here’s the short list:

  • Finds the maximum user ID in your multisite database and uses that value to offset the ID’s of users in your standalone blog’s wp_users table so that they can be inserted into the multisite database without duplicate primary key errors.
  • Finds the maximum user meta ID in your multisite database and uses that value to offset the umeta_id's in your standalone blogs wp_usermeta table so that user meta can be inserted into the multisite database without duplicate primary key errors.
  • Retains the siteurl and home values in your multisite “skeleton” site’s wp_[new_blog_id]_options table. This means that you won’t have to (re)set your new multisite blog’s site url and home url values after applying the multisite_migration.sql file.
  • Looks through all of the standalone blog's posts to find "wp-content/uploads/" and replace it with "wp-content/blogs.dir/[new_blog_id]/files/" to help with migrating uploads to the multisite install.
  • Uses REPLACE, UPDATE and some tricky subqueries to insert or update rows. This means you can apply the multisite_migration.sql file to your multisite database and avoid duplicate wp_users and wp_usermeta entries. Helpful if you need to run several incremental migrations to get all of a blog’s content before making the official transition to the multisite install.

Migrating uploads

The other thing you'll need to do is move your standalone blog's uploads directory. The single_to_multisite_migration command doesn't do this for you. You'll have to manually move the contents of the standalone blog's "wp-content/uploads/" to the multisite install "wp-content/blogs.dir/[new_blog_id]/files/" directory.

Test your migrations thoroughly before deploying.

We’ve tested and used this method several times with great success. It works well for us.

That said, remember to thoroughly test migrations before applying them to any production environment.

Break your local development site or your staging server first and be happy about it! Then fix any mistakes and you'll be ready to apply to your production database.