The unit and functional test tools provided by symfony should suffice in most cases. A few additional techniques are listed here to resolve common problems in automated testing: launching tests in an isolated environment, accessing a database within tests, testing the cache, and testing interactions on the client side.
15.5.1. Executing Tests in a Test Harness
The test-unit
and test-functional
tasks can launch a single test or a set of tests. But if you call these tasks without any parameter, they launch all the unit and functional tests written in the test/
directory. A particular mechanism is involved to isolate each test file in an independent sandbox, to avoid contamination risks between tests. Furthermore, as it wouldn't make sense to keep the same output as with single test files in that case (the output would be thousands of lines long), the tests results are compacted into a synthetic view. That's why the execution of a large number of test files uses a test harness, that is, an automated test framework with special abilities. A test harness relies on a component of the lime framework called lime_harness
. It shows a test status file by file, and an overview at the end of the number of tests passed over the total, as you see in Listing 15-31.
Listing 15-31 - Launching All Tests in a Test Harness
> symfony test-all
unit/myFunctionTest.php................ok
unit/mySecondFunctionTest.php..........ok
unit/foo/barTest.php...................not ok
Failed Test Stat Total Fail List of Failed
------------------------------------------------------------------
unit/foo/barTest.php 0 2 2 62 63
Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay.
The tests are executed the same way as when you call them one by one, only the output is made shorter to be really useful. In particular, the final chart focuses on the failed tests and helps you locate them.
You can launch all the tests with one call using the test-all
task, which also uses a test harness, as shown in Listing 15-32. This is something that you should do before every transfer to production, to ensure that no regression has appeared since the latest release.
Listing 15-32 - Launching All the Tests of a Project
> symfony test-all
15.5.2. Accessing a Database
Unit tests often need to access a database. A database connection is automatically initialized when you call sfTestBrowser::get()
for the first time. However, if you want to access the database even before using sfTestBrowser
, you have to initialize a sfDabataseManager
object manually, as in Listing 15-33.
Listing 15-33 - Initializing a Database in a Test
$databaseManager = new sfDatabaseManager();
$databaseManager->initialize();
// Optionally, you can retrieve the current database connection
$con = Propel::getConnection();
You should populate the database with fixtures before starting the tests. This can be done via the sfPropelData
object. This object can load data from a file, just like the propel-load-data
task, or from an array, as shown in Listing 15-34.
Listing 15-34 - Populating a Database from a Test File
$data = new sfPropelData();
// Loading data from file
$data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml');
// Loading data from array
$fixtures = array(
'Article' => array(
'article_1' => array(
'title' => 'foo title',
'body' => 'bar body',
'created_at' => time(),
),
'article_2' => array(
'title' => 'foo foo title',
'body' => 'bar bar body',
'created_at' => time(),
),
),
);
$data->loadDataFromArray($fixtures);
Then, use the Propel objects as you would in a normal application, according to your testing needs. Remember to include their files in unit tests (you can use the sfCore::initSimpleAutoload() method to automate it, as explained in a tip in the "Stubs, Fixtures, and Autoloading" section previously in this chapter). Propel objects are autoloaded in functional tests.
15.5.3. Testing the Cache
When you enable caching for an application, the functional tests should verify that the cached actions do work as expected.
The first thing to do is enable cache for the test environment (in the settings.yml
file). Then, if you want to test whether a page comes from the cache or whether it is generated, you should use the isCached()
test method provided by the sfTestBrowser
object. Listing 15-35 demonstrates this method.
Listing 15-35 - Testing the Cache with the isCached()
Method
<?php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
// Create a new test browser
$b = new sfTestBrowser();
$b->initialize();
$b->get('/mymodule');
$b->isCached(true); // Checks that the response comes from the cache
$b->isCached(true, true); // Checks that the cached response comes with layout
$b->isCached(false); // Checks that the response doesn't come from the cache
Note You don't need to clear the cache at the beginning of a functional test; the bootstrap script does it for you.
15.5.4. Testing Interactions on the Client
The main drawback of the techniques described previously is that they cannot simulate JavaScript. For very complex interactions, like with Ajax interactions for instance, you need to be able to reproduce exactly the mouse and keyboard input that a user would do and execute scripts on the client side. Usually, these tests are reproduced by hand, but they are very time consuming and prone to error.
The solution is called Selenium (openqa.org/selenium), which is a test framework written entirely in JavaScript. It executes a set of actions on a page just like a regular user would, using the current browser window. The advantage over the sfBrowser
object is that Selenium is capable of executing JavaScript in a page, so you can test even Ajax interactions with it.
Selenium is not bundled with symfony by default. To install it, you need to create a new selenium/
directory in your web/
directory, and in it unpack the content of the Selenium archive (openqa.org/selenium-core/download.action). This is because Selenium relies on JavaScript, and the security settings standard in most browsers wouldn't allow it to run unless it is available on the same host and port as your application.
Caution Be careful not to transfer the selenium/
directory to your production server, since it would be accessible by anyone having access to your web document root via the browser.
Selenium tests are written in HTML and stored in the web/selenium/tests/
directory. For instance, Listing 15-36 shows a functional test where the home page is loaded, the link click me is clicked, and the text "Hello, World" is looked for in the response. Remember that in order to access the application in the test
environment, you have to specify the myapp_test.php
front controller.
Listing 15-36 - A Sample Selenium Test, in web/selenium/test/testIndex.html
<!DOCTYPE html PUBLIC " *W3C*DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>Index tests</title>
</head>
<body>
<table cellspacing="0">
<tbody>
<tr><td colspan="3">First step</td></tr>
<tr><td>open</td> <td>/myapp_test.php/</td> <td> </td></tr>
<tr><td>clickAndWait</td> <td>link=click me</td> <td> </td></tr>
<tr><td>assertTextPresent</td> <td>Hello, World!</td> <td> </td></tr>
</tbody>
</table>
</body>
</html>
A test case is represented by an HTML document containing a table with three columns: command, target, and value. Not all commands take a value, however. In this case, either leave the column blank or use
to make the table look better. Refer to the Selenium website for a complete list of commands.
You also need to add this test to the global test suite by inserting a new line in the table of the TestSuite.html
file, located in the same directory. Listing 15-37 shows how.
Listing 15-37 - Adding a Test File to the Test Suite, in web/selenium/test/TestSuite.html
... <tr><td><a href='./testIndex.html'>My First Test</a></td></tr> ...
To run the test, simply browse to
http://myapp.example.com/selenium/index.html
Select Main Test Suite, click the button to run all tests, and watch your browser as it reproduces the steps that you have told it to do.
Note As Selenium tests run in a real browser, they also allow you to test browser inconsistencies. Build your test with one browser, and test them on all the others on which your site is supposed to work with a single request.
The fact that Selenium tests are written in HTML could make the writing of Selenium tests a hassle. But thanks to the Firefox Selenium extension (seleniumrecorder.mozdev.org), all it takes to create a test is to execute the test once in a recorded session. While navigating in a recording session, you can add assert-type tests by right-clicking in the browser window and selecting the appropriate check under Append Selenium Command in the pop-up menu.
You can save the test to an HTML file to build a test suite for your application. The Firefox extension even allows you to run the Selenium tests that you have recorded with it.
Note Don't forget to reinitialize the test data before launching the Selenium test.