elazar/phantestic

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# elazar/phantestic

A small PHP testing framework that aims to be simple, fast, modern, and flexible.

Currently in a very alpha state. Feel free to mess around with it, but expect things to break.

[![Build Status](https://img.shields.io/travis/elazar/phantestic.svg)](http://travis-ci.org/elazar/phantestic)
[![Code Climate](https://codeclimate.com/github/elazar/phantestic/badges/gpa.svg)](https://codeclimate.com/github/elazar/phantestic)

## Installation

Use [Composer](https://getcomposer.org).

```json
{
    "require-dev": {
        "elazar/phantestic": "^0.2"
    }
}
```

## Components

A **test loader** loads the tests to be run. It can be anything that implements [`\Traversable`](http://php.net/manual/en/class.traversable.php) (i.e. an instance of a class that implements [`\IteratorAggregate`](http://php.net/manual/en/class.iteratoraggregate.php) or [`\Iterator`](http://php.net/manual/en/class.iterator.php), such as [`\Generator`](http://php.net/manual/en/class.generator.php)) to allow loaded tests to be iterable.

The **test runner** uses the test loader to load tests, run them, and in the process emit multiple events that **test handlers** can intercept and act upon.

As an example, [`LocalRunner`](https://github.com/elazar/phantestic/blob/master/src/Runner/LocalRunner.php) runs tests in the same PHP process as the test runner. Its constructor accepts two arguments: the test loader to use and an array of test handler objects that implement [`HandlerInterface`](https://github.com/elazar/phantestic/blob/master/src/Handler/HandlerInterface.php).

When its `run()` method is called, [`LocalRunner`](https://github.com/elazar/phantestic/blob/master/src/Runner/LocalRunner.php) handles injecting an [event emitter](https://github.com/igorw/evenement/blob/master/src/Evenement/EventEmitterInterface.php) into the test handler objects, which enables those objects to register callbacks with the emitter for any events that may be relevant to them.

An example of a test handler is [`CliOutputHandler`](https://github.com/elazar/phantestic/blob/master/src/Handler/CliOutputHandler.php), which outputs the results of executing tests to `stdout` as they are received and a failure summary once all tests have been run.

## Configuring a Runner

There is no stock test runner; one must be configured based on the needs of your project.

Here's a sample runner configuration.

```php
$classmap_path = '../vendor/composer/autoload_classmap.php';
$loader = new \Phantestic\Loader\ClassmapFileObjectLoader($classmap_path);
$handlers = [ new \Phantestic\Handler\CliOutputHandler ];
$runner = new \Phantestic\Runner\LocalRunner($loader, $handlers);
$runner->run();
```

[`ClassmapFileObjectLoader`](https://github.com/elazar/phantestic/blob/master/src/Loader/ClassmapFileObjectLoader.php) locates tests based on the contents of a classmap file, such as the one generated by the `-o` flag of several [Composer](https://getcomposer.org) commands. By default, it looks for class files with names ending in `Test.php`, instantiates the classes, and invokes methods with names prefixed with `test`. Callbacks used to filter test methods based on file, class, and method names and to generate test case objects can be changed using the constructor parameters of [`ClassmapFileObjectLoader`](https://github.com/elazar/phantestic/blob/master/src/Loader/ClassmapFileObjectLoader.php).

## Writing Tests

Theoretically, tests can be anything [callable](http://php.net/manual/en/language.types.callable.php). The test loader may restrict this to specific types of callables (e.g. [`ClassmapFileObjectLoader`](https://github.com/elazar/phantestic/blob/master/src/Loader/ClassmapFileObjectLoader.php) only supports instance methods). The test loader wraps test callbacks in an instance of a class implementing [`TestInterface`](https://github.com/elazar/phantestic/blob/master/src/Test/TestInterface.php), such as the default [`Test`](https://github.com/elazar/phantestic/blob/master/src/Test/Test.php) implementation.

Failures can be indicated by throwing an [exception](http://php.net/manual/en/language.exceptions.php). Other statuses can be indicated by throwing an instance of a subclass of [`Result`](https://github.com/elazar/phantestic/blob/master/src/Result/Result.php). [`Test`](https://github.com/elazar/phantestic/blob/master/src/Test/Test.php) [converts errors to exceptions](http://php.net/manual/en/class.errorexception.php#errorexception.examples) and considers any uncaught exception to indicate failure. Likewise, no exception being thrown indicates success.

```php
// src/Adder.php
class Adder
{
    public function add($x, $y)
    {
        return $x + $y;
    }
}

// tests/AdderTest.php
class AdderTest
{
    public function testAdd()
    {
        $adder = new Adder;
        $result = $adder->add(2, 2);
        if ($result != 4) {
            throw new \RangeException('2 + 2 should equal 4');
        }
    }
}
```

## Writing Test Handlers

Test handlers implement [`HandlerInterface`](https://github.com/elazar/phantestic/blob/master/src/Handler/HandlerInterface.php), which has a single method: `setEventEmitter()`. This method receives an instance of [`EventEmitterInterface`](https://github.com/igorw/evenement/blob/master/src/Evenement/EventEmitterInterface.php) as its only argument. Within its implementation of `setEventEmitter()`, a test handler can use this argument to register event callbacks. An example of this is below, taken from [`CliOutputHandler`](https://github.com/elazar/phantestic/blob/master/src/Handler/CliOutputHandler.php), which registers its own methods as callbacks for several events.

```php
public function setEventEmitter(EventEmitterInterface $emitter)
{
    $emitter->on('phantestic.test.failresult', [$this, 'handleFail']);
    $emitter->on('phantestic.test.passresult', [$this, 'handlePass']);
    $emitter->on('phantestic.tests.before', [$this, 'beforeTests']);
    $emitter->on('phantestic.tests.after', [$this, 'afterTests']);
}
```

Supported events may vary depending on the test loader and runner in use.

### [`LocalRunner`](https://github.com/elazar/phantestic/blob/master/src/Runner/LocalRunner.php)

* `phantestic.tests.before`: Before any tests are run, with the test runner as an argument
* `phantestic.tests.after`: After all tests are run, with the test runner as an argument
* `phantestic.test.before`: Before each test, with the test case and runner as arguments
* `phantestic.test.after`: After each test, with the test case and runner as arguments
* `phantestic.test.failresult`: When a test fails, with the test case and runner as arguments
* `phantestic.test.passresult`: When a test passes, with the test case and runner as arguments
* `phantestic.test.RESULT`: When a test has a result other than passing or failing where `RESULT` is the short name of the class extending [`Result`](https://github.com/elazar/phantestic/blob/master/src/Result/Result.php), with the test case and runner as arguments

### [`ClassmapFileObjectLoader`](https://github.com/elazar/phantestic/blob/master/src/Loader/ClassmapFileObjectLoader.php)

* `phantestic.loader.loaded`: When a test case is loaded, with the test case and associated test class name and test method name as arguments

## Differences from PHPUnit

Tests may be instance methods of classes, but they don't have to be since individual tests can be anything callable. If you do choose to use instance methods for tests:

* There is no equivalent to `PHPUnit_Framework_TestCase`. You may create your own base class if you wish, but it is not required.
* There are no equivalents to `setUp()` or `tearDown()`. Consider using either `__construct()` and `__destruct()` or test handlers in conjunction with a loader that supports `phantestic.test.before` and `phantestic.test.after`.
* If you wish for tests to be located using the same criteria as PHPUnit and you're using Composer, just use [`ClassmapFileObjectLoader`](https://github.com/elazar/phantestic/blob/master/src/Loader/ClassmapFileObjectLoader.php) and specify only the classmap file path when instantiating it.
* Assertions are merely methods that throw exceptions if expected conditions are not met. Consider supplementary libraries like [those recommended for use with Peridot](https://github.com/peridot-php/peridot/wiki/Matchers).
* Mocking is not supported natively. Consider supplementary libraries like [Phake](https://github.com/mlively/phake) or [Mockery](https://github.com/padraic/mockery).
* Database seeding and assertions are not supported natively. Consider supplementary libraries like [Phactory](https://github.com/chriskite/phactory) or [Faker](https://github.com/fzaninotto/Faker).