A unit test is source code which is intended to guarantee the accuracy and stability of another well defined portion of source code. The idea is to write test cases for every non-trivial function or method in the module so that each test case is separate from the others if possible. Studs provides a very simple unit testing framework which can effectively be used to write suites of tests for any source code in the system. It follows the same design as JUnit and therefore should be familair to Java developers.
Before you begin, you must first select a function or method to test. A good place to start is any method that does basic math or text formatting. The next step is to write the test case class. The whole point of a test case is to take a known input and check for an expected output, a condition that is worked out before the test.
In order to make this HOWTO as useful as possible, it will be conducted by example. Assume that we are going to test a fuzzy date formatting tool. This tool will output lazy descriptions of a date, such as “3 hours, 45 minutes ago”, “10 hours ago” and “just in...” The interface would look like the following:
class FuzzyDateFormat { var $currentDate; function FuzzyDateFormat() { $currentDate =& new Date(); } /** * Output a fuzzy date unless the date is more than 24 hours ago, * and then just use a normal format. * * An example would be: 10 hours, 43 minutes ago * * Don't include the hours or minutes in the fuzzy date unless they * are non-zero. If both are non-zero, then output the string 'Just in...' * * @return String the date in string format */ function format($date) {} }
To save space, I am not going to include the implementation here. Besides, the implementation doesn’t matter. We are testing the inputs and outputs, not what is inside. Okay, so the next step is to write the unit test case.
The tests are written using the same assumption made in JUnit. Any method in a class that extends TestCase and begins with the string “test” is assumed to be a test case and will be executed. Before and after every method that is executed, the optional setup() and tearDown() methods are called to allow for common tasks. These methods are excluded in our example.
import('horizon.util.unittest.TestCase'); import('example.Date'); import('example.FuzzyDateFormatTest'); class FuzzyDateFormatTest extends TestCase { function testFormatJustIn() { $formatter =& new FuzzyDateFormat(); $date =& new Date(); $result = $formatter->format($date); $this->assertEquals($result, 'Just in...'); } function testFormatFuzzy() { $formatter =& new FuzzyDateFormat(); $date =& new Date(time() - (10 * 60 * 60)); $result = $formatter->format($date); $this->assertEquals($result, '10 hours ago'); } function testFormatNonFuzzy() { $formatter =& new FuzzyDateFormat(); $date =& new Date(strtotime('01/01/1970 12:01')); $result = $formatter->format($date); $this->assertEquals($result, 'Jan-01-1970 12:01'); } }
The next step is to add this test to a suite, so that the test runner can pick it up and evaluate it within the context of the unit testing framework. We will create a test suite.
import('horizon.util.unittest.TestSuite'); import('horizon.util.unittest.TestResult'); class ExampleTestSuite extends TestSuite { function &suite() { $suite =& new TestSuite(); $suite->addTestSuite(Clazz::forName('example.FuzzyDateFormatTest')); } }
Now that your test suite is all setup, you will need to use the text runner to execute it. There is a script in the scripts directory called testrunner.php which will get you started. It is necessary to use this script because the Studs environment must be initialized prior to running the tests. The contents of this file are as follows:
ini_set('include_path', implode(':', array('.', '../src', '../tests'))); require_once 'horizon/init.php'; import('horizon.util.unittest.runner.TextRunner'); TextRunner::main($_SERVER['argv']);
It is necessary to specify the include_path relative to the location from which the script is being executed. The allow_call_time_pass_reference must be enabled from the commandline to avoid warnings and is necessary because of a shortcoming in PHP. Assuming that the test suite was placed at the root of the tests directory, the command to execute the test suite is:
$> php -q -d allow_call_time_pass_reference=1 testrunner.php ExampleTestSuite
If all goes well, the output should look something like:
Runs: 1/1 Errors: 0 Failures: 0