Created
October 3, 2008 08:55
-
-
Save speedmax/14530 to your computer and use it in GitHub Desktop.
SimpleSpec - SimpleTest extension for Behavior driven development(BDD)
This file contains hidden or 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 | |
require_once 'spec_helper.php'; | |
class Describe_local_variable extends SimpleSpec { | |
function should_retrive_variable_using_array_interface() { | |
$c = create_context(array( | |
'name' => 'peter', | |
'hobbies' => array('football', 'basket ball', 'swimming'), | |
'location' => array( | |
'address' => '1st Jones St', | |
'city' => 'Marry hill', 'state' => 'NSW', 'postcode' => 2320 | |
) | |
)); | |
expects($c['name'])->should_be("peter"); | |
expects($c['name'])->should_not_be("peter wong"); | |
} | |
function should_set_variable_using_array_interface() { | |
$c = create_context(); | |
$c['name'] = 'peter'; | |
$c['age'] = 18; | |
expects($c['name'])->should_be("peter"); | |
expects($c['age'])->should_be(18); | |
} | |
function should_ensure_scope_remain_local_in_a_stack_layer() { | |
$c= new H2o_Context(array('name'=> 'peter')); | |
$c->push(array('name'=>'wong')); | |
$c->push(array('name'=>'lee')); | |
expects($c['name'])->should_be('lee'); | |
expects($c->resolve(':name'))->should_be('lee'); | |
$c->pop(); | |
expects($c->resolve(':name'))->should_be('wong'); | |
$c->pop(); | |
expects($c['name'])->should_be('peter'); | |
} | |
} | |
class Describe_context_lookup_basic_data_types extends SimpleSpec { | |
function should_resolve_a_integer() { | |
$c= create_context(); | |
expects($c->resolve('0000'))->should_be(0); | |
expects($c->resolve('-00001'))->should_be(-1); | |
expects($c->resolve('20000'))->should_be(20000); | |
} | |
function should_resolve_a_float_number() { | |
$c= create_context(); | |
# Float | |
expects($c->resolve('0.001'))->should_be(0.001); | |
expects($c->resolve('99.999'))->should_be(99.999); | |
} | |
function should_resolve_a_negative_number() { | |
$c= create_context(); | |
expects($c->resolve('-00001'))->should_be(-1); | |
} | |
function should_resolve_a_string() { | |
$c= new H2o_Context; | |
expects($c->resolve('"something"'))->should_be('something'); | |
expects($c->resolve("'he hasn\'t eat it yet'"))->should_be("he hasn't eat it yet"); | |
} | |
} | |
class Describe_array_lookup extends SimpleSpec { | |
function should_be_access_by_array_index() { | |
$c= create_context(array( | |
'numbers' => array(1,2,3,4,1,2,3,4,5), | |
)); | |
expects($c->resolve(':numbers.0'))->should_be(1); | |
expects($c->resolve(':numbers.1'))->should_be(2); | |
expects($c->resolve(':numbers.8'))->should_be(5); | |
} | |
function should_be_access_by_array_key() { | |
$c= create_context(array('person' => array( | |
'name' => 'peter','age' => 26, 'tasks'=> array('shopping','sleep') | |
))); | |
expects($c->resolve(':person.name'))->should_be('peter'); | |
expects($c->resolve(':person.age'))->should_be(26); | |
expects($c->resolve(':person.tasks.first'))->should_be('shopping'); | |
} | |
function should_resolve_array_like_objects() { | |
$c= create_context(array( | |
'list' => new ArrayObject(array( | |
'item 1', 'item 2', 'item 3' | |
)), | |
'dict' => new ArrayObject(array( | |
'name' => 'peter','seo-url' => 'http://google.com' | |
)) | |
)); | |
expects($c->resolve(':list.0'))->should_be('item 1'); | |
expects($c->resolve(':list.length'))->should_be(3); | |
expects($c->resolve(':dict.name'))->should_be('peter'); | |
expects($c->resolve(':dict.seo-url'))->should_be('http://google.com'); | |
} | |
function should_resolve_additional_array_property() { | |
$c = create_context(array( | |
'hobbies'=> array('football', 'basket ball', 'swimming') | |
)); | |
expects($c->resolve(':hobbies.first'))->should_be('football'); | |
expects($c->resolve(':hobbies.last'))->should_be('swimming'); | |
expects($c->resolve(':hobbies.length'))->should_be(3); | |
expects($c->resolve(':hobbies.size'))->should_be(3); | |
} | |
} | |
class Describe_context_lookup extends SimpleSpec { | |
function should_use_dot_to_access_object_property() { | |
$c = create_context(array( | |
'location' => (object) array( | |
'address' => '1st Jones St', | |
'city' => 'Marry hill', 'state' => 'NSW', | |
'postcode' => 2320 | |
), | |
)); | |
expects($c->resolve(':location.address'))->should_be('1st Jones St'); | |
expects($c->resolve(':location.city'))->should_be('Marry hill'); | |
} | |
function should_return_null_for_undefined_or_private_object_property() { | |
$c = create_context(array( | |
'document' => new Document( | |
'my business report', | |
'Since Augest 2005, financial projection has..') | |
)); | |
expects($c->resolve(':document.uuid'))->should_be_null(); // Private | |
expects($c->resolve(':document.undefined_property'))->should_be_null(); | |
} | |
function should_use_dot_to_perform_method_call() { | |
$c = create_context(array( | |
'document' => new Document( | |
'my business report', | |
'Since Augest 2005, financial projection has..') | |
)); | |
expects($c->resolve(':document.to_pdf'))->should_match('/PDF Version :/'); | |
expects($c->resolve(':document.to_xml'))->should_match('/<title>my business report<\/title>/'); | |
} | |
function should_return_null_for_undefined_or_private_method_call() { | |
$c = create_context(array( | |
'document' => new Document( | |
'my business report', | |
'Since Augest 2005, financial projection has..') | |
)); | |
expects($c->resolve(':document._secret'))->should_be_null(); // Private | |
expects($c->resolve(':document.undefined_method'))->should_be_null(); | |
} | |
} | |
class Document { | |
var $h2o_safe = array('to_pdf', 'to_xml'); | |
private $uuid; | |
function __construct($title, $content) { | |
$this->title = $title; | |
$this->content = $content; | |
$this->uuid = md5($title.time()); | |
} | |
function to_pdf() { | |
return "PDF Version : {$this->title}"; | |
} | |
function to_xml() { | |
return "<title>{$this->title}</title><content>{$this->content}</content>"; | |
} | |
function _secret() { | |
return "secret no longer"; | |
} | |
} | |
function create_context($c = array()) { | |
return new H2o_Context($c); | |
} | |
?> |
This file contains hidden or 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 | |
/** | |
* @name SimpleSpec | |
* PHP SimpleTest extension for Behavior driven development(BDD) | |
* | |
* why not PHPSpec? well its a good project but SimpleTest has better coverage for testing | |
* and i want better grammer | |
* | |
* Features: | |
* - Reuse SimpleTest framework | |
* - simplly one include from away | |
* - Underscore for readibility - all examples uses underscore to seperate descriptions. | |
* - Natural language grammer | |
* - before_all, after_all is not supported, i don't want to modify SimpleTest | |
* - conventional before/after named prepare/cleanup respectively | |
* | |
* @example | |
* class Describe_his_mum extends SimpleSpec { | |
* function prepare() { | |
* $this->mum = new Mum(array('mood'=>'angry')); | |
* } | |
* | |
* function should_be_very_angry_when_i_break_my_lunch_box() { | |
* expects($this->mum->mood)->should_be('angry'); | |
* } | |
* | |
* function should_be_very_happy_when_i_punch_her_in_the_face() { | |
* punch($this->mum); | |
* expects($this->mum->mood)->should_be('happy'); | |
* } | |
* | |
* function cleanup() { | |
* unset($this->mum); // kill da mum | |
* } | |
* } | |
* @author - Taylor Luk aka 'speedmax' | |
* @license Free for all | |
*/ | |
class SimpleSpec extends UnitTestCase implements ArrayAccess { | |
public $target; | |
private $negate; | |
private $matcher; | |
function __construct($label = false) { | |
if (! $label) { | |
$label = str_replace(array('Describe_', '_'), array('', ' '), get_class($this)); | |
} | |
$this->matcher = new SpecMatcher($this); | |
parent::__construct($label); | |
} | |
function _isTest($method) { | |
if (strtolower(substr($method, 0, 2)) == 'it' || strtolower(substr($method, 0, 6)) == 'should') { | |
return ! SimpleTestCompatibility::isA($this, strtolower($method)); | |
} | |
return false; | |
} | |
function prepare() { | |
} | |
function cleanup() { | |
} | |
function setUp() { | |
$this->prepare(); | |
} | |
function tearDown() { | |
$this->cleanup(); | |
} | |
function __call($name, $args) { | |
$matcher = null; | |
$this->matcher->negate(false); | |
array_unshift($args, $this->target); | |
if (preg_match('/should_not_(.*)/', $name, $match)) { | |
$matcher = $match[1]; | |
$this->matcher->negate(true); | |
} | |
elseif (preg_match('/should_(.*)/', $name, $match)) { | |
$matcher = $match[1]; | |
} | |
if (!method_exists($this->matcher, $matcher)) { | |
throw new Exception("matcher doesn't exist"); | |
} else { | |
call_user_func_array(array($this->matcher, $matcher), $args); | |
} | |
} | |
function offsetGet($object) { | |
return $this->expect($object); | |
} | |
function offsetSet($key, $value) {} | |
function offsetExists($key) {} | |
function offsetUnset($key) {} | |
function expect($object) { | |
$this->target = $object; | |
return $this; | |
} | |
function value_of($object) { | |
return $this->expect($object); | |
} | |
function assert(&$expectation, $compare, $message = '%s') { | |
$result = $expectation->test($compare); | |
if ($this->matcher->negate) { | |
$result = !$result; | |
} | |
if ($result) { | |
return $this->pass(sprintf($message,$expectation->overlayMessage($compare, $this->_reporter->getDumper()))); | |
} else { | |
return $this->fail(sprintf($message,$expectation->overlayMessage($compare, $this->_reporter->getDumper()))); | |
} | |
} | |
/** | |
* Uses a stack trace to find the line of an assertion. | |
* @return string Line number of first assert* | |
* method embedded in format string. | |
* @access public | |
*/ | |
function getAssertionLine() { | |
$trace = new SimpleStackTrace(array('should', 'it_should', 'assert', 'expect', 'pass', 'fail', 'skip')); | |
return $trace->traceMethod(); | |
} | |
} | |
function expects($subject) { | |
$trace = debug_backtrace(); | |
$object = $trace[1]['object']; | |
return $object->expect($subject); | |
} | |
class SpecProxy { | |
function __construct($class) { | |
$this->object = new $class; | |
} | |
function __call($method, $args) { | |
return user_call_func_array(array($this->object, $method), $args); | |
} | |
} | |
class Have_Matcher { | |
function __construct($subject, $count, $runtime) { | |
$this->subject = $subject; | |
$this->count = $count; | |
$this->runtime = $this->runtime; | |
} | |
function __get($key) { | |
$object = $runtime->target; | |
if (is_array($object) && isset($object[$this->subject])) | |
$subject = $object[$this->subject]; | |
elseif (is_object($object) && isset($object->{$this->subject})) | |
$subject = $object->{$this->subject}; | |
return $this->runtime->be(count($subject), $this->count); | |
} | |
} | |
class SpecMatcher { | |
private $tester; | |
public $negate; | |
function __construct($runtime) { | |
$this->runtime = $runtime; | |
} | |
function negate($bool = false) { | |
$this->negate = $bool; | |
} | |
function be($first, $second, $message = '%s') { | |
return $this->runtime->assert(new EqualExpectation($first), $second, $message); | |
} | |
function be_equal($first, $second, $message = '%s') { | |
return $this->be($first, $second, $message); | |
} | |
function be_empty($subject, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
return $this->be_true(empty($subject) == true, "[{$dumper->describeValue($subject)}] should be empty"); | |
} | |
function be_true($result, $message = '%s') { | |
return $this->runtime->assert(new TrueExpectation(), $result, $message); | |
} | |
function be_false($result, $message = '%s') { | |
return $this->runtime->assert(new FalseExpectation(), $result, $message); | |
} | |
function be_null($value, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
$message = sprintf($message, '[' . $dumper->describeValue($value) . '] should be null'); | |
return $this->runtime->assert(new TrueExpectation(), ! isset($value), $message); | |
} | |
function be_a($object, $type, $message = '%s') { | |
if (strtolower($type) == 'object') | |
$type = 'stdClass'; | |
return $this->runtime->assert(new IsAExpectation($type),$object, $message); | |
} | |
function be_an($object, $type, $message = '%s') { | |
return $this->be_a($object, $type, $message); | |
} | |
function be_within_margin($first, $second, $margin, $message = '%s') { | |
return $this->runtime->assert( | |
new WithinMarginExpectation($first, $margin), | |
$second, | |
$message); | |
} | |
function be_outside_margin($first, $second, $margin, $message = '%s') { | |
return $this->runtime->assert( | |
new assertOutsideMargin($first, $margin), | |
$second, | |
$message); | |
} | |
function be_identical($first, $second, $message = '%s') { | |
return $this->runtime->assert( | |
new IdenticalExpectation($first), | |
$second, | |
$message); | |
} | |
function be_reference_of(&$first, &$second, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
$message = sprintf( | |
$message, | |
'[' . $dumper->describeValue($first) . | |
'] and [' . $dumper->describeValue($second) . | |
'] should reference the same object'); | |
return $this->runtime->assert(new TrueExpectation(), SimpleTestCompatibility::isReference($first, $second), $message); | |
} | |
function be_clone_of(&$first, &$second, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
$message = sprintf( | |
$message, | |
'[' . $dumper->describeValue($first) . | |
'] and [' . $dumper->describeValue($second) . | |
'] should not be the same object'); | |
$identical = new IdenticalExpectation($first); | |
return $this->runtime->assert(new TrueExpectation(), | |
$identical->test($second) && ! SimpleTestCompatibility::isReference($first, $second), | |
$message); | |
} | |
function be_copy_of(&$first, &$second, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
$message = sprintf( | |
$message, | |
"[" . $dumper->describeValue($first) . | |
"] and [" . $dumper->describeValue($second) . | |
"] should not be the same object"); | |
return $this->runtime->assert(new FaseExpectation(), | |
SimpleTestCompatibility::isReference($first, $second), | |
$message); | |
} | |
function contain($subject, $target, $message = '%s') { | |
$dumper = new SimpleDumper(); | |
$message = "[ {$dumper->describeValue($subject)}] should contain [{$dumper->describeValue($target)}]"; | |
if (is_array($subject) && is_array($target)) { | |
return $this->be_true(array_intersect($target, $subject) == $target, $message); | |
} elseif (is_array($subject)) { | |
return $this->be_true(in_array($target, $subject), $message); | |
} elseif (is_string($subject)) { | |
return $this->be_true(strpos($target, $subject) !== false, $message); | |
} | |
} | |
function have($target, $count, $key, $messages = '%s') { | |
$dumper = new SimpleDumper(); | |
$subject = null; | |
if (is_array($target) && isset($target[$key])) | |
$subject = $target[$key]; | |
elseif (is_object($target) && isset($target->$key)) | |
$subject = $target->$key; | |
$result = count($subject); | |
$messages = "Expecting count for [$key] should be [$count], got [$result]"; | |
return $this->be($result, $count, $messages); | |
} | |
function match($subject, $pattern, $message = '%s') { | |
$regex = "/^[\/{#](.*)[\/}#][imsxeADSUXJu]*/sm"; | |
if (preg_match($regex, $subject)) { | |
list($subject, $pattern) = array($pattern, $subject); | |
} | |
return $this->runtime->assert( | |
new PatternExpectation($pattern), | |
$subject, | |
$message); | |
} | |
function expect_error($message = '%s') { | |
$context = &SimpleTest::getContext(); | |
$queue = &$context->get('SimpleErrorQueue'); | |
$queue->expectError($this->runtime->_coerceExpectation($this->negate), $message); | |
} | |
function expect_exception($message = '%s') { | |
$context = &SimpleTest::getContext(); | |
$queue = &$context->get('SimpleExceptionTrap'); | |
// :HACK: Directly substituting in seems to cause a segfault with | |
// Zend Optimizer on some systems | |
$line = $this->runtime->getAssertionLine(); | |
$queue->expectException($this->negate, $message . $line); | |
} | |
} | |
?> |
This file contains hidden or 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 | |
/* Sample helper, well setup with your own taste*/ | |
function_exists('simpletest_autorun') or require 'simpletest/autorun.php'; | |
class_exists('SimpleSpec') or require dirname(__FILE__).'/spec.php'; | |
class_exists('H2o') or require dirname(dirname(__FILE__)).'/h2o.php'; | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment