elazar / phantestic
A small PHP testing framework
Requires
- php: >=5.5.0
- evenement/evenement: ^2
This package is auto-updated.
Last update: 2024-10-29 04:36:31 UTC
README
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.
Installation
Use Composer.
{ "require-dev": { "elazar/phantestic": "^0.2" } }
Components
A test loader loads the tests to be run. It can be anything that implements \Traversable
(i.e. an instance of a class that implements \IteratorAggregate
or \Iterator
, such as \Generator
) 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
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
.
When its run()
method is called, LocalRunner
handles injecting an event emitter 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
, 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.
$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
locates tests based on the contents of a classmap file, such as the one generated by the -o
flag of several Composer 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
.
Writing Tests
Theoretically, tests can be anything callable. The test loader may restrict this to specific types of callables (e.g. ClassmapFileObjectLoader
only supports instance methods). The test loader wraps test callbacks in an instance of a class implementing TestInterface
, such as the default Test
implementation.
Failures can be indicated by throwing an exception. Other statuses can be indicated by throwing an instance of a subclass of Result
. Test
converts errors to exceptions and considers any uncaught exception to indicate failure. Likewise, no exception being thrown indicates success.
// 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
, which has a single method: setEventEmitter()
. This method receives an instance of EventEmitterInterface
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
, which registers its own methods as callbacks for several events.
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
phantestic.tests.before
: Before any tests are run, with the test runner as an argumentphantestic.tests.after
: After all tests are run, with the test runner as an argumentphantestic.test.before
: Before each test, with the test case and runner as argumentsphantestic.test.after
: After each test, with the test case and runner as argumentsphantestic.test.failresult
: When a test fails, with the test case and runner as argumentsphantestic.test.passresult
: When a test passes, with the test case and runner as argumentsphantestic.test.RESULT
: When a test has a result other than passing or failing whereRESULT
is the short name of the class extendingResult
, with the test case and runner as arguments
ClassmapFileObjectLoader
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()
ortearDown()
. Consider using either__construct()
and__destruct()
or test handlers in conjunction with a loader that supportsphantestic.test.before
andphantestic.test.after
. - If you wish for tests to be located using the same criteria as PHPUnit and you're using Composer, just use
ClassmapFileObjectLoader
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.
- Mocking is not supported natively. Consider supplementary libraries like Phake or Mockery.
- Database seeding and assertions are not supported natively. Consider supplementary libraries like Phactory or Faker.