Subscribe To Our Blog

Using SimpleTest with the Zend Framework, part 1

by Ben Rice

Recognizing the need to focus on certain specific technologies, we at Slice have spent time researching various development frameworks used to create dynamic websites.  While there are a number of different development frameworks out there, each with their various advantages and disadvantages, for various reasons we have chosen to examine the Zend Framework (ZF) for developing dynamic PHP websites.

My most recent experience prior to joining Slice of Lime was in using .Net in a relatively Agile methodology.  From that experience and from listening to the experience of others who have implemented an Agile methodology, I am becoming convinced that Test-Driven Development (TDD) is an indispensible practice for developing web applications.

While I am satisfied that our decision to focus on ZF will pay dividends to us and our clients, it is still a relatively new technology, and one must cast a particularly wide net to garner documentation on problems in developing with it.  My particular problem at this point is “How do I implement the TDD procedures I am familiar with while using ZF?” I was excited to see that ZF includes the Zend_Test class group, which extends PHPUnit’s classes.  Familiar with SimpleTest, a tool similar to PHPUnit, I thought, how difficult can it be to implement Zend_Test in my project?  The answer for those adequately familiar with Zend is probably “Incredibly Easy!”  For those of us just beginning to use Zend, with no familiarity with PHPUnit, and an eagerness to dive into getting code written the answer is “Difficult enough that I don’t care to investigate beyond a few internet searches.”

For a number of reasons I, like other exasperated bulletin board posters out there,   found the Zend documentation for Zend_Test insufficient to get me started.  I was introduced to SimpleTest by the authors of PHP In Action and their straight-forward example that demonstrates incrementally building a test-suite – an example the Zend documenter would do well to check out when they revisit the documentation for Zend_Test.  I decided I would forgo using Zend_Test and PHPUnit for now in favor of the familiar SimpleTest.

I began by sticking the simpletest directory (downloaded from simpletest.org) into my project, and decided to get one test to pass.  For the moment being, I put the simpletest directory into the webroot directory at the same level as my application and library directories in what is the standard directory configuration for a ZF project.  I’m not convinced I’ll leave it there, but I thought for simplicity’s sake, I’d try having it there.  I may also see if it makes more sense to have it up a level (at the same level as httpdocs) or in the library (with Zend).  I also created a tests directory at the same level that will include all my test files.  The structure looks like this then:

httpdocs
    application
        controllers
        models
            dao
            domain
            manager
        views
    library
    Zend
    simpletest
    tests

The first test file I created in the tests directory is testOne.php:

<?php
require_once(’../simpletest/unit_tester.php’);
require_once(’../simpletest/reporter.php’);

class TestOne extends UnitTestCase {

     function TestPass() {

          $this->pass(”Test Passed”);

     }

}

$test = new TestOne();
$test->run(new HtmlReporter());

I open my browser to http://localhost/tests/TestOne.php.  My first test passed no problem!!!  Ok, not so exciting since I explicitly told it to pass.

Next I decided to test a simple business object.  I created a domain object called Company.  (The model for this application extracts the business objects from the common Table Module pattern that ZF employs.  Instead of the domain objects directly extending or wrapping Zend_DB_Table, a manager class uses data access objects(dao), which extend Zend_DB_Table, to create concrete domain objects.)   I created the file testCompany.php in tests:

<?php
require_once(dirname(__FILE__) . ‘/../simpletest/autorun.php’);

 class TestCompany extends UnitTestCase {

     function TestCreate() {

          $company = new Company();

          $this->assertIsA($company, ‘Company’);    

    }

}

Note: while poking around with SimpleTest I found that I could include autorun.php instead of having to include the unit_tester and reporter files and having to explicitly run the test, a development that must be relatively recent to the publishing of PHP In Action and a nice feature.

I know that I am becoming more of a TDD disciple when I take delight in seeing a red bar upon first writing a test.  Without a Company class my test failed.  Now I added the class file, Company.php,  into /application/models/domain.

<?php

class Company

{

}

Of course I also need to include the class file in my test file:

require_once(dirname(__FILE__) .‘/../application/models/domain/Company.php’);

My test, http://localhost/tests/testCompany.php passes.  I utter, “Red-Green-Refactor,” under my breath.  I am going to want to do some testing of the manager, but I first decide to give my Company object at least one attribute.  I add the following line to TestCompany->TestCreate() to get my red bar back:

$this->assertEqual($company->getID(),0);

To return to green I add to my Company class:

protected $id = 0;

function getID() { return $this->id; }

The manager needs a data access object to get the instance of my Company from the database.  I decide my next step is to test and create my CompanyDAO class.  I am thinking now that I want the structure of my tests directory to mirror the structure of my models directory.  I make a mental note that I need to move my TestCompany file into a domain directory and create a testCompanyDAO.php in tests/dao.

<?php

require_once(dirname(__FILE__) . ’/../../simpletest/autorun.php’);
require_once(dirname(__FILE__) .’/../../application/models/dao/CompanyDAO.php’);

class TestCompanyDAO extends UnitTestCase {

     function TestCreate() {

          $dao = new CompanyDAO();

          $this->assertIsA($dao, ‘CompanyDAO’);

     }

}

My test predictably fails.  I add CompanyDAO.php to /application/models/dao:

<?php

class CompanyDAO extends Zend_Db_Table_Abstract

{
    protected $_name
    = ‘company’;
    protected $_primary = ‘id’;
}

Things suddenly got interesting when my test failed again.  This time my test reported that it could not find Zend_Db_Table_Abstract.  Of course!  One of the first things to do when bootstrapping a Zend Framework app is to register Zend_Loader to autoload the necessary Zend objects.  One thing that I didn’t like about the example that the Zend_Test document provided was that it was using a plugin to the controller to bootstrap the Zend app for every test.  I didn’t want to have to do that for such simple tests as checking to see if I created an instance of Company or not.  I decided to create a simple file that mirrored whatever initialization I needed from the Zend Framework for my test files.  I can then include that file in only the specific test files where I need it.  I added bootstrapTestsForZend.php to my tests directory:

// Copy application bootstrapping

define(’APPLICATION_PATH’, $_SERVER['DOCUMENT_ROOT'] .’/application/’);
define(’APPLICATION_ENVIRONMENT’, ‘development’);

set_include_path( $_SERVER['DOCUMENT_ROOT'] . ‘/library’ .PATH_SEPARATOR . get_include_path() );
require_once “Zend/Loader.php”;
Zend_Loader::registerAutoload();

$configuration = new Zend_Config_Ini(
    APPLICATION_PATH . ‘/config/app.ini’,
    APPLICATION_ENVIRONMENT
);

$dbAdapter = Zend_Db::factory($configuration->database);
Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);

(For more on how/why this configuration is like it is, see the Quickstart.)

While initially my APPLICATION_ENVIRONMENT is set to ‘development’, I will probably add a ‘testing’ environment to my configuration so that I can create and drop a lightweight data file when running tests without affecting the data that other developers are using to do functional testing with.

I added the following line as my first line in testCompanyDAO.php to get back to green:

require_once($_SERVER['DOCUMENT_ROOT'] . ’/tests/bootstrapTestsForZend.php’);

To test my manager class, which is really the workhorse of my domain model, I added /tests/manager/testCompanyMgr.php.

<?php

require_once($_SERVER['DOCUMENT_ROOT'] . ’/tests/bootstrapTestsForZend.php’);
require_once(dirname(__FILE__) . ’/../../simpletest/autorun.php’);
require_once(dirname(__FILE__) . ’/../../application/models/manager/CompanyMgr.php’);

class TestCompanyMgr extends UnitTestCase {

     function TestCreate() {

          $mgr = new CompanyMgr();

          $this->assertIsA($mgr, ‘CompanyMgr’);

     }

} 

The manager class:

<?php
require_once APPLICATION_PATH . ‘/models/domain/Company.php’;

class CompanyMgr {

     protected $_dao;

}

One of the common methods for my manager classes is to return all instances of the business object it handles.  Normally I should probably test  the following functionality in a number of smaller increments, but for demonstration purposes I am skipping ahead here a bit.

There are currently three entries in the Company table in my development database.  I write my test to assert that when I ask the manager to return all entries that I get three back:

function TestGetAll() {

    $mgr = new CompanyMgr();
    $entries = $mgr->getAll();
    $this->assertEqual(count($entries),3);
}

With my tests truly driving my development I work back and forth between code and running my tests until my resulting manager class looks like such:

<?php
require_once APPLICATION_PATH . ‘/models/domain/Company.php’;

class CompanyMgr {
    protected $_dao;
    
    function getAll()
    {
        $dao = $this->getDao();
        $rows = $dao->fetchAll()->toArray();
        $entries = array();

        foreach($rows as $row)
        {
            $entries[] = $this->createFromRow($row);
        }
        return $entries;

     }

    private function getDao()
    {

        if (null === $this->_dao) {
            require_once APPLICATION_PATH . ’/models/dao/CompanyDAO.php’;
            $this->_dao = new CompanyDAO();

        }
        return $this->_dao;

     }

     private function createFromRow($row)
     {
        $company = new Company();
        if (isset($row["id"])) $company->setID($row["id"]);
        return $company;
    }

}

As I said before, I will eventually take the step to create and tear down my data source (whether a simple file or actual db instance) each time I run the tests.  While developing, I’ve only needed to run one test file at a time.  As I move along, I will add these test files into a test suite.  I will probably add the creation of the data source  at that level.  SimpleTest also includes functionality to use Mock Objects, which I would also like to explore in Zend.  I’ll try to include those concepts as well as a few more specific to Zend Framework like testing a Zend_Form and Zend_Validate.

Tags: , , , , , ,

Responses are currently closed, but you can trackback from your own site.