SimpleTest

Test-driven Drupal development

October 24, 2008

Drupal's implementation of SimpleTest-style testing is an amazing tool for developing complex applications. When code is complex enough, a simple change can break things in unexpected places, but with good test coverage, this breakage can quickly be discovered and fixed. The same goes for deploying Drupal sites. By default SimpleTest creates a parallel test database from the ground up, meaning that it installs a brand new Drupal site, rather than copy the instance of Drupal it is installed on. This is all well and good for module development, and maintaining a solid core, but when it comes to testing complex site configurations, it would be nice to be able to test that all the installed modules, configured in such and such a way, are all playing nicely together. Taking this one step further, for a really complex site, it becomes possible to write a set of acceptance tests before site configuration even starts. Knowing that these tests must eventually pass can significantly focus and drive the development work.

That's where overriding the DrupalWebTestCase setUp() function comes into play. SimpleTest will run the setUp() function as defined in the DrupalWebTestCase() class unless the current test case has an overriding setUp() function. Some existing tests append additional functionality during set up, but most usually call the parent method, which is responsible for installing that fresh instance of Drupal.
To copy the existing set up, the parent function can be overridden on a per-test-case basis:

  class MyModuleHelperTestCase extends DrupalWebTestCase {
 
    /**
     * Implementation of setUp().
     */
    function setUp() {
      // Here the existing Drupal instance will be cloned.
    }

By defining the function here, the parent method won't be called by SimpleTest. Also note that this helper test case can be called by additional test cases so that the cloning of the database doesn't need to be re-coded for each test case. In order to clone the existing set up, the database schema is needed. Once we have the schema, it is looped through, creating identical tables, and then populating them:

Writing SimpleTests for a CiviCRM installation

September 23, 2008

On a recent project involving CiviCRM, I really needed to write tests for the workflow, which was very complex, and without tests, brittle.

The solution, partially, is Drupal's SimpleTest (which now uses it's own framework, instead of the original SimpleTest library). I say partially, because unfortunately, CiviCRM sits completely outside of the Drupal API, and doesn't play well with things such as hook_install(), which the Drupal testing framework uses to build a parallel testing database each time tests are run.

As such, the mostly complete solution was to use my custom workflow module's setUp() method to copy the existing CiviCRM database, and then run tests against that:

 

  /**
   * Implementation of setUp().
   */
  function setUp() {
    parent::setUp('civicrm', 'os_custom', 'os_civicompany');
 
    // Now, clone civicrm install
    global $db_prefix;
    $test_db = db_set_active('civicrm');
    // Build list of tables
    // @todo - implement this for postgresql
    $result = db_query("SHOW TABLES");
    while ($table = db_result($result)) {
      // Build list
      $this->tables[] = $table;
    }
 
    // Clone tables with prefix
    foreach ($this->tables as $table) {
      // Note, we intentionally DON'T use the bracketed table names here,
      // since the whole point is to create a set of tables that will work
      // with the testing $db_prefix.
      db_query("CREATE TABLE " . $db_prefix . $table . " AS SELECT * FROM " . $table);
    }
    db_set_active($test_db);
  }

This set of code first runs the parent setUp() method, which creates a new Drupal instance inside the existing database, but with all the tables prefixed with the $db_prefix. It then switches to the CiviCRM database, and copies each table into an identical table, but named with the testing prefix. On advantage, that is mostly a side-effect, is that instead of having a fresh install of a CiviCRM database, I get a testing copy that is configured exactly the same way as the application I am trying to test in the first place*.

In order to clean up after the test, a custom tearDown() method is also needed:

 /**
   * Implementation of tearDown().
   */
  function tearDown() {
    // Cleanup CiviCRM database.
    global $db_prefix;
    if ($db_prefix && ($db_prefix != $this->db_prefix_original)) {
      // Only drop tables if $db_prefix is set, and not equal to the original
      $test_db = db_set_active('civicrm');
      foreach ($this->tables as $table) {
        db_query("DROP TABLE " . $db_prefix . $table);
      }
      db_set_active($test_db);
    }
 
    parent::tearDown();
  }

This simply goes and removes the prefixed copy of the CiviCRM database.

These two methods allow for my custom Drupal modules to test against the CiviCRM database in most cases. Where this method fails is for testing actual calls to the CiviCRM API. Since this API sits completely outside of Drupal, it isn't fooled by simply switching the global $db_prefix for the tests. It may be possible to trick CiviCRM into operating on the test database, but as of this writing, I haven't found an elegant way to do so.

* A similar method could be used to duplicate the configured Drupal application. Instead of calling parent::setUp(), the method would duplicate everything that function does except run the install, and instead copy every table into the test database.