Sanity check before refactoring test code

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

Post Reply
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Sanity check before refactoring test code

Post by nielsene »

Just finished adding new tests to my FrontDispatcher to drive a "Pre-Controller" handler chain to grap easy error conditions before further initialization.

However I think I made a mistake in making the tests too specific (even with all the mocking....)

I think that I should change the three first tests to use generic handlers not named handlers. The tests should be
"First Pre Handler claims and aborts further processing"
"Later Pre-Handler claims and aborts further processing"
"No pre-handler claimed and controller invoked"
"Fall Through/Legacy handling"

instead of the explicit use of the CentralDatabaseDownHandler, InvalidCompHandler, etc. I was hoping to use these tests to drive the awareness of those two handlers -- but that should have come from the tests of the ModuleFactor that creates the handler chain, right?

I should be able to pull alot of the common code into setUp's and tearDown's I know, as well.


Here's the test code (kidna long/repetitive):

Code: Select all

<?php 
require_once(COMPINABOX.'include/classes/controllers/FrontDispatcher.inc');
require_once(COMPINABOX.'include/classes/controllers/ModuleFactory.inc');
require_once(COMPINABOX.'include/classes/handlers/CentralDatabaseDownHandler.inc');
require_once(COMPINABOX.'include/classes/handlers/InvalidCompHandler.inc');
require_once(COMPINABOX.'include/classes/controllers/SlidingDoorsAdminController.inc');
require_once(COMPINABOX.'include/classes/requests/SlidingDoorsAdminRequest.inc');
require_once(COMPINABOX.'include/classes/contexts/SlidingDoorsAdminContext.inc');
Mock::generate("ModuleFactory");
Mock::generate("CentralDatabaseDownHandler");
Mock::generate("InvalidCompHandler");
Mock::generate("SlidingDoorsAdminController");
Mock::generate("SlidingDoorsAdminRequest");
Mock::generate("SlidingDoorsAdminContext");
Mock::generatePartial("FrontDispatcher","FDTestVersion",
		      array("_findModule"));

class TestFrontDispatcher extends UnitTestCase {
  var $savedState;
  
  function TestFrontDispatcher() {
    $this->UnitTestCase('TestFrontDispatcher');
  }

  function setUp() {
    $this->savedState=array("Get"=>$_GET,
			    "Post"=>$_POST,
			    "Cookie"=>$_COOKIE,
			    "Server"=>$_SERVER);
    $_GET=array();
    $_POST=array();
    $_SERVER=array();
    $_COOKIE=array();
  }
  
  function tearDown() {
    $_GET=$this->savedState["Get"];
    $_POST=$this->savedState["Post"];
    $_COOKIE=$this->savedState["Cookie"];
    $_SERVER=$this->savedState["Server"];
  }

  function testCentralDBDown() {
    $_SERVER["REQUEST_URI"]="/register/foo/Admin/bar";
    $viewName="CentralDatabaseDown";
    $sdaRequest =& new MockSlidingDoorsAdminRequest($this);
    $sdaContext =& new MockSlidingDoorsAdminContext($this);

    $dbHandler =& new MockCentralDatabaseDownHandler($this);
    $dbHandler->expectOnce("canHandle",array($sdaContext,$sdaRequest));
    $dbHandler->setReturnValue("canHandle",TRUE);
    $dbHandler->expectOnce("execute",array($sdaContext,$sdaRequest));
    $dbHandler->setReturnValue("execute",$viewName);

    $compHandler =& new MockInvalidCompHandler($this);
    $compHandler->expectNever("canHandle");
    $compHandler->expectNever("execute");
    
    $mc =& new MockSlidingDoorsAdminController($this);
    $mc->expectNever("invoke");

    $sdaFactory =& new MockModuleFactory($this);
    $sdaFactory->setReturnReference("createRequest",$sdaRequest);
    $sdaFactory->setReturnReference("createContext",$sdaContext);
    $sdaFactory->setReturnReference("createController",$mc);
    $handlers = array(); $handlers[0]=&$dbHandler; $handlers[1]=&$compHandler;
    $sdaFactory->setReturnReference("createPreHandlers",$handlers);

    $dispatcher =& new FDTestVersion($this);
    $dispatcher->setReturnReference("_findModule",$sdaFactory);
    $dispatcher->FrontDispatcher();
    $this->assertEqual($viewName, $dispatcher->dispatch());
    $mc->tally();
    $dbHandler->tally();
    $compHandler->tally();
  }

  function testInvalidCompName() {
    $_SERVER["REQUEST_URI"]="/register/foo/Admin/bar";
    $viewName="InvalidCompName";
    $sdaRequest =& new MockSlidingDoorsAdminRequest($this);
    $sdaContext =& new MockSlidingDoorsAdminContext($this);

    $dbHandler =& new MockCentralDatabaseDownHandler($this);
    $dbHandler->expectOnce("canHandle",array($sdaContext,$sdaRequest));
    $dbHandler->setReturnValue("canHandle",FALSE);
    $dbHandler->expectNever("execute");
    
    $compHandler =& new MockInvalidCompHandler($this);
    $compHandler->expectOnce("canHandle",array($sdaContext,$sdaRequest));
    $compHandler->setReturnValue("canHandle",TRUE);
    $compHandler->expectOnce("execute",array($sdaContext,$sdaRequest));
    $compHandler->setReturnValue("execute",$viewName);
    
    $mc =& new MockSlidingDoorsAdminController($this);
    $mc->expectNever("invoke");
    
    $sdaFactory =& new MockModuleFactory($this);
    $sdaFactory->setReturnReference("createRequest",$sdaRequest);
    $sdaFactory->setReturnReference("createContext",$sdaContext);
    $sdaFactory->setReturnReference("createController",$mc);
    $handlers = array(); $handlers[0]=&$dbHandler; $handlers[1]=&$compHandler;
    $sdaFactory->setReturnReference("createPreHandlers",$handlers);
    
    $dispatcher =& new FDTestVersion($this);
    $dispatcher->setReturnReference("_findModule",$sdaFactory);
    $dispatcher->FrontDispatcher();
    $this->assertEqual($viewName, $dispatcher->dispatch());
    $mc->tally();
    $dbHandler->tally();
    $compHandler->tally();
  }


  function testInvokeModuleController() {
    $_SERVER["REQUEST_URI"]="/register/foo/Admin/bar";
    $viewName="SomeView";
    $sdaRequest =& new MockSlidingDoorsAdminRequest($this);
    $sdaContext =& new MockSlidingDoorsAdminContext($this);

    $dbHandler =& new MockCentralDatabaseDownHandler($this);
    $dbHandler->expectOnce("canHandle",array($sdaContext,$sdaRequest));
    $dbHandler->setReturnValue("canHandle",FALSE);
    $dbHandler->expectNever("execute");

    $compHandler =& new MockInvalidCompHandler($this);
    $compHandler->expectOnce("canHandle",array($sdaContext,$sdaRequest));
    $compHandler->setReturnValue("canHandle",FALSE);
    $compHandler->expectNever("execute");

    $mc =& new MockSlidingDoorsAdminController($this);
    $mc->expectOnce("invoke",array($sdaRequest,$sdaContext));
    $mc->setReturnValue("invoke",$viewName);

    $sdaFactory =& new MockModuleFactory($this);
    $sdaFactory->setReturnReference("createRequest",$sdaRequest);
    $sdaFactory->setReturnReference("createContext",$sdaContext);
    $sdaFactory->setReturnReference("createController",$mc);
    $handlers = array(); $handlers[0]=&$dbHandler; $handlers[1]=&$compHandler;
    $sdaFactory->setReturnReference("createPreHandlers",$handlers);


    $dispatcher =& new FDTestVersion($this);
    $dispatcher->setReturnReference("_findModule",$sdaFactory);
    $dispatcher->FrontDispatcher();
    $this->assertEqual($viewName, $dispatcher->dispatch());
    $mc->tally();
    $dbHandler->tally();
    $compHandler->tally();
  }

  function testFallThrough() {

    $_SERVER["REQUEST_URI"]="/bar";
    $dispatcher =& new FrontDispatcher();
    $this->assertEqual(0, $dispatcher->dispatch());
  }

}

?>
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Re: Sanity check before refactoring test code

Post by McGruff »

nielsene wrote:I think that I should change the three first tests to use generic handlers not named handlers. The tests should be
"First Pre Handler claims and aborts further processing"
"Later Pre-Handler claims and aborts further processing"
"No pre-handler claimed and controller invoked"
"Fall Through/Legacy handling"
Yes - that would be clearer. Good thinking :) Little things like that can make code much easier to read. There would otherwise have been that extra jump to make to realise that the "database down" handler isn't important per se - it's just being used as a sample handler object.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

OK thanks for the sanity check. I'll proceed to refactor that shortly. You wouldn't happen to know why moving tally's on class variables from a test case to the tearDown fails?
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

I've never looked at SimpleTest's internals in much detail - actually that's something planned for my week off. I really ought to learn how to write custom assertions properly.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Well the refactoring worked great! Especially as I had abstracted our the "Pre-Handler" filter chain. While the code is still highly mock'd it doesn't feel "wrong" an more as I dn't have to dig as deep through the layers to setup the correct sequence of events.

It has a rather crazy setUp, but I prefer this style as it lets the test caes focus soley on the relevant details... but it does present things a little out of context. I still need to properly test the private method that was knocked out by the partial mock. I think, given how well the "extract class" worked for the Pre-Handlers, I'll do the same for the mapping and test it in nice, simple isolation.

I also introduced a lot of "interface" classes (ie things that would be interfaces if I was using php5), letting me focus more on the class under test instead of heavily mocking more specific subclasses.

Code: Select all

<?php 
require_once(COMPINABOX.'include/classes/controllers/FrontDispatcher.inc');
require_once(COMPINABOX.'include/classes/controllers/ModuleFactory.inc');
require_once(COMPINABOX.'include/classes/controllers/ModuleController.inc');
require_once(COMPINABOX.'include/classes/handlerchains/HandlerChain.inc');
require_once(COMPINABOX.'include/classes/requests/Request.inc');
require_once(COMPINABOX.'include/classes/contexts/Context.inc');
Mock::generate("ModuleFactory");
Mock::generate("ModuleController");
Mock::generate("HandlerChain");
Mock::generate("Request");
Mock::generate("Context");
Mock::generatePartial("FrontDispatcher","FDTestVersion",
		      array("_findModule"));

class TestFrontDispatcher extends UnitTestCase {
  var $savedState;
  var $con;
  var $req;
  var $factory;
  var $controller;
  var $handlers;
  var $views;
  var $dispatcher;

  function TestFrontDispatcher() {
    $this->UnitTestCase('TestFrontDispatcher');
  }

  function setUp() {
    $this->savedState=array("Get"=>$_GET,
			    "Post"=>$_POST,
			    "Cookie"=>$_COOKIE,
			    "Server"=>$_SERVER);
    $_GET=array();
    $_POST=array();
    $_SERVER=array();
    $_COOKIE=array();

    $this->views=array("NULL"=>"","PreHandlers"=>"PreHandler",
		       "Controller"=>"Controller");
    $this->con =& new MockContext($this);
    $this->req =& new MockRequest($this);
    $this->handlers =& new MockHandlerChain($this);
    $this->handlers->expectOnce("invoke");

    $this->controller =& new MockModuleController($this);

    $this->factory =& new MockModuleFactory($this);
    $this->factory->setReturnReference("createRequest",$this->req);
    $this->factory->setReturnReference("createContext",$this->con);
    $this->factory->setReturnReference("createController",$this->controller);
    $this->factory->setReturnReference("createPreHandlers",$this->handlers);

    $this->dispatcher =& new FDTestVersion($this);
    $this->dispatcher->setReturnReference("_findModule",$this->factory);
    $this->dispatcher->FrontDispatcher();

  }

  function tearDown() {
    $_GET=$this->savedState["Get"];
    $_POST=$this->savedState["Post"];
    $_COOKIE=$this->savedState["Cookie"];
    $_SERVER=$this->savedState["Server"];
  }

  function testPreHandlerClaimed() {
    $this->handlers->setReturnValue("invoke",$this->views["PreHandlers"]);
    $this->controller->expectNever("invoke");
    $this->controller->setReturnValue("invoke",$this->views["Controller"]);

    $this->assertEqual($this->views["PreHandlers"],
		       $this->dispatcher->dispatch());
    $this->controller->tally();
    $this->handlers->tally();
  }


  function testInvokeModuleController() {
    $this->handlers->setReturnValue("invoke",$this->views["NULL"]);
    $this->controller->expectOnce("invoke");
    $this->controller->setReturnValue("invoke",$this->views["Controller"]);

    $this->assertEqual($this->views["Controller"], 
		       $this->dispatcher->dispatch());
    $this->controller->tally();
    $this->handlers->tally();
  }

  function testFallThrough() {
    $this->handlers->setReturnValue("invoke",$this->views["NULL"]);
    $this->controller->expectOnce("invoke");
    $this->controller->setReturnValue("invoke",$this->views["NULL"]);

    $this->assertEqual($this->views["NULL"], $this->dispatcher->dispatch());
    $this->controller->tally();
    $this->handlers->tally();
  }

}

?>
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

nielsene wrote:I also introduced a lot of "interface" classes (ie things that would be interfaces if I was using php5), letting me focus more on the class under test instead of heavily mocking more specific subclasses.
I often do the same defining empty "interface" classes whenever I find them. As you say they're handy for creating for mocks and even though php4 doesn't have interfaces for real they still serve a useful purpose. They also document the methods that certain types of class should implement.
Post Reply