Example Drupal module to use for TDD demonstration
To illustrate how to use Test Driven Development while developing a Drupal module, we first need an example module to write: Let's assume we want a page in our Drupal admin console that lists all of nodes in the database, similar to the Administer->Content Management->Content page. It will also filter and sort the list, but instead of filtering on the status and type of nodes, it will filter on words contained in the node title.
The first thing I did was to write the module as quickly as possible as I normally would have without using TDD. Here’s the completed module: demo.module, and demo.info. If you want to follow along this demonstration just install a fresh copy of the latest version of Drupal 6.x (Drupal 6.6 when I wrote this); be sure to create and use a new MySQL database to use in these examples so your existing work won’t get in the way. Then download the demo.module and demo.info files into a new "demo" module folder and enable the demo module in your admin console (Administer->Site building->Modules). It will be called "Demo" and will appear at the bottom of the list of modules in a section called "TDD Demo Modules."
Once the Demo module is enabled, look for a new page in your admin console at Administer->Content management->Demo Page. It will initially display a list of all the nodes in your database, and also allow you to enter a keyword to show only the nodes whose title contains the given word.
If you are using a new empty database, create a couple of pages (Create Content->Page) to have some data to display here. I created two pages with these titles (body text doesn't matter):
- This is my first page
- My second page!
A quick review of the module code will reveal nothing special. Demo.module is very simple… it has some boilerplate code (demo_help and demo_menu), and then displays the new “Demo Page” with the demo_page_view function:
function demo_page_view($keys = NULL) { $header = array( array('data' => t('Page Title'), 'field' => 'title', 'sort' => 'asc') ); $sql = "SELECT * FROM {node} WHERE title LIKE '%%%s%%'"; $sql .= tablesort_sql($header); $result = pager_query($sql, 50, 0 , NULL, $keys); $rows = array(); while ($data = db_fetch_object($result)) { $rows[] = array(check_plain($data->title)); } if (empty($rows)) { $rows[] = array(array('data' => 'No pages match the given pattern.')); } $output = drupal_get_form('demo_pattern_form', $keys); $output .= "Pages matching this pattern:"; $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 50, 0); return $output; }
This is really all there is to demo.module – this function generates a SQL statement using tablesort_sql, then selects all the matching nodes from the database using pager_query and builds an array containing the matches. Then it returns the HTML for a table containing the matching records using the currently selected theme. Later in the module file there are more functions that handle navigation and processing for the “demo_pattern_form.”
This code is so simple it’s hard to imagine that anything could be wrong with it: in just a few lines it gets the data we need and also displays it. It’s also very typical Drupal module code. If you look around the Drupal code base or at code from 3rd party modules you will see a lot of code that looks just like this. It uses common Drupal functions like “theme,” “pager_query” and “db_fetch_object.”
But in my opinion there are a two things wrong with this example module:
- There are no tests – if I needed to change something about the behavior of demo.module I would have no way to know whether or not I broke something after making a code change, other than by opening a browser and testing manually.
- It’s very hard to separate the actual business logic or custom behavior that demo.module provides from the boilerplate code required to run inside of Drupal.
A veteran Drupal developer would disagree with the second point, of course: obviously the SQL statement used in demo_page_view really is the only custom behavior here, and all of the other calls to Drupal functions like drupal_get_form, table_sort_sql, etc., are used to help display our data in a Drupal page. But imagine a PHP developer who wasn’t yet very familiar with Drupal looking at this module file for the first time: how would she or he have any idea what this module did? Or where to begin to change it’s behavior if necessary? This is another important use of tests: as living documentation for what code does and how it should work.
This is a silly example, but imagine if this module did something sophisticated and important to your business. Then imagine if your business decided to upgrade from one version of Drupal to another – or imagine that your business decided to use DJango, Joomla or some other CMS system… or to implement it using custom Java or Ruby code. Then the question would become: what part of the module’s code is ours that we want to keep? And what part of the module is simply needed to coexist with Drupal? Right now it’s not so easy to tell.
I believe that using TDD while writing a Drupal module will not only provide the normal benefits of testing: emergent design, living documentation and simply more robust code, but will also lead to isolating your business’s code from the framework in a natural way. By writing unit tests first, you will have to think about what you are trying to test: your business logic only and not the Drupal framework itself.
In my next post we’ll get started by installing PHPUnit and writing our first PHPUnit unit test with Drupal.