Continuous Integration and Drupal


Published on

A DrupalCamp NYC presentation on how to use SimpleTest with Drupal and a brief primer on the Hudson continuous integration engine.

Published in: Technology, Education
1 Comment
  • Is Aegir really needed for this approach? I can't confirm that I have it running yet, but couldn't you do the following?

    * Have Hudson clone the git repo with the code base.
    * Run a bash script that does the following:
    * Symlinks to a settings file on the host (so you don't have to worry about passing variables to setup the DB).
    * Drops all the tables in your Drupal DB.
    * Uses curl to run the appropriate install profile: curl ''

    If you're using the profiler module, you can set the username/email for the super user without visiting the config screen that normally runs if you click your way through the install process. But really, you probably don't even need the super user account if you're just setting up the site to run SimpleTest, since it creates its own user(s) for each test, right?
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Continuous Integration and Drupal

  1. 1. CONTINUOUS INTEGRATION Automatically Install, Test, and Deploy Your Drupal Site Wednesday, December 16, 2009
  2. 2. Testing and Drupal The focus of Drupal 7 has largely been on automated testing and Drupal adopted SimpleTest as its test framework of choice. Major Drupal 6 modules are starting to get unit tests, but their correctness varies. Regardless, it’s still easy to write your own unit tests. Wednesday, December 16, 2009
  3. 3. SimpleTest SimpleTest is a testing framework that has been adopted for use in Drupal core and contrib. Drupal 7 core is automatically tested using the PIFR module and testing slaves. We’ll talk later about getting SimpleTests running automatically as part of our Hudson continuous integration framework. Wednesday, December 16, 2009
  4. 4. Running Tests SimpleTest is a contributed module for Drupal 6. It is in Drupal 7 core, and it also happens to be part of the Pressflow distribution’s core set of modules. To run tests, the SimpleTest module must be enabled. (Our install profile turns it on by default.) Then you can head to / admin/build/testing to see all the available tests. Wednesday, December 16, 2009
  5. 5. The SimpleTest Interface Each test shown in the SimpleTest administrative interface is an instance of the DrupalWebTestCase class. Each of these tests may contain multiple assertions. Wednesday, December 16, 2009
  6. 6. Running Tests For each assertion in a test, SimpleTest will log whether each passed or failed, along with any PHP exceptions that happen along the way. Wednesday, December 16, 2009
  7. 7. Testing Workflow Ideally, SimpleTests should all be run prior to a commit so that we’re not relying on Hudson to find errors in unit tests. As of right now, quite a few contributed modules have test failures. Wednesday, December 16, 2009
  8. 8. Writing SimpleTests SimpleTest looks for .test files. They should be placed inside a tests folder in your module’s folder. (The modules for Drupal 7’s tests, and the backported block tests for Pressflow are located in the <htdocs>/modules/simpletest/tests.) Each .test file may contain one or more classes that extend DrupalWebTestCase. Wednesday, December 16, 2009
  9. 9. DrupalWebTestCase The DrupalWebTestCase (hereafter DWTC) class defines a number of extra helper methods besides the ones provided by SimpleTest and also defines a Drupal-specific default setup and teardown behavior. You can find the definition for this class at drupal_web_test_case.php inside the simpletest folder. Wednesday, December 16, 2009
  10. 10. Your Test Classes Each class that inherits from DWTC should provide an implementation of the DrupalWebTestCase::getInfo() method: function getInfo() { return array( 'name' => t('RSS and XML formatting tests'), 'description' => t('Tests for the format_rss_item() and format_xml_elements() functions.'), 'group' => t('System'), ); } This information shows up on the SimpleTest admin screen. Wednesday, December 16, 2009
  11. 11. Your Tests In addition to getInfo(), you must create a least one testing method. SimpleTest will run any public methods whose names begin with “test”. Wednesday, December 16, 2009
  12. 12. Behind the Scenes So how does SimpleTest work without disturbing your local database? The real magic happens in the DrupalWebTestCase::setUp() method. That method creates a new random numerical prefix and then uses Drupal’s database prefixing feature to install a new, empty copy of Drupal with the default install profile using the random database prefix. It also creates a temporary files directory (also prefixed with the random number) in the current files directory. Wednesday, December 16, 2009
  13. 13. Behind The Scenes, Cont’d Although it doesn’t show it as a parameter, ::setUp() will also take a variable number of extra arguments as modules that should be enabled in addition to those that come with the default profile. Since block.module is enabled in the default install profile, for example, BlockTestCase doesn’t have to call setUp() with any extra arguments. However, CCK’s tests need to look in the database, and so they need at least the content and text CCK modules, and the schema module to examine DB schema. Wednesday, December 16, 2009
  14. 14. Looking At Tests Here’s the BlockTestCase from block.test: class BlockTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => t('Block functionality'), 'description' => t('Add, edit and delete custom block. Configure and move a module-defined block.'), 'group' => t('Block'), ); } Wednesday, December 16, 2009
  15. 15. Looking At Tests Here’s the BlockTestCase from block.test: /** * Implementation of setUp(). */ function setUp() { parent::setUp(); // Create and login user $admin_user = $this->drupalCreateUser(array('administer blocks')); $this->drupalLogin($admin_user); } Wednesday, December 16, 2009
  16. 16. Looking At Tests Compare that to the CCK ContentCrudTestCase, which is a base class that other CCK tests inherit from. Note how the setUp () method is set to enable the three modules the test will need. class ContentCrudTestCase extends DrupalWebTestCase { function setUp() { $args = func_get_args(); $modules = array_merge(array('content', 'schema', 'text'), $args); call_user_func_array(array('parent','setUp'), $modules); module_load_include('inc', 'content', 'includes/ content.crud'); Wednesday, December 16, 2009
  17. 17. Basic Assertions The main assertions that are built into the UnitTestCase class (from which DrupalWebTestCase inherits) are documented at UnitTestCase.html . Wednesday, December 16, 2009
  18. 18. DrupalWebTestCase DrupalWebTestCase inherits from the SimpleTest library’s WebTestCase class, but also adds extra capabilities for accessing Drupal pages by URL, setting the logged in user, submitting forms with ::drupalPost() and more. You can read about these functions at 265762 . Wednesday, December 16, 2009
  19. 19. Test Types Sometimes you will want to create purely functional tests, as in one I wrote for D7 HEAD to test format_rss_item. This just tests the validity of a regular expression for several common possible XML tags. function testElementNameRegex() { $regex = '/^[w:][w-:]*$/'; $this->assertEqual(preg_match($regex, 'i<3badlyformattedtags'), 0, t('XML Element Regex is valid')); $this->assertEqual(preg_match($regex, 'georss:point'), 1, t('XML Element Regex is valid')); $this->assertEqual(preg_match($regex, ' not:valid'), 0, t('XML Element Regex is valid')); $this->assertEqual(preg_match($regex, '-not-so-valid'), 0, t('XML Element Regex is valid')); } Wednesday, December 16, 2009
  20. 20. Test Types Most of your tests will involve using APIs, such as this test that tests the format_rss_item() call to make sure it escapes XSS. function testXSS() { $sanitized_title = check_plain($this->getRSSTitle()); $sanitized_link = check_url($this->getRSSLink()); $sanitized_description = check_plain($this->getRSSDescription()); $output = format_rss_item($this->getRSSTitle(), $this->getRSSLink(), $this->getRSSDescription(), array()); $simplexml_result = simplexml_load_string($output); Wednesday, December 16, 2009
  21. 21. Test Types Most of your tests will involve using APIs, such as this test that tests the format_rss_item() call to make sure it escapes XSS. $this->assertTrue($simplexml_result !== FALSE, t('SimpleXML could properly parse the output')); $this->assertTrue(strpos($output, $sanitized_title) != FALSE, t('XSS attack %title was filtered', array('%title' => $this- >getRSSTitle()))); $this->assertTrue(strpos($output, $sanitized_link) != FALSE, t('Potentially dangerous URL %link was filtered to %sanitized_link', array('%link' => $this->getRSSLink(), '%sanitized_link' => $sanitized_link))); Wednesday, December 16, 2009
  22. 22. Test-Driven Development A major focus of many agile development practices are Test- Driven (or Behavior-Driven) Development, whereby unit tests are written first to satisfy requirements, and can be continually tested as the project progresses. Another common thread in doing TDD is to bring tests into bug fixes. In many open-source projects, such as jQuery, people ask for a test case to demonstrate a bug, and then a new test can be written so that the regression never happens again. Wednesday, December 16, 2009
  23. 23. Running Tests Can take a long time, especially if your install profile grows. Maybe we can enlist someone else to help out with them. Wednesday, December 16, 2009
  24. 24. Enter Hudson Wednesday, December 16, 2009
  25. 25. Installing Hudson Debian-based packages are available for Ubuntu You can also run Hudson on Tomcat (beside a SOLR install, for example.) Hudson on Ubuntu installs your files at /var/lib/hudson/jobs/ [your job name]/workspace/[top-level SCM package] Wednesday, December 16, 2009
  26. 26. Getting Hudson Working Set up source control Set up a build step that installs Drupal Set up a build step that runs SimpleTests via the shell script Set up JUnit reporting Profit? Wednesday, December 16, 2009
  27. 27. Setting Up Source Control Put your SVN (or CVS or Mercurial, etc.) information into Hudson and save either credentials and/or a public/private keypair Set it up to automatically poll your repository for changes if you want automatic builds Wednesday, December 16, 2009
  28. 28. Automatically Installing Make a vhost and a hosts entry for your Hudson checkout You’ll then want to make a shell script that: Runs drush provision to install your site Make sure your install profile installs SimpleTest! Wednesday, December 16, 2009
  29. 29. Automatically Installing A sample script: #!/bin/bash SITE_PATH=/var/lib/hudson/jobs/ProvisionTest/workspace/trunk/ htdocs cd $SITE_PATH /bin/drush --root=$SITE_PATH --master_db_host=localhost -- master_db_user=root --db_type=mysql --db_host=localhost -- config_path=/etc/apache2/drush-conf --restart_cmd=echo -- uri=http://drushtest --script_user=hudson --profile=provision provision install drushtest Wednesday, December 16, 2009
  30. 30. Running SimpleTests Your install profile must install SimpleTest so that you can use the script to run a number of tests. started a SimpleTest patch to output test results in the JUnit XML format - this patch will be in Pressflow soon and is active on d.o: Once you’re running this patch, your Hudson runs will keep track of the number of test passes and fails. Wednesday, December 16, 2009
  31. 31. Running SimpleTests The other half of the same script, to handle automatically running SimpleTests: rm -rf scripts/tests/*.xml /usr/bin/php scripts/ --url http://drushtest --xml scripts/tests Provision Wednesday, December 16, 2009
  32. 32. Hudson Results Build failure: when Drupal could not be installed, drush will return with an error, and Hudson will notify you that the build has failed. Common causes of this could be parse errors, modules that aren’t in source control but are referred to in the install profile, and so on. Unstable: the installation succeeded, but one or more unit tests did not complete successfully. Wednesday, December 16, 2009
  33. 33. Hudson Results Success: Your installation succeeded and all unit tests passed! Wednesday, December 16, 2009
  34. 34. Unit Testing Recipes Test your install profile! This code will let you make a unit test that installs your full install profile (in this case, called “provision”) and can then run tests against the fully-installed environment. Wednesday, December 16, 2009
  35. 35. Gotchas Drush Provision isn’t _really_ meant to install one site many times. If your site’s URL is more than 14 characters, it will fail after the 10th install, since it makes a new database each time. All those databases will eventually slow your MySQL server down, so you manually have to remove them. Make sure that you install Provision for both the Hudson / Tomcat user and the main web user. Make sure you install curl, php-cli and php-curl so that SimpleTests can run from the command line. Wednesday, December 16, 2009