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
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
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
Selenium is not bundled with symfony by default. To install it, you need to create a new
selenium/ directory in your
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
<!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
... <tr><td><a href='./testIndex.html'>My First Test</a></td></tr> ...
To run the test, simply browse to
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.