2017 © Pedro Peláez
 

project sphec

A Behavior Driven Development (BDD) toolkit in PHP.

image

kirkbowers/sphec

A Behavior Driven Development (BDD) toolkit in PHP.

  • Tuesday, August 15, 2017
  • by kirkbowers
  • Repository
  • 1 Watchers
  • 0 Stars
  • 11 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 8 Versions
  • 0 % Grown

The README.md

Sphec

A Behavior-Driven Development (BDD) toolkit in PHP., (*1)

Introduction

Sphec is a Behavior-Driven Development (BDD) toolkit for PHP that is largely inspired by RSpec, Jasmine, and to a degree by Cucumber. The name is somewhat of a contraction of "spec" and "PHP". It provides a domain specific language (DSL) that is a mixture of natural language shorthand and quasi-PHP for describing how a part of your software should behave and writing tests to ensure it does, in fact, behave that way., (*2)

Some features are:, (*3)

  • Hierarchical contexts for grouping related tests and setting up different pre-test conditions.
  • before and after actions to set up and tear down pre-test conditions.
  • "Local" variables that propagate from the outer context up to each contained example.
  • Tests that are phrase as expectations.

Here's what an example Sphec file looks like, with comments to point out some of the features:, (*4)

require_once __DIR__ . '/../vendor/autoload.php';

// Specify the behavior of a class
specify TestProject\Accumulator

  // Describe how a method of that class is expected to behave
  describe add

    // Set up a subcontext with pre-test conditions shared by multiple examples.
    context with default starting value

      // Set up those pre-test conditions in a before action
      before
        // "Local" variables to be shared by blocks within a context are declared
        // using the @ symbol
        @accumulator = new TestProject\Accumulator;

      // Provide examples of what the expected behavior is.
      it should start with a zero value
        // Note, the "local" member variable accumulator that was create in `before` is
        // available inside the examples.

        // Write tests as expectations.
        expect(@accumulator->get_value())->to_be(0);

      it should accumulate a single value
        @accumulator->add(3);
        expect(@accumulator->get_value())->to_be(3);

      it should accumulate more than one value
        @accumulator->add(3);
        @accumulator->add(5);
        expect(@accumulator->get_value())->to_be(8);

    // Set up a different context with different pre-test conditions.
    context with supplied starting value
      before
        @accumulator = new TestProject\Accumulator(2);

      it should start with a zero value
        expect(@accumulator->get_value())->to_be(2);

      it should accumulate a single value
        @accumulator->add(3);
        expect(@accumulator->get_value())->to_be(5);

      it should accumulate more than one value
        @accumulator->add(3);
        @accumulator->add(5);
        expect(@accumulator->get_value())->to_be(10);

Under the hood, this gets converted to PHP and then eval'd. Since PHP does not have the notion of blocks like Ruby does, a lot has to be done with anonymous functions that are passed the working scope as a parameter. If you don't mind working with that bit of extra verbosity, you can write your Sphec specs in straight PHP., (*5)

Here's what the above sphec shorthand gets converted into in actual PHP:, (*6)

<?php

require_once __DIR__ . '/../vendor/autoload.php';

// Specify the behavior of a class
Sphec\Sphec::specify('TestProject\Accumulator', function($spec) {

  // Describe how a method of that class is expected to behave
  $spec->describe('add', function($spec) {

    // Set up a subcontext with pre-test conditions shared by multiple examples.
    $spec->context('with default starting value', function($spec) {

      // Set up those pre-test conditions in a before action
      $spec->before(function($spec) {
        $spec->accumulator = new TestProject\Accumulator;
      });

      // Provide examples of what the expected behavior is.
      $spec->it('should start with a zero value', function($spec) {
        // Note, the "local" member variable accumulator that was create in `before` is
        // available inside the examples.

        // Write tests as expectations.
        $spec->expect($spec->accumulator->get_value())->to_be(0);
      });

      $spec->it('should accumulate a single value', function($spec) {
        $spec->accumulator->add(3);
        $spec->expect($spec->accumulator->get_value())->to_be(3);
      });

      $spec->it('should accumulate more than one value', function($spec) {
        $spec->accumulator->add(3);
        $spec->accumulator->add(5);
        $spec->expect($spec->accumulator->get_value())->to_be(8);
      });      
    });

    // Set up a different context with different pre-test conditions.
    $spec->context('with supplied starting value', function($spec) {
      $spec->before(function($spec) {
        $spec->accumulator = new TestProject\Accumulator(2);
      });

      $spec->it('should start with a zero value', function($spec) {
        $spec->expect($spec->accumulator->get_value())->to_be(2);
      });

      $spec->it('should accumulate a single value', function($spec) {
        $spec->accumulator->add(3);
        $spec->expect($spec->accumulator->get_value())->to_be(5);
      });

      $spec->it('should accumulate more than one value', function($spec) {
        $spec->accumulator->add(3);
        $spec->accumulator->add(5);
        $spec->expect($spec->accumulator->get_value())->to_be(10);
      });      
    });
  });
});

Note that every anonymous function takes a parameter named $spec. It can be named anything you like (except $this), but using $spec by convention is recommended. The scope provided by the $spec variable will give access to the various commands that are available in that scope as well as any "local" member variables., (*7)

Installing Sphec

The preferred way to install Sphec is with Composer. Assuming you have Composer installed, run:, (*8)

composer global require kirkbowers/sphec

Then, assuming you have $COMPOSER_HOME/vendor/bin in your executable path, you should be good to go., (*9)

Running Sphec

Sphec is a command line tool. By default, it expects to be run in the root directory of a project, it expects all specs to be in a directory called spec, and it expects all spec files to end in either the suffix _spec.sphec or _spec.php., (*10)

Files ending with the suffix .sphec will be treated as Sphec shorthand and will be converted to PHP before being run. Files ending with .php will be run as is., (*11)

If those assumptions hold, you can simply run it on the command line:, (*12)

sphec

This will show the output as a string of dots and/or F's for each test that passed or failed, followed by a summary:, (*13)

$ sphec

..............................................................................................................
Successes: 110, Failures: 0
Success!

To see more verbose output showing the hierarchy of all contexts and examples run, use the -v flag., (*14)

If you want to run only one particular spec file, pass the path to the file as an argument to sphec. Likewise, if you want to only run the tests in a particular directory, pass the path to the directory and it will run all specs named *_spec.sphec and *_spec.php in that and all subdirectories., (*15)

Different scopes: Context, Setup, and Example

While inside a Sphec specification, there are three different types of scopes that you can be inside of: context, setup and example., (*16)

Context scope

You are in a context scope whenever you are just inside a specify, describe, or context block. Context blocks are executed immediately in order to build the tree of subcontexts and examples. The available commands inside of a context scope are:, (*17)

  • describe $label
  • context $label
  • before
  • after
  • it $label

Local member variables are not available inside of a context scope block., (*18)

Setup scope

You are in a setup scope whenever you are inside a before or after. Setup blocks are executed lazily, only at the time that tests are actually run. There are no special commands available inside of a setup block. Local member variables are available, as this is where they should be set up or torn down., (*19)

Example scope

You are in an example scope whenever you are inside an it block. Example blocks are executed lazily, only at the time that tests are actually run. The one special command available is expect. Local member variables are available, as this is where they should be set up or torn down., (*20)

Expectations

Tests are performed inside of examples by stating expectations. An expectation always takes this form:, (*21)

  expect($computed)->test($expected);

Where test is one of the test methods listed below (some tests, like to_be_falsey, do not take an $expected parameter). Execution will continue after any expectations that fail, with all the failures gathered up and reported on the console after all tests have been performed., (*22)

Available tests

to_be_equivalent($expected) passes if the computed and expected values are both the same value and same type. The test is performed with ===., (*23)

to_be($expected) is a shortcut alias for to_be_equivalent., (*24)

to_equal($expected) passes if the computed and expected values are the same value after type coercion. The test is performed with ==., (*25)

to_not_be($expected) passes if the computed and expected values are either a different value or a different type. The test is performed with !==., (*26)

to_not_equal($expected) passes if the computed and expected values are not the same value after type coercion. The test is performed with !=., (*27)

to_be_true() passes if the computed value is strictly true (of type boolean)., (*28)

to_be_truthy() passes if the computed value is true after type coercion., (*29)

to_be_false() passes if the computed value is strictly false (of type boolean)., (*30)

to_be_falsy() passes if the computed value is false after type coercion. In PHP a lot of values satisfy this condition, including false, null, 0, "0", array(), and an unset variable., (*31)

to_be_greater_than($expected) passes if the computed value is strictly greater than the expected value (after type coercion)., (*32)

to_be_greater_than_or_equal($expected) passes if the computed value is greater than or equal to the expected value (after type coercion)., (*33)

to_be_less_than($expected) passes if the computed value is strictly less than the expected value (after type coercion)., (*34)

to_be_less_than_or_equal($expected) passes if the computed value is less than or equal to the expected value (after type coercion)., (*35)

to_throw($expected) passes if the anonymous function passed to expect throws the expected exception (or a subclass) when executed. This one works a little different than the above tests. expect must be passed an anonymous function that takes no parameters. $expected is the name of an expected exception as a string., (*36)

not_to_throw() passes if the anonymous function passed to expect executes without throwing any exceptions. expect must be passed an anonymous function that takes no parameters., (*37)

Local variables

"Local" variables can be declared in before and after blocks and consumed inside it examples. The shorthand for a local variable the at sign (@) followed by a variable name. So, assuming you are inside a context, this works:, (*38)

    before
      @foo = 3;
    it uses a local var in an example
      expect(@foo)->to_be(3);

Scope is defined by indent level

Much like Python and CoffeeScript, the scope of blocks in Sphec shorthand is controlled by the level of indentation. For example, the two context blocks below are peers:, (*39)

specify MyClass
  describe some_method
    before
      @foo = 3
    context in a certain situation
      before
        @bar = 0
      it behaves a certain way
        ...
    context in a different situation
      before
        @bar = 42
      it behaves differently

Local variables go in and out of scope depending on the containing context. In the above example, @foo is shared by both context blocks, but the two context blocks have their own unique copy of @bar., (*40)

There are three notable exceptions to the rule about indenting., (*41)

The inside of before, after, and it blocks is quasi-PHP. That is, it's PHP with @ local vars and expect commands that will be expanded by the Sphec parser. If you need further inner scopes inside of these blocks, it should be handled by the usual PHP curly braces. To make this concrete, if you need to declare an anonymous function to test for a raised exception, you can indent further inside your it block to style it nicely:, (*42)

    it might do something catastrophic
      // Notice, the braces are needed, and the extra indent is legal
      $myfunc = function() {
        // Do something that might throw an exception
      };
      expect($myfunc)->to_throw('CatastrophicException');

Another exception is comments. The indent level of comments are ignored, so it is legal to comment out sections by prepending the lines of code with double slashes or hashes., (*43)

  context under certain circumstances
    it does something
      ...
//     it won't work!  This is pending.
//       ...

    // We are still inside the "under certain circumstances" block
    it does something else
      ...

A third exception is heredocs. Inside of before, after and it blocks it is legal to assign values to variables using the heredoc notation. In this case, the inside of the heredoc, and especially the marker closing the heredoc, may (must) be left justified. Normal indenting rules resume once the heredoc is closed., (*44)

  content under certain circumstances
    it creates a multiline string
      $blech = some_function_call();
      $foo = <<<EOS
This left justified string does not break the indentation rules.
I have a few other things I could say...
EOS;

      expect($blech).to_be($foo);

Future Areas of Development

Sphec is in its early stages of development and, although quite feature rich, has room to grow. Here are some anticipated, or hoped for, future features., (*45)

  • Most interesting applications work with a database. In the Rails ecosystem there are strong tools for setting up and populating a test database. It would be nice to have similar capabilities in PHP-land, especially when working with WordPress.
  • Pending examples (using the xit command) would be nice.
  • Be able to continue running tests after an error has occurred. Currently, the program will simply bomb out. RSpec will keep going and report an "E" for any test that has an error.

Contributing

Sphec is totally open source and I welcome any help to flesh it out. If you'd like to contribute, do the usual fork, create a branch off of development, do your thing then send me a pull request., (*46)

Testing

Sphec tests itself! Kinda' meta, eh?, (*47)

There are three ways to test Sphec if you are working on it., (*48)

One, there are tests of Sphec's core functionality. These tests go through a bit of gymnastics to unit test the inner workings, but they run directly off the root directory. To run them, simply run:, (*49)

bin/sphec

Two, there are tests that all fail so that the failure messages can be visually inspected. I'm of the strong mindset that any UI element, even if it is just printing to the console, must be visually inspected to make sure it looks as you'd expect. To inspect the failure messages, run:, (*50)

bin/sphec failure_spec

Three, there is a test project that runs the latest development branch from github on a more conventional suite of tests (without going through the hoops necessary for Sphec to test itself). To test them, run these steps from the project root:, (*51)

cd test_project/
composer update 
vendor/bin/sphec

You should see a "Success!" message., (*52)

Enjoy!, (*53)

The Versions

15/08 2017

dev-master

9999999-dev

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

15/08 2017

v0.2.2

0.2.2.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

14/08 2017

dev-development

dev-development

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

13/08 2017

v0.2.1

0.2.1.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

13/08 2017

V0.2.0

0.2.0.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

13/08 2017

v0.1.2

0.1.2.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

07/08 2017

v0.1.1

0.1.1.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers

31/07 2017

v0.1.0

0.1.0.0

A Behavior Driven Development (BDD) toolkit in PHP.

  Sources   Download

MIT

The Requires

 

by Kirk Bowers