Getting More From The Lime Testing Framework

by Jakub Zalas on 9th February 2010

Lime is a testing tool bundled with symfony. It can be, however, used separately with any PHP application. It is a great alternative for famous PHPUnit. Its advantage is simplicity. Since writing tests with lime is dead easy it’s a perfect choice for unit testing newbies. lime tests

Simplicity

What makes lime so powerful is that almost no effort is needed to create a new test. You just open a new file, initialize lime_test object and you’re ready to start writing your tests.
  1. include dirname(__FILE__) . ‘/../bootstrap/unit.php’;
  2.  
  3. $test = new lime_test(3, new lime_output_color());
  4. $test->diag(‘::stripText()’);
  5.  
  6. $text = gyText::stripText(‘Lorem Lipsum’);
  7. $test->isa_ok($text, ‘string’, ‘::stripText() returns a string’);
  8. $test->cmp_ok($text, ‘===’, ‘lorem-lipsum’, ‘::stripText() replaces spaces with dash’);
  9.  
  10. $text = gyText::stripText(‘Gdańsk’);
  11. $test->cmp_ok($text, ‘===’, ‘gdansk’, ‘::stripText() handles diacritic characters’);
Note: file included in the first line initializes symfony related libraries (like autoloader). If you want to use lime without symfony you should replace this line with simple require statements. You’ll need to include lime and other classes used in test.

Reigning the Chaos

Lime approach is simple and quick to start with. Unfortunately,  it becomes hard to maintain in big projects. As the number of tests grows test files become complicated and unclear. Splitting them into several smaller files isn’t a big improvement. Besides, too often helper functions are needed to make tests more readable and avoid code repetition. Object oriented approach solves the problem in a better way.
  1. class gyTextTest extends gyLimeTest
  2. {
  3.   public function testStripTextReturnsString()
  4.   {
  5.     $test = gyText::stripText(‘Lorem Lipsum’);
  6.     $this->isa_ok($test, ‘string’, ‘::stripText() returns a string’);
  7.     $test->cmp_ok($text, ‘===’, ‘lorem-lipsum’, ‘::stripText() replaces spaces with a dash’);
  8.   }
  9.  
  10.   public function testStripTextHandlesDiactricCharacters()
  11.   {
  12.     $text = gyText::stripText(‘Gdańsk’);
  13.     $test->cmp_ok($text, ‘===’, ‘gdansk’, ‘::stripText() handles diacritic characters’);
  14.   }
  15. }
  16.  
  17. $test = new gyTextTest(3);
  18. $test->run();
What I did was group tests as methods of a test class. Adding new test is as simple as creating a new method. Test cases are clearly separated and I gain power of object orientation. Note: Lime 2, the next generation of Lime, will be far more sophisticated. Out of the box it’ll let you use neat annotations or test classes. For good introduction to Lime 2 read Best Practice Testing with Lime 2 by Bernhard Schussek.

The Magic

Every public method which name starts with ‘test‘ is considered to be a test and is called by a run() method. You can see the full implementation on the next listing.
  1. class gyLimeTest extends lime_test
  2. {
  3.   public function __construct($plan = null, $outputInstance = null)
  4.   {
  5.     $outputInstance = is_null($outputInstance) ? new lime_output_color() : $outputInstance;
  6.  
  7.     parent::__construct($plan, $outputInstance);
  8.   }
  9.  
  10.   public function run()
  11.   {
  12.     foreach ($this->getTestMethods() as $method)
  13.     {
  14.       $test    = $method->getName();
  15.       $message = sfInflector::humanize(sfInflector::underscore($test));
  16.  
  17.       $this->diag($message);
  18.       $this->setUp();
  19.       $this->$test();
  20.       $this->tearDown();
  21.     }
  22.   }
  23.  
  24.   protected function getTestMethods()
  25.   {
  26.     $reflection  = new ReflectionClass($this);
  27.     $methods     = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
  28.     $testMethods = array();
  29.  
  30.     foreach ($methods as $method)
  31.     {
  32.       if (preg_match(‘/^test/’, $method->getName())
  33.       && 0 == $method->getNumberOfParameters())
  34.       {
  35.         $testMethods[] = $method;
  36.       }
  37.     }
  38.  
  39.     return $testMethods;
  40.   }
  41.  
  42.   public function setUp()
  43.   {
  44.   }
  45.  
  46.   public function tearDown()
  47.   {
  48.   }
  49. }

Separation

Every test should be run in a clean and separate environment. That’s why before each test setUp() is called. Most often I use it to load test data into the database. Respectively after each test tearDown() should clean things up.

Handy initialization

Common initializations can be done in a constructor. Additional feature is added here. Since I’m annoyed by having to pass lime_output_color instance all the time to get results in color I made it a default behavior.

Good tests are the documentation

Giving descriptive and meaningful test method names is important for the reader. It helps to figure out what’s the purpose of a test case and catch the requirements. To support practice of appropriate naming a diagnostic message is created from the method name and printed out before each test.

Hierarchy

The gyLimeTest class is a base test class in most of my projects. As it’s part of my test plugin I cannot put project specific methods there. What I usualy do is I introduce additional project test class. This way I can put common project methods and assertions in one place.

Hierarchy of test classes