Workshop - week 09

From mi-linux
Revision as of 14:48, 3 April 2009 by 0704840 (talk | contribs)
Jump to navigationJump to search

Main Page >> Web Frameworks >> Web Frameworks - Workbook >> Workshop - week 09

Unit testing in Zend is done using the Zend_Test_PHPUnit classes. Let’s look at an example.

This particular example assumes that we're moving most of our bootstrapping to a plugin. This simplifies setup of the test case as it allows us to specify our environment succinctly, and also allows us to bootstrap the application in a single line.

So let's:

  • Step 1 - Move all our bootstrapping to a plugin
  • Step 2 - Create our test cases
  • Step 3 - Create a test suite
  • Step 4 - Run the tests!

Step 1 - Moving all our bootstrapping to a plugin

In Zend Framework, plugins are used to listen for certain events in the front controller. Events in the front controller bookend each of the major actions that occur: routing, the dispatch loop, and dispatching an individual action. The actual hooks defined are:

* routeStartup(): prior to routing the request
* routeShutdown(): after routing the request
* dispatchLoopStartup(): prior to entering the dispatch loop
* preDispatch(): prior to dispatching an individual action
* postDispatch(): after dispatching an individual action
* dispatchLoopShutdown(): after completing the dispatch loop

In most examples of Zend Framework MVC apps, we show a bootstrap file that contains the entire application initialization -- loading configuration, loading all plugins, initializing the view and database, etc. This works well, but it can lead to a somewhat sloppy file, and also leaves the potential to leak important information about your system should the file ever be displayed without processing.

We can solve this by pushing most initialization into an early-running plugin -- specifically, a routeStartup() plugin. Here's an example:

<?php

// application/InitializationPlugin.php

class InitializationPlugin extends Zend_Controller_Plugin_Abstract
{

  // -------------------------------
  // Public functions
  // -------------------------------

  // Creation of our Plugin
  public function __construct($env)
  {
      $this->setEnv($env);
  }

  // Route startup hook
  public function routeStartup(Zend_Controller_Request_Abstract $request)
  {
      $this->loadConfig();
  }
  
  // -------------------------------
  // Private functions
  // -------------------------------

  // Setting the environnement
  private function setEnv($env)
  {
    $frontController = Zend_Controller_Front::getInstance(); 
    $frontController->setParam('env', $env);
  }
  
  // All our config stuff!
  private function loadConfig()
  {
    // #############################################################################
    // APPLICATION
    // #############################################################################    
    
    // APPLICATION CONSTANTS - Set the constants to use in this application.
    // These constants are accessible throughout the application, even in ini 
    // files. We optionally set APPLICATION_PATH here in case our entry point 
    // isn't index.php (e.g., if required from our test suite or a script).
    defined('APPLICATION_PATH')
        or define('APPLICATION_PATH', dirname(__FILE__));
    
    // FRONT CONTROLLER - Get the front controller.
    // The Zend_Front_Controller class implements the Singleton pattern, which is a
    // design pattern used to ensure there is only one instance of
    // Zend_Front_Controller created on each request.
    $frontController = Zend_Controller_Front::getInstance();
    
    // CONTROLLER DIRECTORY SETUP - Point the front controller to your action
    // controller directory.
    $frontController->setControllerDirectory(APPLICATION_PATH . '/controllers');
    
    // #############################################################################
    // VIEWS
    // #############################################################################
    //
    // LAYOUT SETUP - Setup the layout component
    // The Zend_Layout component implements a composite (or two-step-view) pattern
    // With this call we are telling the component where to find the layouts scripts.
    Zend_Layout::startMvc(APPLICATION_PATH . '/layouts/scripts');
    
    // VIEW SETUP - Initialize properties of the view object
    // The Zend_View component is used for rendering views. Here, we grab a "global" 
    // view instance from the layout object, and specify the doctype we wish to 
    // use. In this case, XHTML1 Strict.
    $view = Zend_Layout::getMvcInstance()->getView();
    $view->doctype('XHTML1_STRICT');
    
    // #############################################################################
    // READ CONFIG FILE
    // #############################################################################
    
    // CONFIGURATION - Setup the configuration object
    // The Zend_Config_Ini component will parse the ini file, and resolve all of
    // the values for the given section.  Here we will be using the section name
    // that corresponds to the APP's Environment
    $env = $frontController->getParam('env');
    $configuration = new Zend_Config_Ini(
        APPLICATION_PATH . '/config/app.ini', 
        $env
    );
    
    // #############################################################################
    // CREATE DB
    // #############################################################################
    
    // DATABASE ADAPTER - Setup the database adapter
    // Zend_Db implements a factory interface that allows developers to pass in an 
    // adapter name and some parameters that will create an appropriate database 
    // adapter object.  In this instance, we will be using the values found in the 
    // "database" section of the configuration obj.
    $dbAdapter = Zend_Db::factory($configuration->database);
    
    // DATABASE TABLE SETUP - Setup the Database Table Adapter
    // Since our application will be utilizing the Zend_Db_Table component, we need 
    // to give it a default adapter that all table objects will be able to utilize 
    // when sending queries to the db.
    Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);
    
    // #############################################################################
    // SAVE CONFIG TO REGISTRY
    // #############################################################################
    
    // REGISTRY - setup the application registry
    // An application registry allows the application to store application 
    // necessary objects into a safe and consistent (non global) place for future 
    // retrieval.  This allows the application to ensure that regardless of what 
    // happends in the global scope, the registry will contain the objects it 
    // needs.
    $registry = Zend_Registry::getInstance();
    $registry->configuration = $configuration;
    $registry->dbAdapter     = $dbAdapter;
    
    // CLEANUP - remove items from global scope
    // This will clear all our local boostrap variables from the global scope of 
    // this script (and any scripts that called bootstrap).  This will enforce 
    // object retrieval through the Applications's Registry
    unset($frontController, $view, $configuration, $registry);

  }
}

Note that the loadConfig() function contains the code that used to be in your bootstrap file.

Your bootstrap file is now a lot more simple, as it only creates the front controller and registers our plugin:

<?php

// application/bootstrap.php

require_once("InitializationPlugin.php");

// Create front controller
$frontController = Zend_Controller_Front::getInstance();

// Create plugin - contains all our setup
$frontController->registerPlugin(new InitializationPlugin('production'));

Check point

At this point in time, your application should work just as it did before! Please test that you can still access your guestbook, add comments etc...

"Why have we done all this then?" I hear you ask. Two reasons:

  • All our configuration is now in a separate file (InitializationPlugin.php). Our bootstrap file is very light and readable.
  • This simplifies setup of the test cases as it allows us to specify our environment succinctly, and also allows us to bootstrap the application in a single line.

So let's write our test cases!

Useful links

Step 2 - Creating our test cases

All MVC test cases should extend Zend_Test_PHPUnit_ControllerTestCase. This class in turn extends PHPUnit_Framework_TestCase, and gives you all the structure and assertions you'd expect from PHPUnit -- as well as some scaffolding and assertions specific to Zend Framework's MVC implementation.

In order to test your MVC application, you will need to bootstrap it. This is done by the first 2 functions in the code below. They basically call the plugin we have created in step 1. This will set-up the application environement and configuration.

Once the above is done, all our test cases functions are called. In this case we have a single testIndexAction test case that simply ensures that dispatching to ‘/guestbook/index’ results in the controller named ‘guestbook’ and an action called ‘index’ are the last to be executed. This might seem trivial, but it helps detect errors with your controllers.

<?php

// application/tests/GuestbookControllerTest.php

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
require_once("../InitializationPlugin.php");

// This is our unit testing controller
class GuestbookControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{

  // -------------------------------
  // Bootstrap application
  // -------------------------------

  //Prepares the environment before running a test.
  protected function setUp()
  {
    $this->bootstrap = array($this, 'appBootstrap');
    parent::setUp();
  }
 
  //Prepares the environment before running a test.
  public function appBootstrap()
  {
    $this->frontController->registerPlugin(new InitializationPlugin('development'));
  }

  // -------------------------------
  // Our test cases
  // -------------------------------

  // Test our main controller
  public function testIndexAction()
  {
    $this->dispatch ('/guestbook/index');
    $this->assertController ('guestbook');
    $this->assertAction ('index');
  }

  // More test cases here !

}

Obviously you can perform more complex tasks and checks, please see Programmer's reference guide link below.

By Daniel Hardy 0607197 Make Sure You save all of your cases as ..........Test.php (Note: It is important you use a capital T)

Useful links

Step 3 - Creating a test suite

Now that we have written all our test cases, we need to create a "test suite".

PHPUnit offers a variety of methods for setting up test suites, some trivial and some complex. The Zend Framework test suite, for instance, goes for a more complex route, adding component-level suites that require a fair amount of initial setup, but which allow us fairly fine-grained control.

However, testing and test automation should be easy and the complex approach is overkill for most of our applications. Fortunately, PHPUnit offers some other methods that make doing so relatively simple. The easiest method is to use an XML configuration file.

As an example, consider the following:

<phpunit>
    <testsuite name="My Test Suite">
        <directory>./</directory>
    </testsuite>
</phpunit>

(save the file in "application/tests/phpunit.xml")

Providing a directory directive to the testsuite directive scans for all files ending in "Test.php" in that directory and all its sub-directories, meaning you don't have to keep a list of your test cases manually. It's a great way to automate the suite.

We have chosen to organize our test files as follow:

  • application/tests/controllers/ for tests cases related to controllers
  • application/tests/models/ for tests cases related to models

Note: You can specify more parameters, e.g. filters to specify which classes to include and/or exclude from coverage reports, logging directive to specify what kinds of logs to create and where, etc... See "Setting up your Zend_Test test suites" below for more information.

Useful links

Step 4 - Running the tests

Now that we have created our test cases and our test suite, we can finally run the tests! This is simply done by running the following command in a command line window:

If you are currently logged in to Linux then to be able to run the phpunit command in a Console window first type the following:

ssh mi-linux.wlv.ac.uk

and when prompted enter your University Account password. You will then be able to execute the command. This is because the utility PHPunit is installed on the server not on the client machines.

To use PuTTY from Windows connect to mi-linux.wlv.ac.uk, navigate to the application/tests folder and execute the command. Update provided by Mike Allen 0722226

phpunit --configuration phpunit.xml

Note: Go to the folder where your XML test suite file is stored before running the command.

The result should be:

Phpunit1.gif

"OK (1 test, 2 assertions)" tells us that the 2 assertions we made in our test case are correct. Remember we are checking that when we dispatch to ‘/guestbook/index’, the controller named ‘guestbook’ and an action called ‘index’ are the last to be executed:

$this->dispatch ('/guestbook/index');
$this->assertController ('guestbook');
$this->assertAction ('index');

Creating an error

Let's purposely insert an error in our controller. Add the following division by zero in the "index" action of your "guestbook" controller:

    public function indexAction()
    {
        $error = 50/0; // ERROR !
        $model = $this->_getModel();
        $this->view->entries = $model->fetchEntries();
    }

Run the same command:

phpunit --configuration phpunit.xml

This time the result should be:

Phpunit2.gif

Further work

Assertions are at the heart of Unit Testing; you use them to verify that the results are what you expect. To this end, Zend_Test_PHPUnit_ControllerTestCase provides a number of assertions to make testing your MVC apps and controllers simpler.

The assertions tested above are very trivial. You can obviously write more complex and useful ones.

More details here

By Mike Austin (0704840) Testing MySQL database connections using PHPUnit & DbUnit

I found a very interesting slideshow by Sebastian Bergmann ( the creator of PHPUnit) on how to test database connections.It is a standalone example but by picking out the relivant code, i was able to test my application could successfully communicate with my MySQL database.

http://www.slideshare.net/sebastian_bergmann/testing-phpmysql-applications-with-phpunitdbunit