I've previously explained why automated tests should be written for Drupal, so now I'll show how to add some tests to an existing Drupal 7 module.

Another article is available showing how to write tests for Drupal 8.

Our goal: Make sure the module doesn't break the site

This guide is going to do add tests to do two very simple things:

  1. Enable a module.
  2. Make sure the site doesn't blow up.

These are two simple building blocks that can be improved upon over time. Doing such a simple test will also help confirm that there aren't any facepalm-worthy mistakes in the module ... like the bug left in Metatag 7.x-1.15 that blew up any sites which had enabled the Metatag Validation submodule. Ahem. Sorry about that.

Types of tests

Before we begin it's worth noting that there are two main types of tests supported in Drupal 7 - functional tests and unit tests.

Unit tests are designed for confirming functions and classes, i.e. the module's API, work the way they're supposed to. They don't load the database and so also don't load a full Drupal site, which greatly limits what can be used to run the tests. It's worth noting, however, that they are super fast to run and can complete in mere seconds, so after getting the hang of writing them it can make the testing process a little less time intensive. Tests of this type are built using the DrupalUnitTestCase class.

Functional tests load a full installation of Drupal to be toyed with, so they're ideal for confirming the module's functionality. These tests are built from the DrupalWebTestCase class. The idea is to script a pretend web browser to step through the website and check what happens on the site - look for form fields, look for text that should be there, submit forms, make sure that the site doesn't blow up, etc. These tests are the easier of the two to write because it literally involves stepping through the website like a person would normally. A major downside, however, is that they are slower than unit tests - because it loads a full Drupal installation before beginning each set of tests it can take a few minutes to run a single test. That said, it's totally worth it.

Step 0: Naming conventions

In this guide the module's name is "mymodule", but it could be anything. The site has a directory named "mymodule" and that directory contains a file named "mymodule.info" and "mymodule.module". Because this is a custom module, as opposed to a contributed module or a Feature, the module is stored in "sites/all/modules/custom", the .info file is located at "sites/all/modules/custom/mymodule/mymodule.info" and the .module file is located at "sites/all/modules/custom/mymodule/mymodule.module".

With those preliminary details out of the way, let us begin.

Step 1: Add a line to the info file

Drupal's testing system needs to be informed that any new test files exist. Simpletest is based upon OOP, and because Drupal 7 doesn't support the PSR-0 or PSR-4 autoloader schemes, the standard rules for adding classes to Drupal 7 need to be followed. To do this, the file has to be added to the module's info file, like so:

name = My Module
description = Does some stuff with the things.
core = 7.x

; Tests.
files[] = tests/mymodule.test

The new line is the one beginning with "files[]" - the rest of the file really doesn't matter. This is a standard part of a Drupal module's info file which indicates to Drupal's internal systems that the file listed here must be indexed in the system registry. From there, Drupal will know that if a class happens to be inside the file, when that class is requested by code throughout the system it will know to load this specific file. In short, it's a very rudimentary class autoloader.

The value on this line follows a simple structure - it's the name of a file. In this case, the filename will be "mymodule.test" - note, the file's extension is "test", not "php", "txt", "inc" or anything else. The rest of the filename is not that important, it is just a convention used through the Drupal community for a module's initial test coverage to be kept in a file named for the module, i.e. "mymodule.test".

The test file is being stored in a directory within the module's director named "tests". This is another convention that has built up over the years. Moving the test files into a separate directory helps keep the module's top level directory from getting too large, and then makes it less messy when multiple test files are included. But I'm getting ahead of myself.

Step 2: Create the tests file

The next step is to create the actual test file itself. Based upon the information above, of a custom module named "mymodule", the first tests are going to be stored in a file named "mymodule.test" and it will be stored in the "tests" subdirectory. Put together, the codebase might look like the following:

  • includes
  • misc
  • modules
  • profiles
  • scripts
  • sites
    • all
      • modules
        • contrib
          • ctools
          • metatag
          • token
          • ...
        • custom
          • mymodule
            • mymodule.info
            • mymodule.module
            • tests
              • mymodule.test
    • themes
      • omega
      • omega_mysite
  • themes

In this case, the location of the custom module named "mymodule" is "sites/all/modules/custom/mymodule", thus the test file will be located at "sites/all/modules/custom/mymodule/tests/mymodule.test".

Step 3: Create the first test class

Following Drupal's coding standards and convention, the class should start with the module's name in CamelCase and end with "TestCase"; this also helps avoid name collisions because Drupal 7 doesn't use PHP's namespaces system. It's then normal to add an extra word or three explaining what the test class is for. For this guide, the test is just going to confirm basic functionality works, so the class can be named "MyModuleBasicTestCase". This results in code looking as follows:

<?php

/**
 * @file
 * Some tests for the My Module module.
 */

/**
 * Test basic functionality of My Module.
 */
class MyModuleBasicTestCase extends DrupalWebTestCase {
}

Once this class is added it will be necessary to clear Drupal's caches in order for it to be available to the system.

Step 4: Tell the system about the test

While it's all well and good to have a class with an appropriate name that extends one of the two Drupal test classes, the system needs to know what this test is actually called. This is achieved by adding a method to the class named "getInfo". This class is really simple, it just returns an array of values that tell Drupal what its name is, a longer description, and indicates what group it belongs to. For this guide the following would be sufficient:

  /**
   * {@inheritdoc}
   */
  public static function getInfo() {
    return array(
      'name' => 'MyModule basic test',
      'description' => 'Confirm that the MyModule module can be enabled.',
      'group' => 'mymodule',
    );
  }

The 'name' attribute is pretty obvious - it's the label used when viewing the full list of tests, so it should be short and to the point. The 'description' attribute is for giving a slightly more verbose explanation of what the tests class is for; usually it should be used to explain what tests the class covers, so that when someone looks at the tests available in the admin interface it's clear what's what.

The 'group' option is used to ... group tests together. This comes into play when running the tests - it's possible to tell Drupal to run all of the tests of a single group at one time, rather than doing them one at a time or rather than running all of the tests at once. It is typical to initially give the group the same name as the module, but in some cases there might be so many tests that they're put into separate groups, e.g. one group for the module's APIs, one for the module's UI functionality, etc. Anyway, for now it's best to just stick with one group with the name of the module.

Step 5: Set up the tests

When tests are started they will initially look for a method named "setUp". This is used to inform the test system that additional modules need to be enabled, and maybe do other setup tasks like create a new user, maybe a new content type, etc. In short, it's for initial steps to be done after Drupal is actually installed but before the test operations begin.

At a basic level, the class should look like this:

  /**
   * {@inheritdoc}
   */
  public function setUp(array $modules = array()) {
  }

As can be seen, the function has the list of modules passed as an argument, and new modules can be added to the list to be automatically available when the tests start. Note that when using the DrupalWebTestCase class the site will start off with Drupal's "standard" installation profile, so lots of the core modules will already be installed, like Field UI, Node, Contextual, Search, Toolbar, etc. Beyond that, however, the test system doesn't automatically know that "this test is for MyModule so I should automatically install it too", so everything else has to be specified.

For this guide, there's just one module that needs to be installed - "My Module" itself. To add this module to the list just add it as an item to the array, i.e.:

  /**
   * {@inheritdoc}
   */
  public function setUp(array $modules = array()) {
    // Enable this custom module.
    $modules[] = 'mymodule';
    parent::setUp($modules);
  }

Now, any time the tests are started it will go through the steps to create a "Standard" Drupal installation and then enable the "mymodule" module.

Well, almost.

Step 6: Add a test function

There's one missing piece. When the tests are started it checks through all of the classes in the system that extend from the two test classes. However, it won't automatically run them if there isn't this one extra piece.

In order for SimpleTest to run a test class there must be a method that starts with the word "test". This means that there needs to be a method in the new class named "testSomethingThenRunAway", "testLoggingIntoTheSite", "testChangingMyHairColor", "testFizzlepopBerrytwist" - just something that starts with the word "test".

For this guide the aim is to just make sure the module can be enabled and won't blow up the site. This means that, along with all of the steps above, all that's needed is to add a function with an appropriate name:

  /**
   * Make sure everything works to this point.
   */
  public function testTheSiteStillWorks() {
    // Not doing anything right now, save it for later.
  }

And that's it. Now, starting the new test through either the website or the command line should result in the tests running correctly and not giving any errors. Presuming no errors are found, this means that the module can be enabled successfully!

Progress!

Step 7: Verify the site still loads

One final step to add is a few lines that can confirm that the website still actually works - it's fine that enabling the module doesn't break anything, but it's not quite enough.

For now we'll take an initial step of just loading the site's homepage, and assume that if the homepage is working that nothing went horribly wrong.

  /**
   * Make sure the site still works. For now just check the front page.
   */
  public function testTheSiteStillWorks() {
    // Load the front page.
    $this->drupalGet('<front>');

    // Confirm that the site didn't throw a server error or something else.
    $this->assertResponse(200);

    // Confirm that the front page contains the standard text.
    $this->assertText(t('Welcome to Drupal'));
  }

The inline comments explain what each step does, but in short, it just loads the front page and makes sure it isn't completely broken.

This is also why these tests use DrupalWebTestCase instead of DrupalUnitTestCase. Without having the full Drupal system installed it isn't possible to confirm that enabling the module won't completely break the site.

Putting it all together

Combining all of the changes above will make the module look like this:

sites/all/modules/custom/mymodule/mymodule.info:

name = My Module
description = Does some stuff with the things.
core = 7.x

; Tests.
files[] = tests/mymodule.test

sites/all/modules/custom/mymodule/tests/mymodule.test:

<?php

/**
 * @file
 * Some tests for the My Module module.
 */

/**
 * Test basic functionality of My Module.
 */
class MyModuleBasicTestCase extends DrupalWebTestCase {

  /**
   * {@inheritdoc}
   */
  public static function getInfo() {
    return array(
      'name' => 'MyModule basic test',
      'description' => 'Confirm that the MyModule module can be enabled.',
      'group' => 'mymodule',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function setUp(array $modules = array()) {
    // Enable this custom module.
    $modules[] = 'mymodule';
    parent::setUp($modules);
  }

  /**
   * Make sure the site still works. For now just check the front page.
   */
  public function testTheSiteStillWorks() {
    // Load the front page.
    $this->drupalGet('<front>');

    // Confirm that the site didn't throw a server error or something else.
    $this->assertResponse(200);

    // Confirm that the front page contains the standard text.
    $this->assertText(t('Welcome to Drupal'));
  }

}

Et voila!

Running tests

Before any tests can be run, a module needs to be enabled. While the machine name of the module is "simpletest", in the modules admin page (admin/modules) it's actually called "Testing" and can be found in the "Core" module group. Enabling this module preps some things in the database and the directory structure,

There are two different ways of running Drupal 7's Simpletests - through the administrative interface when logged in as an administrator, or from the command line using a script that's bundled with Drupal. This article just focuses on using the administrative interface, because it's always there and harder to get lost in than the command line.

To run the tests, go to the configuration dashboard (/admin/config) and click on the "Testing" link listed under the "Development" section. This will present a list of every test available on the site, grouped by the "group" value from the getInfo() method defined above. The first thing to note is that while it is possible to run every single available test in one go, it could take literal hours to do so it's not a good idea. So, instead just focus on the new tests.

Search through the list to find the module the tests are for, i.e. the getInfo() "group" value that was set above. All of the tests for this group can now be run in one go just by clicking the checkbox beside it, scrolling down and clicking "Run tests". Alternatively, expand the group by clicking the arrow beside the group name and select the individual tests to run, and again click "Run tests".

When the "Run tests" button is clicked it uses core's Batch API to run all of the processes. The first thing it does is create a barebones installation of Drupal core using the "standard" installation profile, which can take a little while. It then takes its cues from the setUp() method of the test class to work out what modules need to be installed, plus any other details that are included. Once it's finally all set, it runs all of the test methods in the test class one at a time.

When the tests are all finished it'll show a summary of the results and give a chance to quickly re-run the same tests again, which is simpler than digging through the huge list again. The results section will group the messages by the test class, rather than in one huge list, and it'll hopefully show green lines for each of the steps simpletest made. If there are verbose messages added, which we'll get into next time, a link will be provided to view the HTML file that was created.

And that's all there is to it.

Homework time

A lot can be built from these rudimentary beginnings. Additional lines can be added to confirm that other pages can be loaded, that other text can be confirmed to be shown on the page, that forms exist and they have the expected fields, etc. Also, additional test methods can be added to group different assertions, e.g. maybe one test method for testing node pages, one for custom pages, etc. It's worth taking some time to adding some additional code to this test class to help feel comfortable with it.

However, for now just start with the above. It is pretty quick to add a test like the above to any and all modules and to submit patches to add such a test to a contrib module, so it's worth taking the time to do this. As mentioned previously having tests available means that the automated tests can be enabled for a module, and it's also a good place to start encouraging others to add more, slowly building the module's test suite.

And that's it for now

The seven (ok, eight) steps have covered quite a lot of important little details. These are the building blocks from which all Drupal 7 tests begin. From these humble beginnings, great things will come, so stay tuned for more articles about testing Drupal!