I've been thinking some about Behavior Driven Development (BDD) and how it would be nice for PHP to have a simple and intuitive BDD framework. (Currently I don't think it has any, so actually something would be better than nothing)
I've been sort of looking at both rSpec and JBehave to get some ideas. One thing I like about JBehave are the "Stories" that it uses for acceptance testing. And I like how rSpec sets up fine grained specifications. I was thinking that it would be nice to sort of combine the two, so you can have specifications that can be as finely grained or as broad as you want, which makes it easy to use the same framework for all specification related tests.
One really nice thing about the Stories in JBehave is that the "tests" are split up into 3 different areas: Contexts, Events, and Outcomes ... which means that you can reuse them as needed without copying and having duplicate code. So I am using this idea too.
And instead of Pass/Fail as traditional TDD does, to completely break away from the mentality, I was thinking of having 4 basic outcomes of a spec run:
1. Implemented, means no errors, and no mocks in the world
2. Implementing, means no errors, but the world still uses mocks
3. Not Implemented, means has errors or is missing a context, event, or outcome
4. Skipped, means skipped (e.g spec not relevant to this platform)
Specs will also be allowed to be nested, and the spec run will output the results in a sort of outline format, which can serve a multitude of purposes.
A report output might look something like this (example output in plain text for a string utility):
I've come up with the interface for the system, and would appreciate some feedback if you have any. The specs I used for the example are based on the specs from the rSpec tutorial. In addition to an easy to use interface, I've tried to make it as readable as possible, so I would appreciate any comments/suggestions.Code:SAMPLE REPORT OUTPUT String Util Specs [... Implementing ...] - A string util should count words [Implementing] - should count ascii words [Implemented] - should count accented words [Implemented] - should count chinese words [Implementing] - with spaces [Implementing] - with no spaces [Implementing] 2 / 6 specs Implemented 4 / 6 specs Implementing 0 / 6 specs Not Implemented 0 / 6 specs Skipped
First setting up the specs in plain english:
The Contexts, Events, and Outcomes are derived from the Givens, Whens, and Thens, i.e, "Given these contexts, when these events, then these outcomes". The plain english specs match up to the Context, Event, and Outcome names.PHP Code:class Stack_Specs extends The_Specs {
public function defineSpecs() {
$this->addSpec(
'A new stack should be empty', $this
-> Given ('a new stack')
-> When ('check if stack is empty')
-> Then ('stack should be empty')
);
$this->addSpec(
'A stack with one item should not be empty', $this
-> Given ('a stack with one item')
-> When ('check if stack is empty')
-> Then ('stack should not be empty')
);
$this->addSpec(
'A stack with one item should return top item when you request top', $this
-> Given ('a stack with one item')
-> When ('request top from stack')
-> Then ('result should equal one item')
);
}
}
Now the 2 Contexts (both are using mocks to show that all specs should start out using mocks). A Context "sets up" The World (each spec is run in its own world):
PHP Code:class A_New_Stack_Context extends A_Context {
public function setsUp(The_World $world) {
$stack = $world->mock('Stack');
$stack->method('isEmpty')->returns(true);
$world->stack = $stack;
}
}
PHP Code:class A_Stack_With_One_Item_Context extends A_Context {
public function setsUp(The_World $world) {
$stack = $world->mock('Stack');
$stack->method('isEmpty')->returns(false);
$stack->method('top')->returns('one item');
$world->stack = $stack;
}
}
Then the Events. An Event "occurs in" The World:
PHP Code:class Check_If_Stack_Is_Empty_Event extends An_Event {
public function occursIn(The_World $world) {
$world->result = $world->stack->isEmpty();
}
}
PHP Code:class Request_Top_From_Stack_Event extends An_Event {
public function occursIn(The_World $world) {
$world->result = $world->stack->top();
}
}
And finally the Outcomes. An Outcome "sets expectations in" The World:
PHP Code:class Result_Should_Equal_One_Item_Outcome extends An_Outcome {
public function setsExpectationsIn(The_World $world) {
$this->value($world->result)->should->equal('one item');
}
}
PHP Code:class Stack_Should_Be_Empty_Outcome extends An_Outcome {
public function setsExpectationsIn(The_World $world) {
$this->value($world->result)->should->be->true;
}
}
PHP Code:class Stack_Should_Not_Be_Empty_Outcome extends An_Outcome {
public function setsExpectationsIn(The_World $world) {
$this->value($world->result)->should->be->false;
}
}
A spec can also have multiple Contexts and multiple expected Outcomes. I'm debating on whether or not multiple Events should be allowed. To setup multiple ones, you'd just use And(). Here is an example:
PHP Code:class My_Specs extends The_Specs {
protected function defineSpecs() {
$this->addSpec(
'spec description', $this
-> Given ('one context')
-> And ('another context')
-> When ('an event')
-> Then ('expect outcome 1')
-> And ('expect outcome 2')
);
}
}
So there is the basic proposal. Any thoughts, ideas, feedback, criticisms, rants, or raves?![]()






Bookmarks