Skip to content

Instantly share code, notes, and snippets.

@lightsofapollo
Created December 30, 2011 22:32
Show Gist options
  • Save lightsofapollo/1541788 to your computer and use it in GitHub Desktop.
Save lightsofapollo/1541788 to your computer and use it in GitHub Desktop.
Simple PHP BDD runner
<?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();
?>
@lightsofapollo
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment