highly extensible, highly enjoyable, PHP testing framework.
Peridot is a modern testing tool for PHP 5.4+. It makes testing fun again. It can easily be extended via an awesome event system, allowing developers to create plugins and custom reporters with ease. Follow and contribute on GitHub.
Features
- Flexible testing interface
- Event driven plugin architecture
- Easily add user defined cli options
- Easily create custom test reporters
- Pass/fail based on exceptions
- Mix in functionality via child scopes
- Create your own testing DSLs
Installation
Peridot can be installed several ways.
Globally (Composer)
$ composer global require peridot-php/peridot:1.0.0
And make sure the .composer/vendor/bin
is on your PATH
export PATH="$PATH:$HOME/.composer/vendor/bin"
Locally (per project)
$ composer require peridot-php/peridot:1.0.0
Manual (local)
The latest stable phar can be downloaded here. You can place it anywhere you like.
Manual (global)
Use the following commands to install the peridot
command for use anywhere:
$ sudo wget http://peridot-php.github.io/downloads/peridot.phar -O /usr/local/bin/peridot
with curl:
$ sudo curl http://peridot-php.github.io/downloads/peridot.phar -o /usr/local/bin/peridot
then:
$ sudo chmod a+x /usr/local/bin/peridot
Getting Started
<?php //arrayobject.spec.php
describe('ArrayObject', function() {
beforeEach(function() {
$this->arrayObject = new ArrayObject(['one', 'two', 'three']);
});
describe('->count()', function() {
it("should return the number of items", function() {
$count = $this->arrayObject->count();
assert($count === 3, "expected 3");
});
});
});
?>
$ peridot arrayobject.spec.php
ArrayObject
->count()
✓ should return the number of items
1 passing (19 ms)
Peridot DSLs
By default, Peridot uses a BDD style DSL for describing your code's behavior via specs. Here's the breakdown:
-
describe($description, $func)
This function creates a suite, and is used to organize behaviors. It's fair game to nest these.
-
context($description, $func)
This function is identical to
describe
. It is provided to offer additional organization for your specs. -
beforeEach($setupFunc)
You can add setup functions that execute for all specs using this function. If you set variables on
$this
inside of the function body, it will create instance variables that are inherited by specs and suites. -
afterEach($tearDownFunc)
You can add teardown functions that execute for all specs using this function.
-
it($description, $func)
This function creates a single spec and adds it to the suite it is nested in.
Pending Specs
You can make any spec our suite pending by prefixing the function with an x.
<?php
xdescribe("A pending suite", function() {
xcontext("when using a pending context", function() {
xit("should have a pending spec", function() {
});
});
});
?>
Custom DSLs
You can also create your own testing DSLs! Check out Example: Creating An Acceptance Testing DSL.
Assertions
A Peridot test fails when an exception is thrown. Peridot's default DSL configures the default behavior of PHP's native assert function to throw exceptions, but you could just as easily use an existing matcher library or roll your own.
If you're looking for an existing PHP matcher library, you might want to look at Hamcrest.
Plugins
Peridot plugins are easy to write. Peridot exposes a handful of events that can be hooked into via a configuration file.
Peridot will look for a file called peridot.php
in the current working directory, or one can be specified using the --configuration
option via the console. The configuration file will be included once, and it should return a function that accepts a single EventEmitter containing ->on()
and ->emit()
methods.
<?php //peridot.php
return function(EventEmitterInterface $emitter) {
$emitter->on('test.failed', function($test) {
//do something with the failed test
});
}
?>
For more information see Example: Creating An Acceptance Testing DSL.
Coming soon: "Creating The Peridot Code Coverage Plugin."
Reporters
Peridot ships with a spec reporter for printing test results in a hierarchal manner. However, you can easily create your own reporters.
Reporters can be registered via the configuration file like any other plugin through the peridot.reporters
event.
Check out Peridot's own peridot.php file for an example of creating a custom reporter.
peridot
Usage: peridot [options] [files] Options: --grep (-g) Run tests matching <pattern> (default: *.spec.php) --no-colors (-C) Disable output colors --reporter (-r) Select which reporter to use (default: spec) --bail (-b) Stop on failure --configuration (-c) A php file containing peridot configuration --reporters List all available reporters --version (-V) Display the Peridot version number --help (-h) Display this help message.
-g, --grep
Only run test files whose name matches the provided pattern. The default grep pattern is *.spec.php
-C, --no-colors
Disable colors in output.
-r, --reporter
Select which reporter to use. This is the reporter name registered via ReporterFactory::register.
-b, --bail
Tell peridot to stop running tests as soon as there is a failure.
-c, --configuration
A path to a peridot configuration file. Defaults to getcwd() . '/peridot.php'
--reporters
List available test reporters.
Scopes
Peridot is able to safely use $this
inside of test closures because of scopes. Scopes allow us to place state on a test without conflicts.
Scopes also allow mixing in behavior to tests via a powerful concept called child scopes. Consider the following:
<?php //peridot.php
class WebDriverScope extends Scope
{
protected $driver;
protected $emitter;
public function __construct(RemoteWebDriver $driver, EventEmitterInterface $emitter)
{
$this->driver = $driver;
$this->emitter = $emitter;
$this->emitter->on('runner.end', function() {
$this->driver->quit();
});
}
public function getPage($url)
{
$this->driver->get($url);
}
public function findElementById($id)
{
return $this->driver->findElement(\WebDriverBy::id($id));
}
}
return function(EventEmitterInterface $emitter) {
$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome());
$driverScope = new WebDriverScope($driver);
$emitter->on('suite.start', function($test) use ($driverScope) {
$test->getScope()->peridotAddChildScope($driverScope);
});
}
?>
By mixing the WebDriverScope
in to our test's scopes, we have made the following possible in our tests:
<?php
describe('The home page', function() {
it('should have a greeting', function() {
$this->getPage('http://localhost:4000');
$greeting = $this->findElementById('greeting');
assert($greeting->getText() === "Hello", "should be Hello");
});
});
?>
For a fully functional example of the above, checkout the Peridot scope example on Github.
Between scopes, events, and DSLs - Peridot gives you plenty of opportunities for extension.
Event Reference
The following events are exposed by Peridot's event emitter:
-
peridot.start
Fires when the Peridot application is constructed. Useful for defining additional CLI arguments and options.
Arguments:
Environment$environment
-
peridot.configure
Fires when the Peridot application is configured.
Arguments:
Configuration$configuration
-
peridot.execute
Fires right before Peridot starts execution. Happens after CLI options are parsed and before specs are loaded.
Arguments:
InputInterface$input
OutputInterface$output
-
peridot.reporters
Fires when Peridot reporters are registered. Useful for registering additional reporters.
Arguments:
InputInterface$input
ReporterFactory$reporters
-
peridot.load
Fires right before Peridot starts loading tests. Useful for changing loading behavior.
Arguments:
Command$command
Configuration$configuration
-
peridot.end
Fires when the Peridot application exits.
Arguments:
integer$exitCode
InputInterface$input
OutputInterface$output
-
runner.start
Fires right before the suite runner starts.
Arguments: No arguments
-
runner.end
Fires right after the suite runner ends.
Arguments: No arguments
-
suite.start
Fires right before a suite runs.
Arguments:
Suite$suite
-
suite.end
Fires right after a suite runs.
Arguments:
Suite$suite
-
suite.halt
When fired, this event signals a suite to stop running.
Arguments: No arguments
-
test.passed
Fires when a test passes.
Arguments:
Test$test
-
test.pending
Fires when a test pends.
Arguments:
Test$test
-
test.failed
Fires when a test fails.
-
test.start
Fires right before a test runs.
Arguments:
Test$test
-
test.end
Fires right after a test runs.
Arguments:
Test$test
-
error
Fires when a PHP error occurs. The standard error arguments passed to a function registered via PHP's native
set_error_handler
will be passed to this event.Arguments:
integer$errno
string$errstr
string$errfile
string$errline
Example Test Suites
Below are examples of real projects in the wild using Peridot.
Running peridot's tests
Peridot's tests were written using Peridot. After cloning peridot, you can run tests using:
$ bin/peridot specs/