Unit Testing and Drupal 7 Modules

Over the last year and a half, I've been doing a lot of Drupal development at work. Over that time I've come to enjoy it, at least compared to Wordpress development. One thing that always bugged me with Drupal module development (and Wordpress development) is getting the code unit tested. This is especially critical when you have changing requirements and the possibility of different people working on the code.

Drupal, amazingly, offers SimpleTest and a web interface for writing and running unit tests. They also have what they call Web Tests, which are unit tests that can run Drupal and test things like DB access or requesting pages. SimpleTest isn't phpUnit, but what Drupal gives you is worth looking into.

Getting Set Up

The first thing to do is create a modulename.test file in your project. This file will contain your test suites for your module. If you have multiple suites (like for testing different classes in your module) they all go in here. I couldn't figure out a way to separate them out to different files, even by requiring them in.

Also remember to add this to your modulename.info file under the files[] section.

I also recommend adding a dependency for the "simpletest" module on your module.

Under Modules admin page, make sure that SimpleTest is installed an enabled, and enable your module.

Creating a Test Case

Now, inside your .test file you will to set up a series of classes that will house your tests. These will extend either DrupalUnitTestCase or DrupalWebTestCase. Here is a basic test suite:

class MymoduleTestSuite extends DrupalUnitTestCase
{
    static public function getInfo()
    {
        return array(
            'name' => 'Module Tests',
            'description' => 'Unit Tests for this module',
            'group' => 'My Module',
        );
    }    
}

As with many things in Drupal, there is a naming convention that your test suites will need to follow. The class name must be [ModuleName][Description]TestCase. [ModuleName] is your module's name without underscores. The case doesn't seem to matter. The [Description] part just needs to be something. If you are testing a class your module has built, you can do something like MyModuleThisClassTestCase. I am not 100% sure if the TestCase part of the name is necessary, but all the examples I found had it, so I stuck with adding it.

The next part is the static function called getInfo(). This returns information that the test runner will display to the user when they run their tests.

UnitTestCase vs WebTestCase

Before I get to far, I need to explain the difference between these two classes. Your unit test will extend one or the other, and it depends on how much your tests need to do. Drupal considers anything logic related that doesn't do anything with Drupal to be a unit test. So if you are testing logic that doesn't need the DB, or posts, or output, use a unit test.

If you need to do anything that interacts with the DB or you need to retrieve something from the site, use a WebTestCase. WebTestCases take much longer to run though as they set up and destroy a DB with each run.

My recommendation is to split up your tests so that non-DB/node related things are in a unit test, and only the things related to the DB are in a web test. This does create more test suites, but it will speed up your testing.

Unit Testing

OK, so now to create our tests. You add a test by creating a public function with the name test[Something], where something is a description of the test. testAddTwoNumbers is a valid test name. You then use different assert methods to test your code.

public function testAddTwoNumbers()
{
    $result = myFuncToAdd(1,2);
    $this->assertEqual(3, $result, 'These numbers should match');
}

There are a whole suite of asserts that you can use. The Drupal API site has a full list available for the different assertions.

Running Your Tests

To run your tests, go under 'Configuration' -> 'Development' -> 'Testing'. You will see all of the tests that are available for your site. Scroll down and find the group you set in your getInfo() method and check that box. Scroll down and click 'Run Tests' to kick off your test suite.

The test runner will fire off and run your tests. When it is finished, you'll get a readout of any errors, warnings, and successful tests hilighted in red, yellow, and green.

Tips and Gotchas

DB Interaction

As I said above, you can interact with the DB using a WebTestCase. When Drupal runs a WebTestCase, it creates a sandbox set of tables that exist only for the duration of the test. This is good and bad. This is good because then you can run these tests on the live site without touching data, but bad because it is a very expensive operation.

The other caveat is that only Drupal DB functions work correctly. If you use raw SQL, like with db_query(), make sure that table names are surrounded by curly braces, like this:

db_query('SELECT * FROM {myTable}');

SimpleTest will properly translate the table name into whatever temporary table SimpleTest created. If you don't do this, it will use the live table, not the generated one.

Loading Modules

If you need to bootstrap your module, say like if your module has an install routine, use a WebTestCase and add a setUp() method to your test case:

public function setUp()
{
    parent::setUp('myModule', 'someOtherModule');
}

This will bootstrap any needed modules. Bootstrapping your own modue will run any install routines, like setting up tables if you use the Schema API.

Multiple Test Suites

You can split up your tests into different Test Suites as long as you put them all in the mymodule.test file you created. This is extremely useful so that you don't have to run all your tests at the same time and want to only run a certain unit test suite, or a specific web test. You cannot, however, put them in other files and include/require them in your .test file. Drupal won't see them.

Where did all these PDO errors come from?!

SimpleTest wraps a lot of the code your tests run. If you try to create an object that you didn't properly bootstrap, SimpleTest will throw a PDO error as it throws up looking up your object. So, if you see a PDO error on a line where you are creating a new object, your test can't load your object. Either check to make sure you've bootstrapped your module correctly or include the needed files to see your class.

Debugging

Debugging sucks in SimpleTest under Drupal. You can't echo anything out as that will break the test runner. From my searching, you have a few options:

  • Write your errors to a file, and check the file after your tests run
  • Use $this->fail($varToCheck); to add a line to the test runner
  • Use xdebug to step through your code

Other things like drupal_set_message or writing to the watchdog logs get eaten up by the test runner. So use one of the above three options. I normally use the second one as xdebug isn't always installed on the remote machines I'm testing on.

Get Testing

So now you don't have a reason to not unit test your Drupal modules. Get out there and start using SimpleTest, and then Chris Hartjes won't cut you.


Comments

Categories: php,drupal