Before we can start building a search application we need some sample data that we can index and use for testing, not to mention a site we can use to test this all out on. In this tutorial we'll walk through installing Drupal 7 and importing some sample data.
In order for this to work I built a Drupal 7 site with a content type named Fish, and then imported a whole bunch of descriptions of various fish from Wikipedia. You should be able to use the provided database dump in order to get up and running with a sample Drupal site pre-populated with some sample data.
If you're not planning on following along and building the fish finder application in the Search API and Solr series, or are planning on implementing Solr search on your own site instead you can probably skip this tutorial. Just note that the rest of the tutorials in the series assume you've got a working Drupal 7 site with some content.
By the end of this tutorial you should have a working Drupal 7 site with sample content running on your localhost for playing with while watching the rest of the series.
Solr is an application that runs on it's own, independent of Drupal. So before we can integrate our Drupal site with the Solr server we first need to install Solr and get it running. In this tutorial we'll walk through the requirements for installing Solr and then look at a quick and easy way to get it running on our localhost for development purposes. This setup is best for when you're working on a site locally, but have it hosted elsewhere and someone else is managing your Solr install for you.
The basic process is to download the Solr application, unpack it somewhere on your localhost, and then run the start.jar file that comes with the example application: java -jar start.jar
. To ensure it's working you can connect to the Solr web UI on your localhost at http://localhost:8983/solr/.
Note: When using Solr with Drupal and the Search API module we currently need to use the most recent 4.x version of Solr, despite the fact that there is a 5.x version that just came out. At the time these tutorials where recorded the Search API module had not yet been updated to work with Solr >= 5.x. The general installation instructions for localhost development however are not likely to change much at all between Solr 4 and 5 so even if you're using Solr 5 this should still be relevant information.
By the end of this tutorial you should be able to run Solr on your localhost and verify that it is working.
Additional resources
If you're installing Solr on a production server there are some additional considerations you'll want to be aware of. The most important thing is that instead of just running the Solr application directly you'll likely want to run it inside of a J2EE server container like Tomcat or Jetty. In this tutorial we'll look a installing Solr on Ubuntu using the same process that we would recommend for a production installation. We'll do this by starting with a Vagrant VM with nothing but Ubuntu installed and walk through the steps required to get Solr up and running.
The steps are going to be similar, but different depending on your OS of choice, and it would be impossible for us to cover them all. So here's a general outline of what you'll want to do:
- Install Java. Note, you need the full JDK for Solr not just the JRE.
- Install a J2EE application server like Tomcat or Jetty.
- Download the version of Apache Solr you want to use.
- Configure your J2EE application server to run Apache Solr.
- Configure your system to automatically start/stop your application server.
We'll be following along with the instructions provided in this excellent blog post by Ben Chavet over on Lullabot.com, though some of the commands will be a little different since software versions have changed, etc. If you prefer to follow along with written instructions, or want to have the list of commands next to you when it comes time to set things up for real, give that post a read.
A note about security. We will NOT be going over how to restrict access to the Solr instance on the Ubuntu server as that's outside the scope of this tutorial. However, it's important that you do so. At a minimum you'll want to configure your firewall to prevent access to the Solr host from anything other then the web server where you Drupal site lives.
By the end of this tutorial you should be able to list the requirements and considerations that need to be evaluated when installing Solr on a production server.
Things you might want to copy/paste if you're following along:
Content of Tomcat context file - /usr/local/tomcat/conf/Catalina/localhost/solr.xml
:
<Context docBase="/usr/local/tomcat/webapps/solr.war" debug="0" crossContext="true">
<Environment name="solr/home" type="java.lang.String" value="/usr/local/tomcat/solr" override="true" />
</Context>
Content of the Solr definition file - /usr/local/tomcat/solr/solr.xml
:
<?xml version="1.0" encoding="UTF-8" ?>
<solr persistent="false">
<cores adminPath="/admin/cores">
<core name="drupal" instanceDir="drupal" />
</cores>
</solr>
Additional resources
In this tutorial we'll walk through downloading and installing the Search API module, the Search API Solr module, and their dependencies. Then we'll look at using the Search API Solr configuration files with our Solr server. These configuration files are specially crafted to help with indexing data contained in a Drupal site and allow Solr to have a better understanding of Drupal's entities, fields, and the data that they contain. For example, mapping a Drupal Field API body field on a page node type to the appropriate field type in Solr.
The gist of this tutorial is, locating the Solr configuration files in the search_api_solr/solr-conf/
directory, talking a little bit about what each one does, and then demonstrating how to copy those files into the configuration for your Solr server so that Solr will start using them.
After looking at the various configuration files, and then placing them into our Solr instance, we'll connect Drupal to our Solr server by creating a new Search API server configuration within Drupal's UI. This will allow us to confirm that our Apache Solr server, and Drupal, will be able to talk to one another.
By the end of this tutorial you should be able to configure Solr to work with Drupal, connect the two, and verify that the connection is working.
Additional resources
In order for Solr to provide search results we need to first send our Drupal content to the Solr server so that it can be indexed. In this tutorial we'll look at connecting the Search API module with the Solr server and creating an index that maps content in Drupal to data types in Solr. We'll also look at how the various configuration options effect the Solr index.
After creating a Search API index configuration we'll look at running the indexer, essentially queuing all the content on our site for indexing, and then telling Drupal to send the documents on our site to the Solr server for indexing. This can be done either via the UI or with Drush. You can choose to index content as it's created, or for sites with higher rates of new content you can send it to the indexer in periodic batches. Whichever you choose, making sure that you've got a system in place for periodically sending items queued for indexing to Solr is a critical step.
By the end of this tutorial you should be able to send content from Drupal to Solr for indexing, and verify that the content is showing up in the Solr server's index.
The Search API module by itself doesn't provide a UI for submitting a search query, or a page for displaying results. Instead, it exposes an API that other modules can use to provide those features. This makes it super flexible, but it also means we've got some extra work to do in order to allow someone to actually perform a search and see the results.
In this tutorial we'll look at using the Search API Pages module to create a simple search page with a form at the top and a list of results ordered by relevancy. Search API Pages is the quickest and easiest way to replace the Drupal core search module's functionality with a form that uses Solr for a search backend instead of MySQL.
When creating a new page with the Search API Pages module we can choose the view mode that we would like to use for displaying results. It works very nicely with Drupal's built-in view modes, as well as contributed modules like Display Suite, in order to allow for a high level of customization of view modes, and thus of the displayed results.
You can also configure the query type to use, choosing from one of: multiple terms, single term, or direct query. For integration with Solr you'll likely want to choose direct query, and allow Solr to handle the query parsing since it has a lot of advanced options that go far beyond what Search API handles on its own. However, we'll look at the different query type configurations, and demonstrate things we can do with direct query searches and the powerful Solr query syntax that we can't do with the other modes.
Finally, we'll look at the block that Search API Pages provides, and use it to replace the search form on the home page of our site with a form that points to our new Search API Pages search results page.
By the end of this tutorial you should be able to expose a page on your site that will allow your visitors to perform a search using the Solr index and have the results displayed in Drupal.
Additional resources
There are a couple of configuration options available when configuring a Search API index that we haven't looked at yet: adding additional fields, and using boost values to increase the relevance of a keyword when found in a specific field.
Solr allows you to index any number of additional fields, so we'll add a species and genus field to our index. This is one of the reasons using Search API to interface with Solr is so great. Through it's use of the Entity API, the Search API module has a deep understanding of all the content types on your site and the fields that are attached to them, without you having to write any code, or do anything other than configure things in the UI.
One of the benefits of creating your own search index is that you know your data better than anyone, and you know what people are hoping to find in your content. Solr allows you to configure a boosting value that can be used to increase the relevancy of keywords found depending on where in the data it's located. For example, when someone searches for a keyword we can probably assume that if the keyword is in the page title that the keyword is worth more relevancy points than if the keyword is found in the page body. With boosting we can affect the relevancy ranking of results and help our users more quickly find what they are looking for.
By the end of this tutorial you should be able to add additional fields to your Solr index so their content is available for searching, as well as assign a relevancy boosting value when keywords are found in specific fields.
The Search API module supports a handful of data alterations and processors; additional operations that can be performed on a document before it's indexed or during the display of search results. While Solr actually handles the majority of these for us already, this tutorial will look at the available options, talk about what each one does, and explain which ones are still relevant when using Solr as a backend.
Looking at data alterations in the Search API module also raises an important point about security. By default, Search API doesn't care about your content's access control settings. In order to prevent people from seeing results for their searches that contain data they shouldn't have access to we need to make sure we account for that in our configuration.
Here's a good list of the currently available data alterations and processors, though it's worth noting that not all of them are available for all search backends. Also, as we'll see, not all of them are recommended when using Solr even if they are available. Solr's tokenizer for example is much more full featured than the Search API tokenizer, so when using Solr as a backend it's best to keep the Search API tokenizer turned off and let Solr do its thing.
By the end of this lesson you should be able to use data alterations and processors to filter out specific content types from your Solr index and to highlight keywords found when displaying search results. You'll also be able to explain why some alterations and processors are better left off so that Solr can handle those tasks directly.
Additional resources
Being able to display search results using the Views module provides a huge amount of flexibility with respect to what is listed, what it looks like, and more. In this tutorial we'll look at using the Search API Views module, included in the Search API project, to create a view that allows users to search our Solr index and display the results as a table, or really, in any other way that Views can display content. We'll also cover some special considerations regarding access control and entity relationships that we need to keep in mind when using Views to display search results.
The biggest difference between creating a view that lists a bunch of nodes, and one that displays search results is that you need to use your Search API index as the base table from which you're building your view. Then, by default, the view only has access to the fields that are in the Solr index. This allows you to build the entire view without having to query the database. Or you can use the Views module's ability to define relationships to other buckets of content to query the database and pull in additional information. There's a huge amount of flexibility.
When building views from the Solr index you can optionally expose one or more filters. Essentially creating a form that allows someone to construct a search query. This can be as simple as exposing a keyword text field, or as complex as you would like to get. We'll look at using exposed filters to create a form that users can perform a search with and create a more complete search experience. We'll also look at how you can move those exposed filters into a block that can be displayed on the home page of our site, allowing us to replace the functionality provided to the Drupal core Search module with Views and Solr.
By the end of this tutorial you should be able to create a view that displays search results using the Search API Views module.
Additional resources
Using facets allows users of your search application to further narrow the results returned from a keyword search by selecting one or more attributes of the returned content and saying either show me only these, or show me everything but these. In this tutorial we'll take a look at some examples of faceted searching in practice, and then we'll use the Facet API module to expose facets for our genus and species fields.
One of the most common uses of facets is on e-commerce sites like Zappos.com that have huge collections of products that users can browse through, and narrow down, to focus in on exactly the pair of shoes they are after. In this example facets allow you do to things like narrow the results returned from your initial keyword search to just shoes for men, which are brown, size 10.5, and on sale. You can can also see faceting in action any time you perform a search on our site.
We'll use facets to allow users of the fish finder application to limit the results returned to just those of a specific species or genus. In doing so we'll also look at the options available for determining how facets should be displayed, whether or not we should show a facet that has zero documents in our result set, and how to combine multiple facets together into a single query using either AND or OR logic.
By the end of this tutorial you should be able to use the Facet API module in conjunction with Search API in order to provide facets that your users can use to further narrow and refine their search results.
Additional resources
Depending on the data that is being searched, some shorter general words, like "a", "the", or "is" can adversely effect search result relevancy. Consider the word "the", which in a standard description of a fish in our database could easily appear hundreds of times or more. When a search is performed, part of the algorithm that calculates the relevancy of any document in the index is to count the number of times a word appears in the text being searched. The more often it appears, the more relevant the document. Words like "the" however often have little to no real bearing on a document's actual relevancy. These words should instead be excluded from the ranking algorithm.
Stop words can also serve another purpose. You can filter out words that are so common in a particular set of data that the system can't handle them in a useful way. For example, consider the word "fish" in our dataset. It's probably very common. With only 500 fish being indexed it's not really going to make much difference, but what if we were indexing five million fish, and each one had the word "fish" in the description even just five times? That's 25 million occurrences of the word "fish". Eventually we might start to hit the upper limit of what Solr can handle. The word "fish" in this case is probably also not very useful in a search query. You're browsing a fish database. Are you really likely to search for the query fish and expect any meaningful results? Likely it would instead return every result. It would be like going to Drupal.org and searching for the word "drupal" and expecting to get something useful. Not going to happen.
Solr has the ability to read in a list of stop words, or words that should be ignored during indexing, so that those words do not clutter your index and are removed from influencing result relevancy. In this tutorial we'll take a look at configuring stop words for Solr.
First, we'll use the Solr web UI to see the most common terms in our index for the body field. Then, based on that list, and the list of common stop words provided by the Solr team, we'll configure our stopwords.txt file. Finally, we'll re-index all the content of our site so that it makes use of the new stop words configuration and re-examine the most common terms noting that our stop words no longer appear in the list.
By the end of this tutorial you should be able to use the Solr web UI to get a list of the most common terms in your index, and know how to add terms to Solr's stopwords.txt file to prevent them from showing up in your index.
Additional resources
Solr provides the option to configure synonyms for use during both indexing and querying of textual data. A synonym is a word or phrase that means exactly or nearly the same thing as another word or phrase in the same language. For example, shut is a synonym of close. Synonyms, if not accounted for, can cause a dilution of search result relevancy when searching for a keywords that have lots of variations in your index.
Consider for example the words, "ipod", "i-pod", and "i pod". It's pretty easy to imagine a scenario in which the content of our site could contain all three variations of the word. When someone searches though they are likely just going to search for one, but expect results for all three. In order to not break those expectations we need to make sure we account for this scenario. Another example from the the Drupal world would be the terms "CMI" and "configuration management". Chances are if you search for one you would be happy to see results for the other.
In this tutorial we'll look at using the synonyms.txt file that is part of our Solr configuration in order to account for synonyms in our data. Of course the exact words you use will depend on the content of your site, but we can at least cover how they work and how to configure them.
By the end of this tutorial you should be able to configure Solr to be aware of synonyms in your data in order to improve the quality of your search results.
Additional resources
One of the benefits of building our own search application is that we have ultimate control over the ranking of items. Combined with our superior knowledge of our own content we can use this to ensure that when someone searches for a specific keyword we bubble our best content for that term to the top of the list, regardless of whatever Solr might rank it based on its internal algorithms. This is commonly referred to as promoted, or sponsored, results; the artificial boosting of a particular document to the top of the result list for a specific query.
A similar, but not exactly the same, example would be sponsored results on Google searches, where you can pay to have your page listed at the top of the results for a specific keyword or set of keywords. We are going to be doing all of this except for the part where we let people pay to promote results, though you could certainly build that part on your own if you need that.
Solr uses a configuration file named elevate.xml, in conjunction with a processor, to elevate results at the time a query is performed. We can promote specific documents in our Solr index by figuring out the unique Solr ID for a document and then adding it to the elevate.xml file along with some information about a query, or queries, this document should be promoted for.
In this tutorial we'll learn how to find a Solr document's unique ID, and then configure Solr to use an elevate.xml file that will promote the "How to Use the Fish Finder" page to the top of the results when someone searches for the term "fish". This configuration is all within the Solr application itself and doesn't really rely on Drupal in anyway. As such, the material in this tutorial should be applicable to your Solr search applications even if you're not building them with Drupal.
By the end of this lesson you should be able to configure promoted documents in your own Solr-based search application.
Additional resources
Testing, and quality assurance, are an important part of maintaining high-quality software. Together they ensure that the code we write functions as it is intended to, and that changes made later on do not introduce any regressions into our application. Its tedious work and generally involves coming up with a list of tasks that prove a specific feature is working, and then repeating the list of tasks every time a change is made.
Automated testing is the process of writing code to reduce the number of things we need to do manually when testing our software. That list you created to ensure your software is working correctly is likely something that can be easily automated with a testing framework like SimpleTest. Doing so can dramatically reduce the amount of time it takes to perform QA, and improve the overall stability of our applications.
The SimpleTest testing framework was introduced into Drupal core early on in the Drupal 7 development cycle. Since then it has had a profound impact on the way our community develops Drupal. Core now contains a whole suite of tests that cover somewhere in the neighborhood of 75% of the softwares functionality. Every time a new features is introduced, or a bug is fixed, this battery of tests confirms that the fix to the date formatting function didn't inadvertently break something else.
In this series we'll learn to write our own automated tests for Drupal 7 using SimpleTests. We'll walk through enabling SimpleTest, and running one or more test cases with both the SimpleTest UI, and with drush. Then we'll cover some of the related terminology like the difference between functional tests and unit tests. As well as discuss why testing is an important part of development and worth the investment.
Then we'll walk through writing a test case and a set of assertions in orders to verify that various features of our site are working. We'll use functional tests based on the DrupalWebTestCase
class, which simulates a browser navigating our site and clicking on links, and filling out forms. And the DrupalUnitTestCase
class, which allows us to write super fast unit tests for the business logic contained within our code.
After watching this series you should be able to:
- Install the SimpleTest module and run the tests included with core.
- Define and know the difference between functional tests, unit tests, test cases, and assertions.
- Create a new test case so that SimpleTest can discover and run your tests.
- Write assertions that test the validity of your application in a given state.
- Use the test browser to click on links and navigate to pages on a site.
- Fill out and submit forms and AJAX form elements using the test case browser.
- Handle file and image uploads in test cases.
- Write unit tests, and understand how the workflow for running unit tests differs from that of functional tests.
- Use the SimpleTest 7.x-2.x module from contrib to test existing websites.
This series assumes that you have basic understanding of Object Oriented PHP, and are familiar with Drupal module development. If you need to brush up on your module development watch our series on Drupal 7 Module Development. And, while not required to run tests, we do make use of drush in this series so knowing how to run drush commands is helpful.
Writing tests can be a challenging. But it doesn't have to be. And the benefits of doing so are huge. Especially as our web applications become more complex, as our teams grow, and as the multitude of ways in which people interact with our web applications changes constantly. Spending some time learning how to write tests will not only make your applications more reliable, it'll also make you a better developer.
Additional resources
Developers write tests for a variety of different reasons and understanding why, and how, writing tests can improve the quality of our code and our sanity helps motivate ourselves to write tests. The tests we write usually take one of two forms. Functional tests are used to test an applications behavior. When I click this link does it take me to the about page? Unit tests are used to verify the logic within a small segment, or unit, of code. When I call this function that is supposed to return a link with these specific parameters does it return the correct data? There are a ton of reasons that investing time and resources into writing automated tests is a big win in the long run and in this lesson we'll cover a bunch of them, including:
- Eliminate repetitive tasks.
- Improve overall stability of an application.
- Ensure critical paths & edge cases function.
- Reduce (not eliminate) the number of tests that need to be performed manually.
Types of Tests
There are are two main types of tests and we're going to be writing. Functional tests, and Unit tests.
Functional tests, also known as integration tests, test the functionality of a particular aspect of an application by simulating normal interaction with that system. In the case of web software this usually refers to simulating a user navigating and interacting with the website as if they where using their browser, keyboard, and mouse.
Unit testing is the art and practice of taking a small portion of code, a unit, and subjecting it to programmatic tests to prove its correctness. For example, testing a function that performs unit conversion by comparing a known input with an anticipated result.
UPDATE: This video references qa.drupal.org which is no longer in use. On Friday, October 23rd 2015 - qa.drupal.org received and processed it's last
test. The qa.drupal.org testbots have now been superseded by DrupalCI - and test results are now displayed directly on Drupal.org. While the mechanics are different now, the end result is still all patches to Drupal core are automatically tested.
SimpleTest is the tool that Drupal 7 core uses for discovering, and running all of it's tests. SimpleTest is also the name of the system that is used to write tests that can be run by the SimpleTest module. Understanding how SimpleTest locates the code that makes up our tests, and then executes that code, is an important part of understanding how to write tests. In this lesson we'll be discussing the pieces that make up the SimpleTest toolkit, some related terminology, and some best practices for dealing with SimpleTests in Drupal.
Methodology
Some best practices to keep in mind when working with SimpleTest and writing tests.
- Tests should always be run in a separate environment from your production site.
- Each test case sets up a completely new Drupal env, as such your test case needs to prepare the new environment as appropriate for your tests. Things like enabling any necessary modules, creating dummy content, and users etc. For this reason, it's best to have tests that require a similar setup be part of the same test case for better performance.
- After a test case is run the environment is torn down and started fresh for the next test case. This ensures there is no contamination between test cases and that each test can count on knowing exactly what to expect from the environment it's running in.
- SimpleTest is not always ideal for doing functional testing of your existing site. Drupalize.Me for example, we want to test that our About page is visible. But that's content, and doesn't live in code anywhere so creating it at install time can be tricky: We'll cover this scenario in more depth later.
- Always test both sides of the problem. If your site only allows authenticated users to make comments, and you write a test to make sure authenticated users can comment you'll also want to make sure you write a test that verifies that anon. users can not comment. If you're checking that a value exists under specific conditions, also verify that it doesn't exist when those conditions are not met
Terminology
SimpleTest: The original name of the module used for testing in Drupal. Though it's now called just "Testing", the terms are used interchangeably. Based on the PHP SimpleTest library.
Test Case: A set of conditions under which the tester will determine whether the system being tested satisfies requirements and works correctly. A Test case defines the environment in which a test or set of tests will run. Including preconditions such as what modules are enabled, what content exists, etc. It is assumed that these same preconditions are true for all individual tests within a test case.
In Drupal, all test cases are classes that extend the DrupalWebTestCase
, or the DrupalUnitTestCase
class.
Test (Test method): Each individual test checks the functionality of one discreet thing. Example, if our test case is "User cancellation" there are many variations of cancellation that we need to test. Cancel the account and keep their content, cancel the account and delete their content, don't allow cancellation of the account with UID 1, etc. Each of these individual tests is checking one piece of functionality with the test case.
Assertion: The smallest unit of a test. An assertion is used to check wether or not a given condition is true in the current context.
Additional resources
Before we get into writing our own tests we need to enable the SimpleTest module, and while we're at it we may as well talk about the various configuration options it has. And then why not run a few of the tests that come with Drupal core as a way to learn how to discover and run all the tests, a group of tests, or even an individual test case.
The SimpleTest module comes with Drupal 7 core, but is not enabled by default. So we can start by enabling the module. On the administration page for enabling modules the module is listed as "Testing" instead of SimpleTest which can be a bit confusing. The the UI you'll almost always see the name "Testing", but in the code, documentation, and at the command line it's generally referred to as SimpleTest.
Once the module is enabled there are just a few new permissions and configuration options to cover. The new 'run tests' permission, and a couple of settings related to enabling verbose output for debugging of tests. Make sure you check them all out.
Tests can be run in a variety of different ways, either from your browser using the Testing module's UI, or at the command line with drush. We'll cover both of them in this lesson. And in doing so we'll look at how to run a group of tests, or an individual test case, and the output that's generated by running tests.
I've also got a module (which you can download from this page) that contains a couple of failing tests. This way we can run a test case that fails, and see what the output looks like from a failure. SimpleTest outputs some helpful debugging information, and with the verbose option enabled will even save the generated HTML of each page the SimpleTest browser saw so that we can review it after the test run is complete.
If you're not familiar with drush checkout our series on using drush.
By the end of this lesson you should be able to enable and configure the simple test module and run some tests.
Example drush commands
Run all tests in the "Block" group:
drush -l http://localhost/demos/simpletest-7x/docroot test-run Block
Run just the "BlockHashTestCase" test case:
drush -l http://localhost/demos/simpletest-7x/docroot test-run BlockHashTestCase
Note: as of Drush 7 the drush test-run
command is no longer part available. See https://github.com/drush-ops/drush/issues/599.
You can either, use Drush 6, or use the scripts/run-tests.sh
scrip that comes with Drupal core. See the documentation for the run-tests.sh script for more information.
In this lesson we'll write a hello world test that navigates to the front page of a standard Drupal installation and verifies the existence of the text, "No front page content has been created yet.". This will allow us to walk through creating a .test file, adding it to our modules .info file, as well as cover the basics of extending DrupalWebTestCase
to write our own test case. For this lesson we'll use the helloworld sample module as a starting point and expand it to add a very basic test. The goal being to gain a better understanding of how the SimpleTest module discovers, and runs our tests. Then in future lessons we can expand on our test case to add more robust features.
All SimpleTest tests are an extension of either the DrupalWebTestCase
(we'll focus on this one), or the DrupalUnitTestCase
base classes. There are two pieces to the discover process. Writing a new class that extends one of the base classes, and then declaring the file that contains this new class in your module's .info file so that the file and it's code are indexed and inserted into the registry.
Create a new file inside the helloworld module's directory: tests/helloworld.test. The convention is to name files that contain test classes with a .test extension. Similar to how module file, despite being purely PHP, are named with a .module extension.
Then we'll add a basic skeleton test class like so:
class HelloworldTests extends DrupalWebTestCase {
/**
* Metadata about our test case.
*/
public static function getInfo() {
return array(
'name' => 'Hello World',
'description' => 'Tests for the Hello World module.',
'group' => 'Hello World Group',
);
}
}
Our class includes a getInfo()
method, it's required, and it returns a simple associative array with meta-data about our test. This information is used by the SimpleTest UI and other test runners to provide human readable information about our tests and to do things like group like tests together. When you view the lists of tests in the SimpleTest UI, you're seeing the 'name', and 'description' provided by your test classes' getInfo() method.
All tests should have a setUp() method as well. This method is called by the test runner after Drupal has been installed, but before our tests are run. It gives us the chance to perform any additional configuration on the environment within which the tests are running. In our case, we need to enable the helloworld module since it's not part of the default install profile. We can do that by delegating to the parent classes' setUp() method and passing an array of modules we want enabled. That might look something like this:
/**
* Perform any setup tasks for our test case.
*/
public function setUp() {
parent::setUp(array('helloworld'));
}
Finally, we need to provide at least one method whose name starts with "test". This method, and all others whose name starts with test will be called in succession by the test runner and are expected to contain the assertions that make up our individual test.
public function testHelloWorld() {
$this->drupalGet('helloworld');
$this->assertText('Hello World. Welcome to Drupal.', 'The page content is present.');
}
This test will navigate to the url /helloworld, and then verify that the text "Hello World. Welcome to Drupal.", is displayed on the page.
In order for our test to show up in the SimpleTest UI we need to add it to the registry. We can do so by modifying the helloworld.info file and adding the line files[] = tests/helloworld.test
to the file. Then clear the cache and Drupal should discover your new test case and allow you to run the tests either through the SimpleTest UI or with drush.
Additional resources
DrupalWebTestCase documentation
Written tutorial based on this video
Assertions are like the answer sheet a teacher uses when grading a multiple choice test. When we write tests our primary objective is to check, or assert, that the state of some system, or the value of some variable, in the specified context matches our expectations. Like saying, "I expect that when I break this cookie in half I'll see chocolate pieces.". If you break the cookie and they are not there you might then decide that this particular cookie does not pass the chocolate chip cookie test. And flag it for review.
There are a lot of different types of assertions that we can make when using SimpleTest and this lesson attempts to demystify what each of the different types of assertions does. There is some really good documentation about the various types of assertions and some examples of using them in the Assertions documentation page on Drupal.org.
Not all of the assertions in SimpleTest are listed there though. For that, your best bet is to look at the API documentation for DrupalWebTestCase, and use the filter field at the top of the table to limit the list to only methods that start with the keyword "assert". This will give you a complete, and up-to-date list of all the assertions.
Basic assertion types reference:
Checking the value of something
- assertTrue
- assertFalse
- assertNull
- assertNotNull
- assertEqual
- assertNotEqual
- assertIdentical
- assertNotIdentical
Checking for the presence of something
- assertPattern
- assertNoPattern
- assertRaw
- assertNoRaw
- assertText
- assertNoText
- assertTitle
- assertNoTitle
- assertUniqueText
- assertNoUniqueText
- assertLink
- assertNoLink
- assertResponse
Checking form elements
- assertFieldById
- assertNoFieldById
- assertFieldByName
- assertNoFieldByName
- assertFieldChecked
- assertNoFieldChecked
- assertOptionSelected
- assertNoOptionSelected
Additional resources
If we're going to do functional testing of the various pages in our application we'll need to be able to navigate to the pages in question. The DrupalWebTestCase's built in browser provides us with two different ways to navigate our site. In this lesson we'll explore using the DrupalWebTestCase'::drupalGet()
, and DrupalWebTestCase::clickLink()
functions in order to navigate throughout our application. The former is useful when we know the exact URL of the page we want to navigate to and the latter is useful when we don't necessarily know the URL but we know which link to follow to get there.
I like to think of DrupalWebTestCase::drupalGet()
as typing an address into the URL bar in my browser. I know the page I want to navigate to so take me right to it. DrupalWebTestCase::drupalGet()
can be used inside a test case like so:
public function testHelloWorld() {
$this->drupalGet('helloworld');
}
This points the SimpleTest internal browser to the helloworld
page on our site, and loads the HTML content of that page into the buffer so we can perform assertions on its content.
DrupalWebTestCase::clickLink()
on the other hand is more like looking at the content of the page, reading through the links in the navigation menu and then deciding that you would like to click on the one labeled "Hello World". You don't know what address it's going to take you to, but that's okay because what matters is that you end up on the page. DrupalWebTestCase::clickLink()
operates by scanning the SimpleTest browsers buffer of the current page's HTML content for any link whose label matches the value provided.
public function testHelloWorld() {
$this->clickLink('Hello World');
}
This would have the effect of clicking on the following link:
<a href="helloworld">Hello World</a>
Or really, just extracting the value of the href attribute from that a tag and feeding it to DrupalWebTestCase::drupalGet()
.
I find the best reference for these helper methods is located in the Drupal.org handbook. https://www.drupal.org/node/265762