Writing your own test - Drupal 6/7

Last modified: April 26, 2009 - 05:18

NB this documentation is a work in progress, you can see that progress and contribute feedback over at http://groups.drupal.org/node/11020

Types of Automated Tests

Simple Test is intended to support two kinds of automated testing: Unit Testing and Functional Testing.

  • Unit Testing: Test the code "from the developer's perspective." Test the code at the level of the "smallest testable unit", where the smallest unit is a function. Verify that the code behaves as expected. In the ideal unit, every public function is tested.
  • Functional Testing (similar to Behavioral Testing): Test the code "from the user's perspective." This tests the way the application behaves by simulating user interactions. For example, it may posts form data and then check the response

(For a short introduction to these two testing strategies, read Testing, fun? Really? at IBM Developer Works.)

SimpleTest supports the writing of both Unit Tests and Functional Tests.

Where do test files go and what do I call them?

Most of the time, you want to test a single module, in which case you simply create a file called module.test in the module's directory.

One exception to this rule is for various core library files, such as those files found in the /includes directory. For these you would create a file called filename.test in the /modules/simpletest/tests/ directory, where filename is the file in /includes that contains the code you're testing.

Modules that wish to have multiple test files (for example, both a .test file for the module itself and a .test file to ensure the module conforms to external RFCs or specifications) may also create a "tests" subdirectory and place multiple .test files within it.

Taking a tour: What's in a test file?

Test Case classes (test groupings)

Each .test file must contain one or more DrupalWebTestCase classes which hold groups of tests. DrupalWebTestCase classes are displayed in the testing module user interface. Checkboxes for each class allow different combinations to be run for each test run.

An example DrupalWebTestCase class declaration is:

<?php
class ModuleNameTestCase extends DrupalWebTestCase {
  ...
}
?>

This new test case, ModuleNameTestCase, extends the DrupalWebTestCase class. The DrupalWebTestCase class encapsulates many of the features needed to write unit tests or functional tests. The job of the test writer is simply to add testing functions to the existing framework.

The most common use case for a test case is to test the features and functions of a Drupal module.

A typical module can get away with having only one DrupalWebTestCase class to hold all of its tests. Such a test case would hold several testing functions, each of which tests a particular function (unit testing) or feature (functional testing) of the module.

However, there are circumstances where a test suite should have more than one class for testing:

  • If both unit tests and functional tests are being written. Dividing unit tests and functional tests into separate classes can keep the code tidy and make it easy to select just a certain subset of tests to run.
  • If some parts of the module might be tested in isolation from others, grouping the tests into classes can make testing more specific. (Example: module-related tests vs. its theme-related tests)
  • If there would be performance impacts of running all of the tests at once (for example, all node module or all database system tests), it makes sense to split them up into separate groupings.
  • If the module just plain does a lot of stuff and you want to break things up a bit to make them more manageable, additional test groupings also can make sense.

When creating multiple testing groups in a single .test file, use a ModuleNameDescriptionOfTestTestCase naming convention instead (for example, DatabaseSelectTestCase, DatabaseFetchTestCase, DatabaseInsertTestCase, etc.).

getInfo() method (metadata about test group)

Each TestCase class requires a getInfo() method, which returns meta-data about the test: its name, description, and what "group" it belongs to (such as the module it's written for).

An example getInfo() method:

<?php
 
// Inside TestCase class...

  /**
   * Implementation of getInfo().
   */
 
public static function getInfo() {
    return array(
     
// 'name' should start with what is being tested (menu item) followed by what
      // about it is being tested (creation/deletion).
     
'name' => t('Whoozit CRUD'),
     
// 'description' should be one or more complete sentences
      // explaining the test.
     
'description' => t('Test creating, reading, updating and deleting (CRUD) a Whoozit.'),
     
// 'group' should be a logical grouping of test cases, like a category.
      // Suggestion: Use the name of the module to be tested.
     
'group' => t('Module'),
    );
  }
?>

Test methods (the tests themselves)

The vast bulk of .test files are the test methods, which contain the tests themselves. At the heart of all test methods are assertions.

A list of all available assertions can be found at http://drupal.org/node/265828

Here are example test methods in a typical test case:

<?php
 
// Inside TestCase class...

  /**
   * One-sentence description of test.
   */
 
function testModuleNameTaskVariation() {
   
// Code that does something to be tested.

    // Test that the code did what it was supposed to do.
   
$this->assertEqual($some_value, $another_value, t('Testing equality of some value and another value'));
  }

 
/**
   * One-sentence description of test.
   */
 
function testModuleNameTaskVariation() {
   
// ...
   
$this->assertNotEqual($some_value, $another_value, t('Testing inequality of some value and another value'));
  }
?>

Some guidelines about test methods:

  • Aim for each test method to test only one thing. This makes it much easier to hone in on what was being tested when failures appear. For example, rather than an enormous testUserLogin() function with several variations, instead do testUserLoginDuplicateUser() and testUserLoginRequiredFieldsMissing().
  • For functional tests, name the methods testModuleVerbVariation(), such as testUserLoginDuplicateUser().
  • For unit tests, name the methods testFunctionNameVariation, such as testVariableGetMissingValue().

setUp()/tearDown() methods (setup and/or cleanup after tests)

There are two methods available for configuring the test environment: setUp() and tearDown(). The setUp() method runs before every test method, and the tearDown() runs after each test method. When using setUp(), always call the parent setUp() method on the first line of your setUp() method:

<?php
function setUp() {
 
parent::setUp();

 
// Do other stuff here...
}
?>

When invoking the parent's setUp() method, a list of modules that need to be enabled for testing can be passed in. When writing a module test, make sure to include the module you are testing as a parameter to the parent::setUp() method

Normally, a test suite will use the setUp() method to enable and install the current module. Other initialization may go here, too. (See the example below.) For example, when writing a test for the blog module, the setUp() method will begin like this:

<?php
function setUp() {
 
// This installs the blog module, which is the module this suite is testing.
 
parent::setUp('blog');
}
?>

The tearDown() method is used less frequently. It should be used only when the test needs to clean up after itself. Remember that SimpleTest cleans out its environment when it is complete, and the default tearDown() does its own cleaning. So in most cases, a custom tearDown() is not necessary.

Just as is the case with setUp(), a custom tearDown() method should always call its parent. This should be done at the end of the tearDown() method, though.

<?php
function tearDown() {
 
// Do tear down tasks...

  // Finally...
 
parent::tearDown();
}
?>

Here's an example case for testing the module my_module:

<?php
class MyModuleUnitTest extends DrupalWebTestCase {

 
/**
   * Implementation of setUp().
   */
 
function setUp() {

   
// Load two modules: the statistics module, and my_module (the module we are testing)
   
parent::setUp('statistics', 'my_module');

   
// Next, perform any required steps for the test methods within this test grouping.
   
variable_set('some_variable', 'some value');
  }

 
/**
   * Implementation of tearDown().
   */
 
function tearDown() {
   
// Perform any clean-up tasks.
   
variable_del('some_variable');

   
// The last thing a tearDown() method should always do is call its parent tearDown() method.
   
parent::tearDown();
  }
}
?>

References on unit testing and Drupal

SimpleTest and Drupal 6/7

SimpleTest beyond Drupal

Information about previous versions of SimpleTest. These articles make use of hooks, methods, and functions that do not exist in current versions of SimpleTest.

 
 

Drupal is a registered trademark of Dries Buytaert.