Topic “SimpleTest”

Test-driven Drupal development, take 2

A while ago I wrote about a method we'd been using internally here at OpenSourcery for testing existing Drupal configurations. There are currently a few issues in the works that would get this functionality into core-SimpleTest:

In the meantime, however, since this functionality has immediate benefits to making more robust Drupal sites, I've decided to release the module we're using internally until this is part of core-SimpleTest, and gets back-ported to Drupal 6.x. I've placed the module on GitHub rather than on Drupal.org so as not to clog things up with placeholder modules. I've also attached a tar-ball to this post for those without Git.

Update: This module has also been released on Drupal.org in order to allow people to track updates.

To use the module, it must be enabled, and then included at the top of any test file that extend the class:

// Include SimpleTest Clone
module_load_include('test', 'simpletest_clone');

tests then extend the SimpleTestCloneTestCase class instead of DrupalWebTestCase:
class CustomTestCase extends SimpleTestCloneTestCase {
...
}

Occasionally, on a given site or set of tests, it may not be desirable to clone every table because this can make tests run for a very long time. Because of this, there is a method in place for excluding tables from being cloned:

  /**
   * When testing data-intensive sites, if the tests will still run correctly,
   * tables can be excluded here. The structure will still be cloned, but the
   * data will not be copied over.
   */
  function __construct($testId = NULL) {
    parent::__construct($testId);
    $this->excludeTables = array(
      // List of tables to exclude goes here.
      'example_table_foo',
      'example_table_bar',
    );
  }

Tagged as: Drupal, SimpleTest, Test-driven development

Test-driven Drupal development

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:

Tagged as: Drupal, SimpleTest, Test-driven development

Writing SimpleTests for a CiviCRM installation

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.

Tagged as: CiviCRM, Drupal, SimpleTest

Syndicate content