The definitive guide of Symfony 1.1

15.1. Automated Tests

Any developer with experience developing web applications is well aware of the time it takes to do testing well. Writing test cases, running them, and analyzing the results is a tedious job. In addition, the requirements of web applications tend to change constantly, which leads to an ongoing stream of releases and a continuing need for code refactoring. In this context, new errors are likely to regularly crop up.

That's why automated tests are a suggested, if not required, part of a successful development environment. A set of test cases can guarantee that an application actually does what it is supposed to do. Even if the internals are often reworked, the automated tests prevent accidental regressions. Additionally, they compel developers to write tests in a standardized, rigid format capable of being understood by a testing framework.

Automated tests can sometimes replace developer documentation since they can clearly illustrate what an application is supposed to do. A good test suite shows what output should be expected for a set of test inputs, and that is a good way to explain the purpose of a method.

The symfony framework applies this principle to itself. The internals of the framework are validated by automated tests. These unit and functional tests are not bundled with the PEAR package, but you can check them out from the SVN repository or browse them online at http://trac.symfony-project.org/browser/branches/1.1/test.

15.1.1. Unit and Functional Tests

Unit tests confirm that a unitary code component provides the correct output for a given input. They validate how functions and methods work in every particular case. Unit tests deal with one case at a time, so for instance a single method may need several unit tests if it works differently in certain situations.

Functional tests validate not a simple input-to-output conversion, but a complete feature. For instance, a cache system can only be validated by a functional test, because it involves more than one step: The first time a page is requested, it is rendered; the second time, it is taken from the cache. So functional tests validate a process and require a scenario. In symfony, you should write functional tests for all your actions.

For the most complex interactions, these two types may fall short. Ajax interactions, for instance, require a web browser to execute JavaScript, so automatically testing them requires a special third-party tool. Furthermore, visual effects can only be validated by a human.

If you have an extensive approach to automated testing, you will probably need to use a combination of all these methods. As a guideline, remember to keep tests simple and readable.

Note Automated tests work by comparing a result with an expected output. In other words, they evaluate assertions (expressions like $a == 2). The value of an assertion is either true or false, and it determines whether a test passes or fails. The word "assertion" is commonly used when dealing with automated testing techniques.

15.1.2. Test-Driven Development

In the test-driven development (TDD) methodology, the tests are written before the code. Writing tests first helps you to focus on the tasks a function should accomplish before actually developing it. It's a good practice that other methodologies, like Extreme Programming (XP), recommend as well. Plus it takes into account the undeniable fact that if you don't write unit tests first, you never write them.

For instance, imagine that you must develop a text-stripping function. The function removes white spaces at the beginning and at the end of the string, replaces nonalphabetical characters by underscores, and transforms all uppercase characters to lowercase ones. In test-driven development, you would first think about all the possible cases and provide an example input and expected output for each, as shown in Table 15-1.

Table 15-1 - A List of Test Cases for a Text-Stripping Function

Input Expected Output
" foo " "foo"
"foo bar" "foo_bar"
"-)foo:..=bar?" "__foo____bar_"
"FooBar" "foobar"
"Don't foo-bar me!" "don_t_foo_bar_me_"

You would write the unit tests, run them, and see that they fail. You would then add the necessary code to handle the first test case, run the tests again, see that the first one passes, and go on like that. Eventually, when all the test cases pass, the function is correct.

An application built with a test-driven methodology ends up with roughly as much test code as actual code. As you don't want to spend time debugging your tests cases, keep them simple.

Note Refactoring a method can create new bugs that didn't use to appear before. That's why it is also a good practice to run all automated tests before deploying a new release of an application in production — this is called regression testing.

15.1.3. The Lime Testing Framework

There are many unit test frameworks in the PHP world, with the most well known being PhpUnit and SimpleTest. Symfony has its own, called lime. It is based on the Test::More Perl library, and is TAP compliant, which means that the result of tests is displayed as specified in the Test Anything Protocol, designed for better readability of test output.

Lime provides support for unit testing. It is more lightweight than other PHP testing frameworks and has several advantages:

  • It launches test files in a sandbox to avoid strange side effects between each test run. Not all testing frameworks guarantee a clean environment for each test.
  • Lime tests are very readable, and so is the test output. On compatible systems, lime uses color output in a smart way to distinguish important information.
  • Symfony itself uses lime tests for regression testing, so many examples of unit and functional tests can be found in the symfony source code.
  • The lime core is validated by unit tests.
  • It is written in PHP, and it is fast and well coded. It is contained in a single file, lime.php, without any dependence.

The various tests described next use the lime syntax. They work out of the box with any symfony installation.

Note Unit and functional tests are not supposed to be launched in production. They are developer tools, and as such, they should be run in the developer's computer, not in the host server.