Created
December 30, 2011 22:32
-
-
Save lightsofapollo/1541788 to your computer and use it in GitHub Desktop.
Simple PHP BDD runner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
This is what I had in mind while thinking about a spec framework for PHP. | |
It is more verbose then other languages but its all PHP and should be easy enough to augment. | |
I admit I wrote this without any thought to compatibility with anything else. | |
*/ | |
//Contexts (befores, afters and shoulds are executed within a context). | |
class Context { | |
public function expects($subject){ | |
return new ExpectHandler($this, $subject); | |
} | |
} | |
//Matchers are objects (maybe even just callbacks). | |
class ToBeMatcher { | |
public $subject; | |
public $context; | |
public function __construct($subject, $context){ | |
$this->subject = $subject; | |
$this->context = $context; | |
} | |
public function match($actual){ | |
//Report stuff here | |
if($this->subject !== $actual){ | |
//Lame reporting! | |
echo "\n\t\t(Fail) Expected: '" . var_export($this->subject, true) . "' To Be: '" . var_export($actual, true) . "'"; | |
} else { | |
echo "\n\t\t(Success)"; | |
} | |
} | |
}; | |
//Expectation handlers (could be used for not conditions, etc...) | |
class ExpectHandler { | |
public $matchers = array(); | |
public $context; | |
public $subject; | |
public function __construct($context, $subject){ | |
$this->context = $context; | |
$this->subject = $subject; | |
$this->matchers = array( | |
'toBe' => "ToBeMatcher" | |
); | |
} | |
public function __call($method, $arguments){ | |
if(isset($this->matchers[$method])){ | |
$matcher = new $this->matchers[$method]($this->subject, $this->context); | |
call_user_func_array(array($matcher, 'match'), $arguments); | |
} else { | |
throw new Exception("Missing matcher: " . $method); | |
} | |
} | |
} | |
//Spec is a root level test (I would usually think of this as a single file) | |
//It contains no functionality of its own. | |
class Spec { | |
protected $context; | |
protected $rootDescription; | |
public function __construct(){ | |
$this->context = new Context(); | |
} | |
public function describes($desc, $callback){ | |
$this->rootDescription = new Description($this->context, $desc, $callback); | |
} | |
public function runs(){ | |
$this->rootDescription->run(); | |
} | |
} | |
//Shoulds. Everything with a run method is an "action" | |
class Should { | |
public $context; | |
public $description; | |
public $callback; | |
public function __construct($description, $callback, $parent){ | |
$this->parent = $parent; | |
$this->description = $description; | |
$this->callback = $callback; | |
} | |
public function run(){ | |
$callback = $this->callback; | |
echo "\n\t should " . $this->description; | |
$callback($this->parent->context); | |
} | |
} | |
//Execution logic lives in a description which is itself an "action" and can be nested. | |
class Description { | |
//Callbacks | |
public $actions = array(); | |
public $befores = array(); | |
public $afters = array(); | |
//Context | |
public $context; | |
public $description; | |
public $parent; | |
public function __construct($context, $description, $callback, $parent = null){ | |
$this->context = $context; | |
$this->description = $description; | |
$this->parent = $this->parent; | |
$callback($this); | |
} | |
public function should($desc, $callback){ | |
$this->actions[] = new Should($desc, $callback, $this); | |
} | |
public function beforeEach($callback){ | |
$this->befores[] = $callback; | |
} | |
public function afterEach($callback){ | |
$this->afters[] = $callback; | |
} | |
public function describes($description, $callback){ | |
$this->actions[] = new Description($this->context, $description, $callback, $this); | |
} | |
public function run(){ | |
echo "\n\n" . $this->description . "\n\n"; | |
foreach($this->actions as $action){ | |
foreach($this->befores as $before){ | |
$before($this->context); | |
} | |
$action->run(); | |
foreach($this->afters as $after){ | |
$after($this->context); | |
} | |
} | |
} | |
} | |
$spec = new Spec; | |
//This spec should be all the user is exposed to: | |
$spec->describes(" -- My Something Class -- ", function($spec){ | |
$spec->beforeEach(function($that){ | |
$that->var = false; | |
}); | |
$spec->should('fail when expecting $that->var to be true', function($that){ | |
$that->expects($that->var)->toBe(true); | |
}); | |
$spec->describes("--- NESTED CONTEXT --", function($spec){ | |
$spec->beforeEach(function($that){ | |
$that->var = true; | |
}); | |
$spec->should('have set $that->var to true without changing the results of the outer scope', function($that){ | |
$that->expects($that->var)->toBe(true); | |
}); | |
}); | |
$spec->should('succeed when expecting $that->var is false', function($that){ | |
$that->expects($that->var)->toBe(false); | |
}); | |
}); | |
//End spec | |
$spec->runs(); | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If we are thinking in terms of what we could do like RSpec I think shared behaviors could easily be implemented within a framework like this as well.