Skip to content

Instantly share code, notes, and snippets.

@speedmax
Created October 3, 2008 08:55
Show Gist options
  • Save speedmax/14530 to your computer and use it in GitHub Desktop.
Save speedmax/14530 to your computer and use it in GitHub Desktop.
SimpleSpec - SimpleTest extension for Behavior driven development(BDD)
<?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);
}
?>
<?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);
}
}
?>
<?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