"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
Continuous Integration and Drupal
1. CONTINUOUS INTEGRATION
Automatically Install, Test, and Deploy Your Drupal Site
Wednesday, December 16, 2009
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Basic Assertions
The main assertions that are built into the UnitTestCase class
(from which DrupalWebTestCase inherits) are documented at
http://simpletest.org/api/SimpleTest/UnitTester/
UnitTestCase.html .
Wednesday, December 16, 2009
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 http://drupal.org/node/
265762 .
Wednesday, December 16, 2009
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. 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. 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. 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. 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
25. Installing Hudson
Debian-based packages are available for Ubuntu
http://hudson-ci.org/debian/
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. 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. 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. 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
30. Running SimpleTests
Your install profile must install SimpleTest so that you can use
the run-tests.sh script to run a number of tests.
ComputerMinds.co.uk 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: http://drupal.org/node/602332
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. Running SimpleTests
The other half of the same script, to handle automatically running
SimpleTests:
rm -rf scripts/tests/*.xml
/usr/bin/php scripts/run-tests.sh --url http://drushtest --xml
scripts/tests Provision
Wednesday, December 16, 2009
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. Hudson Results
Success: Your installation succeeded and all unit tests passed!
Wednesday, December 16, 2009
34. Unit Testing Recipes
Test your install profile!
http://gist.github.com/249754
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. 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