There's another set of really useful helper methods that we haven't looked at yet which are geared towards creating dummy content for testing. Including simple tasks like generating random strings, or random names of a specified length, and more complex things like creating nodes, or even new content types. In this lesson we'll take a lot at the various helper methods in DrupalWebTestCase that assist in the creation of dummy content by creating various pieces of random content and then testing functionality based on that content.
In order to demonstrate these tools lets look at writing some tests for the core statistics module which amongst other things tracks page views for content. In order to perform these tests we're going to need a piece of content that we can view, and then view again to ensure that the statistics are properly updating.
Creating random strings can be done with DrupalWebTestCase::randomName()
and DrupalWebTestCase::randomString()
. Both methods take an argument that allows you to set the number of characters that should be returned, or you can just leave out to get the default. The difference between the two is that DrupalWebTestCase::randomName()
will only use alphanumeric characters and the returned string will always start with a letter. This is useful for things like generating random usernames or email addresses, or other things that don't allow for special characters.
The DrupalWebTestCase::drupalCreateNode()
helper can be used to create nodes. While you could navigate to and fill out the form using SimpleTest this is much quicker and requires far less code. Here's an example of creating a node:
$title = $this->randomName();
$settings = array(
'type' => 'page',
'promote' => 1,
'title' => $title,
'body' => array(LANGUAGE_NONE => array(
array(
'value' => 'Copy goes here',
'format' => filter_default_format(),
),
)),
);
$node = $this->drupalCreateNode($settings);
This will create a new page node with a random title and the string "Copy goes here" in the body field. The array of values passed to DrupalWebTestCase::drupalCreateNode()
should mirror the structure of a node object. If you're unsure what that structure is one easy way to figure it out is to use node_load() to retrieve an existing node and inspect the returned object.
Additional resources
One element of interacting with a web application that we haven't looked at yet is uploading files. How does the DrupalWebTestCase browser attach a file to the the image field when filling out an article form? In this lesson we'll look at the helpers that DrupalWebTestCase has for handling file interactions including how to use the sample files provided in core, attaching files to a file upload input element, and submitting a form with files attached. In order to demonstrate this we'll be writing a test that creates a new article node with an image attached by filling out the article node creation form and submitting it.
You can get a list of the dummy/test files that come with SimpleTest using the DrupalWebTestCase::drupalGetTestFiles()
method. Whenever possible I recommend making use of the provided files.
$files = $this->drupalGetTestFiles('image');
$this->verbose(print_r($files, TRUE));
You'll need to tell SimpleTest what type of files you're interested in, and it'll then return an array of the available files of that type. Valid types are: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'. Check out the DrupalWebTestCase::drupalGetTestFiles documentation for more information.
Once you've got the list of files you can attach files to a form element using DrupalWebTestCase::drupalPost()
by specifying the full path to the file as the value for the file or image field in the $data array.
$images = $this->drupalGetTestFiles('image');
$image_realpath = drupal_realpath($images[0]->uri);
$edit = array(
'title' => 'Test Article With Image',
'files[field_image_und_0]' => $image_realpath,
);
If SimpleTest doesn't contain a suitable file you can use your own by including the file with your module, and then using the full path to that file in your tests.
$custom_file = drupal_get_path('module', 'helloworld') . '/tests/test.csv';
$realpath = drupal_realpath($custom_file);
This lesson also covers the use of DrupalWebTestCase::drupalPostAJAX()
, which allows for the simulation of an AJAX request using the SimpleTest browser. The primary difference between it and the drupalPost method is you need to specify the name of the element that triggers the AJAX request.
$this->drupalPostAJAX('node/add/article', $edit, 'field_image_und_0_upload_button');
Additional resources
Due to the tightly coupled nature of much of Drupal 7's code most of the test suite is based around functional tests and not unit tests. However, there are a handful of unit tests in core as well as the DrupalUnitTestCase
class that we can extend to write our own unit tests. Since unit tests are an important part of a complete test suite lets take a look at how we can extend the DrupalUnitTestCase
class and add some unit tests to our module. While functional tests do a good job of telling us that something is broken and we should fix it they often are not very helpful for locating the exact problem. Unit tests on the other hand are much more specific and can generally tell us both that something is wrong, and what is wrong.
Unit tests don't have access to the database or the file system since they are not run in the context of a fully bootstrapped Drupal environment. Note that calling any Drupal functions which attempt to access the database will result in an exception being thrown and cause your tests to fail. This includes functions like watchdog()
, and module_implements()
. However, because Drupal doesn't have to be bootstrapped, and we don't have to create an entirely new environment, unit tests are a whole lot faster to execute than functional tests.
In this lesson we're going to take a look at writing a unit test for a function in our module that is used to convert a length of time in seconds to a string like 1 hour 6 minutes. We'll start by looking at the code that does the conversion, and then come up with a list of known inputs and their expected outputs. Finally, we'll write a unit test by adding a new .test file that contains our unit tests and bombarding our conversion function to make sure it's working properly.
Additional resources
So far all the tests that we've written are testing the functionality of our module in the context of a fresh Drupal install. However, in the real world we're also going to want to have tests that test the content of our application. As well as other things that it's unrealistic to assume we'll also have the time and resources to write a complete installation routine to replicate. Instead, it would be nice if we could test against a clone of our site rather than a from scratch installation of Drupal.
The SimpleTest module included with Drupal 7 core doesn't support this feature. However, the 7.x-2.x version of the module in contrib has a DrupalCloneTestCase
class that we can extend instead of the usual DrupalWebTestCase that operates on a clone of the database from our existing site. Allowing us to test things like, "Does the about page exist.", and other mission critical features of our site. In this lesson we'll take a look at installing the SimpleTest module from contrib, and writing some new tests using the 2.x version of the API in order to test an existing website.
Note that after you install the 2.x version of the SimpleTest module none of the tests we've written so far, or any of the tests from core will be in the list of available tests to run. This is because each tests needs to explicitly declare that it is compatible with the 2.x version of the test suite. This is done in the .info file of the module that provides the tests.
Add this to your .info file:
testing_api = 2.x
As of the time that this video was recorded you also need to apply the patch in this SimpleTest issue. The current version of the patch to use is https://www.drupal.org/files/issues/983266-10-simletest-clone.patch. Without it the DrupalCloneTestCase setup method won't properly clone the tables from your existing site. You should always use whatever the latest working patch is from that issue.
Here's how I patched it:
curl -O https://www.drupal.org/files/issues/983266-10-simletest-clone.patch
patch -p1 < 983266-10-simletest-clone.patch
Additional resources
Patched with patch from issue #983266
In this short series you will learn how to find and evaluate Drupal modules for your project. This first tutorial will show you where to find modules on Drupal.org, and various ways you can use Drupal.org's search tools.
Additional resources
Drupal.org Modules page
Drupal module categories
Similar modules group
Drupal case studies
Planet Drupal
An open source project’s strength comes from the power of its base of contributors, and a Drupal project is no different. Although every line of code added or changed in Drupal core goes through rigorous peer review, contributed modules are more of a Wild West where anyone who jumps through a few basic hoops can add modules for everyone to download. Whether or not a module is well maintained, its overall code quality, and how well used it is in the overall community are all important factors for you to consider when selecting modules. This tutorial will talk about determining these factors by closely inspecting the tools Drupal.org provides, starting with the central feature of all Drupal modules: the project page.
Honeypot module helps you stop spammers in a different way than the typical spam modules that put up barriers, like captchas. Spambots will typically hit a form like the user registration page, fill in all the fields, and submit the form. Honeypot adds a hidden field to the form that users won't see, but spambots will. If that form is filled in, you know you've found a bot and the submission is discarded. This tutorial will give you an overview of the Honeypot module, including the configuration and seeing how it works. This tutorial is based on a Lullabot.com Module Monday post.
Additional resources
Drupal's revisioning system is really powerful. Out of the box we can keep track of changes in our content and restore to a previous version with just a couple clicks. However, on large sites with a lot of activity in their content, revisions can grow exponentially up to a size that it can compromise performance and disk storage. The Node Revision Delete module can help us to keep this under control. In this tutorial we'll walk through an overview of this helpful module. This tutorial is based on a Lullabot Module Monday article.
Additional resources
Lullabot.com Node Revision Delete article
Node Revision Delete module
Drupal's custom fields allow site builders to tweak out their content types with all kinds of data: phone numbers, file uploads, maps, and more. When it comes to tabular information, though, most of us fall back on simple HTML tables in the body field. The TableField module aims to fix that by storing and editing data tables with a single consolidated Drupal field type. In this tutorial we'll give you an overview of how to use the TableField module. This tutorial is based on a Lullabot Module Monday article.
Additional resources
Many businesses, both large and small, would like to take better advantage of their web presence by selling their products or services directly online. Setting up ecommerce, however, can be a very daunting task. In this series you will create an online t-shirt shop, and in the process you will learn how to use the basic pieces of Drupal Commerce, including setting up a Paypal payment system, along with an introduction to the Feeds module, for importing our catalog, and the Rules module, to set up taxes. In this lesson we'll kick things off by reviewing the requirements for the Sweet Tees store, and discuss how we'll be going about implementing them.
Additional resources
Drupal Commerce is a complete package for running an online store. As such, it actually contains numerous submodules that each implement features of an online store, and can be turned on or off depending on the precise functionality required. In this section, we’ll look at each module in turn, and outline its purpose and where it fits. In this lesson we'll get an overview of the major components of Drupal Commerce, including:
- Carts, checkout, taxes, and payment
- Customers and orders
- Products and pricing
- Additional Drupal Commerce add-ons
Additional resources
Before we get to the first step in creating our store—adding products—it’s worth taking some time to discuss and understand how Drupal Commerce treats products within the system. While product management may appear unintuitive at first, the product management features in Drupal Commerce are designed to allow for maximum flexibility. In this lesson we'll explain what product types are, and take a look at both the required fields that come with them, and the custom fields you can add.
Additional resources
The first step in setting up our online t-shirt store is to turn on a formidable array of modules to enable the Drupal Commerce module, and then set up product types for the merchandise that Sweet Tees sells: T-shirts and stickers. In this lesson we'll get the right modules enabled, configure our two product types, and then create some sample products to make sure it's all working the way we expect.
Additional resources
The Feeds module provides the capability to ingest data from any number of different sources and transform it into native Drupal constructs. It’s a versatile tool that can be used for things such as large-scale data migration, automatically populating a block of recent content from external sites, or synchronizing content posted among multiple sites. In this lesson we'll learn how Feed importers are created with the three main pieces of Fetchers, Parsers, and Processors.
Additional resources
Using Drupal, 2nd edition
Using Drupal source code
Feeds project
Now that we’re familiar with the Feeds module’s data import tools, let’s turn our attention to importing t-shirt products for Sweet Tees. Sweet Tees has kindly furnished us with a comma-separated values (CSV) file containing all of their t-shirt product data for this purpose. CSV is a common data export format from databases and spreadsheet programs. A CSV file is a simple text file that encodes a data set with each record on its own line, and a separator (such as a comma or tab) between the data values. In this lesson we'll create a feed importer for CSV files, map the CSV data to our Drupal Commerce products, and then actually import the CSV product data into our store.
Additional resources
Now that all of this groovy product data is showing up fine in the administrative interface, there’s just one problem: how do we expose these products on our public website to our customers? And further, how do we avoid displaying a product catalog with 25 entries in it, when from a customer’s point of view we only sell four products (three t-shirts and a sticker)? In this lesson we'll get an overview of how Drupal Commerce handles this with the concept of product displays and product attributes.
Additional resources
In this lesson we'll put what we just learned into action by creating a content type for product displays, and a few sample products to get the hang of how things work.
Additional resources
In this lesson we'll create a nice interface for browsing our products. We will categorize our product displays using the taxonomy system to get our like items grouped together. We'll walk through the process for creating a catalog vocabulary and terms, and then configure the content type and product type to bring it all together.
Additional resources
With our products nicely categorized, and the individual items looking good, the last step is to design a top-level catalog overview page using the Views module. In this lesson we'll create a catalog view and use relationships to pull in our product data. We'll also need to aggregate multiple products into one item so we don't have a crazy list of every possibility as individual items, and we'll wrap it all up nice and tidy by grouping the products based on taxonomy.