AperiTestCase

I've added a simple, trait-like way of adding new features to test cases and fixture plug-ins. This helps to write cleaner, easy-to-read tests.

You can mix and match tools for different domains in ways which are impossible with standard SimpleTest. Need to test a shell script which writes to a file? Just mix in the filesystem fixture tools with the shell test tools — and then add any number of custom fixtures, assertions or etc of your own on top. Everything can be re-used in other tests in any combination, as required.

Note that AperiTestCase exists a level down in the hierarchy. You probably won't want to use it directly. Extend AperiUnitTest to create a unit test. Web tests are derived from AperiWebTest. All the usual web test / unit test tools are available as well as a few extras.

The following table lists all the extra methods added to the standard SimpleTest set.

Test Case Methods: Fixtures & Decorators

Environment

These can be useful when setting up skip conditions.

Additional Framework Hooks

Like the other framework hooks (skip, setUp, tearDown, etc), these methods are optional.

BeforeInvoking/afterInvoking are called outside the setUp/tearDown cycle so be careful what you put in them. For example, you wouldn't want to create an instance of the tested class here. You'll need a fresh instance for each new test — use setUp instead.

Other Methods

Decorating Test Cases

Often you'll want to add extra capabilities to the base test case class — assertions, fixtures etc — to help test a new domain. Extending the basic SimpleTest framework with a derived class is probably the first and simplest choice. However, as the complexity of the extra code increases, you'll very quickly run into the usual problems with inheritance.

As an example, I've had to test a cli app (Rephactor) which interacts with a database, a subversion repository, and the file system. At the end of all that I had to assert stdout/stderr output from the script. A variety of tools and assertions are required for these different domains. Sometimes I'll need to use them separately, sometimes all together, but there's no sane way to do that using inheritance. You can't just stack a series of unrelated behaviours one on top of the other.

This sort of problem would normally be a signal to refactor, dumping inheritance and using aggregation instead. That's exactly what I've done here with a traits-like system of "method injection". It's very easy to plug-in extra features to a basic test case. Different toolsets can be combined in any way you need for a particular test.

You can also add discrete fixture objects to test cases using the same mechanism.

Adding Test Case Decorators

Decorators should be added in a beforeInvoking() method. All the decorator's methods will magically become methods of the test case:

<?php    

class MyDecorator {

    function 
foo() {
        ...
    }
}
class 
MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->mixin(new MyDecorator));
    }
    function 
testSomething() {
        ...
        
$this->foo(); // donated by MyDecorator
        
...
    }

How many decorators can you add? As many as you like:

<?php    

class MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->mixin(new A);
        
$this->mixin(new B);
        
$this->mixin(new C);
        ...
        ...
    }

Decorators can be mixed and matched freelly in almost any combinations. The sole restriction is that a given method cannot be implemented by more than one decorator (except for framework hooks skip, setUp, teardown etc — see later). If different decorators try to add the same method an exception will be thrown.

Note: this "method conflict" check looks for public methods of the same name in different decorators. It is assumed that public / non-public methods are distinguished by the use of underscores in the old php4 way. Php5 visibility keywords have not been tested. Personally, I've never found a use for these but I could update the code if there was a demand.

Note that method calls are only passed on to the decorator stack if they are not implemented by the test case. Hence, a test case foo() always overrides any decorator foo(). If you want to override a method in a fixture/decorator class either implement it in the test case or extend the fixture/decorator class itself.

Debugging

Since a given method may be implemented by the test case, one of its antecedents, or one of the decorators (or by nothing at all) how can you find out which one it is?

<?php    

class MyTestCase extends AperiUnitTest {

    function 
test() {
        ...
        
$this->dumpClassWith('foo');
        ...
    }

Or show the complete list of callable methods and their declaring classes:

<?php    

class MyTestCase extends AperiUnitTest {

    function 
test() {
        
$this->dumpInterface();
    }

Decorate a Decorator

A test case can decorate its decorators. This isn't as daft as it might sound. Sometimes a decorator will provide a base functionality which you want to refine in the test.

<?php    

class MyTestCase extends AperiUnitTest {

    function 
foo() {
        
// ...
        // do some additional thing
        // ...
        
$this->decorators()->foo();
    }

$this->decorators() is a composite object containing all decorators. The call will be picked up by the object which implements foo().

Adding New Assertions

A new domain typically requires some new assertions. Sometimes these are useful simply to keep the code clean. At other times the standard set of assertions just don't know how to deal with the new domain. For example, you might want to assertPing() of a connection object or assertStdout() of a shell script.

Once you've written (and tested!) a new expectation class, the next step would normally be to add a corresponding assert~ method to the test case:

<?php    

class MyTestCase extends AperiUnitTest {

    function 
assertFoo($candidate$message '%s') {
        
$this->assert(
            new 
FooExpectation;
            
$candidate
            
$message);
    }

Alternatively, if you think you'll need to use this again in other tests, you can add the method to a decorator (which can be mixed in with any number of other decorators and test cases). Pass the test case in to the decorator and hook in to the simpletest framework in the usual way:

<?php    

class FooDecorator {

    function 
__construct($test_case) {
        
$this->_test_case $test_case;
    }
    function 
assertFoo($candidate$message '%s') {
        
$this->_test_case->assert(
            new 
FooExpectation;
            
$candidate
            
$message);
    }
    ...
    ...
}

Add the FooDecorator to the test case:

<?php    
class MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->mixin(new FooDecorator($this));
    }
    function 
testSomething() {
        ...
        ...
        
$this->assertFoo(...);
    }

The assertFoo() method is now available to the test case.

Dependency Injection

You can pass a class name to mixin() rather than an object instance and leave the messy instantiation details to Phemto. Phemto can often figure out dependencies without any special configuration but obviously it's up to you to sort that out.

Note that the the test case's injector instance, $this->injector(), will be used to assemble objects. The current test case class and the chosen SimpleReporter-derived reporter will be pre-registered.

As an example, the above can be simplified to:

<?php    

class FooDecorator {

    function 
__construct(AperiTestCase $test_case) { // type hint!
        
$this->_test_case $test_case;
    }
    ...
    ...
}

class 
MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->mixin('FooDecorator'); // just the class name
    
}

This feature was added purely for the convenience of automatic instantiation. I wasn't thinking about injectable decorator behaviours. Neither should you, probably, but there might be some uses. Occasionally I've needed an unwritable file or dir in a filesystem fixture. On nix, "/root/" can be used but obviously this won't work on windows. An injectable fixture might be useful in similar situations. Test suites could automatically configure themselves for different OS's or users could inject a fixture of their own.

Note that it's best to type hint on "AperiTestCase" rather than the derived classes AperiUnitTest or AperiWebTest. If you hint higher up the decorator is less widely re-usable. You might think you'll never want to use a unit test decorator in a web test but what if you want to test a web script which writes a file? The decorator system was specifically designed to allow anything to be mixed in with anything else.

Fixtures

There is a second, specialised type of decorator: fixture objects. These hear the framework hooks: skip, setUp, teardown etc, and hence can be used to set up and tear down test fixtures.

There's no special requirement for writing a fixture class. Just implement one or more of the framework hooks — whatever you need.

By default, the fixture will also act as a test case decorator. Very often it makes sense to encapsulate fixture-creation code along with some related assertions.

Fixtures should be registered with the test case in a beforeInvoking() method, same as above.

<?php    

class FooFixture {

    function 
setUp() {
    }
    function 
tearDown() {
    }
    ...
    ...
}

class 
MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->addFixture('FooFixture');
    }
    ...
    ...

The addFixture() method is provided as an alias of mixin() to help tests read smoothly — it explains the intent better. Both behave in exactly the same way. AperiTestCase will figure out what kind of object it is and if it should be plugged in to the setUp/tearDown cycle.

Note that, if you add many fixture objects, they will hear setUp() calls in the order in which they were registered and tearDown() in reverse order.

In case you were wondering, the exact call sequence of all the framework hooks is as follows:

before any tests are run:

after all tests are run:

The main point to make is that fixture objects "wrap" the test methods ie fixture before/setUp are always called before both of the test case before/setUp methods. Fixture tearDown/after are always called after both of the test case tearDown/after methods.

The before & after methods aren't used often (and the above sequence is a lot simpler to grasp without them). However, they are very useful if you want to write an abstract test case or fixture. You can slip stuff into the setUp tearDown cycle without using the setUp and tearDown methods themselves and hence you don't need to worry about method chaining in the derived classes.

That's also the reason why I've added two hooks before the tests (beforeEverything & beforeInvoking) and two after (afterInvoking & afterEverything).

Note that if you add a beforeEverything() method to your test case, this must always be chained to its parent. AperiUnitTest uses this to add a couple of decorators to the base AperiTestCase class. None of the other framework hooks need to be chained in this way. Of course if you add a hierarchy of your own on top, you may need to chain methods in your own classes.

Non Decorating Fixtures

You can also add an object as a "non-decorating" fixture:

<?php    

class MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->addNonDecoratingFixture('FooFixture');
    }
    ...
    ...

This will hear setUp/tearDown/etc calls as normal but public methods will not be injected into the test case.

Fixtures usually do need to donate a few methods to the test case. You'll only rarely want to use this method, if at all.

Skipping

Fixture objects will often need to be plugged into the SimpleTest skip() mechanism. Just add a skip() method and call test case skipIf() and/or skipUnless() in the usual way. Of course the fixture will need a reference to the test case:

<?php    

class FooFixture {

    function 
__construct(AperiTestCase $test_case) {
        
$this->_test_case $test_case;
    }
    function 
skip() {
        
$this->_test_case->skipIf(
            
$some_boolean_condition
            
$message);
        
$this->_test_case->skipUnless(
            
$another_boolean_condition
            
$message);
    }
    ...
    ...

A ready-rolled class, RequiredService, does nothing else but skip. It's used to check for running services such as sshd, mysqld etc:

<?php    

class MyTestCase extends AperiUnitTest {

    function 
beforeInvoking() {
        
$this->mixin('RequiredService''sshd');
    }
    ...
    ...

Examples

Different Classes Same Tests

AperiTestCase has a simple way to run different classes past the same set of tests provided they all have a common ancestor (class or interface). Type-hint based dependency injection is used to wire everything up.

<?php    

class TwoHandedAxe implements Axe {...}
class 
OneHandedAxe implements Axe {...}
class 
FiremansAxe implements Axe {...}

class 
CanChopStuffUp extends AperiUnitTest {

    function 
shouldTest() {
        return array(
            
'TwoHandedAxe'
            
'OneHandedAxe'
            
'FiremansAxe');
    }
    function 
setUp() {
        
$this->axe $this->injector()->create('Axe');
    }
    function 
test() {
        ...
        ...
    }

The test case will be run several times, once with each class named in shouldTest().

Pass constructor args by sticking them in an array:

<?php    

class CanChopStuffUp extends AperiUnitTest {
    ...

    function 
setUp() {
        
$this->axe $this->injector()
            ->
create('Axe', array($a$b));
    }
    ...
    ...

AperiUnitTest

AperiUnitTest is a direct replacement for SimpleTest's UnitTestCase. Extend with your own class:

<?php    

class CanDoSomething extends AperiUnitTest {

    function 
test() {
        ...
        ...
    }

Tests must be run with a special runner script.

As well as all the usual AperiTestCase and UnitTestCase stuff you have:

The array comparison assertions can be useful if you want to assert a list of values but don't care what order they come in or what keys they're assigned to. A simple assertEqual() is too sensitive for that.

AperiWebTest

AperiWebTest is a direct replacement for SimpleTest's WebTestCase. It inherits all the AperiTestCase stuff plus all the standard WebTestCase behaviours.

I've tweaked assertPattern() to optionally allow the number of matches to be specified and added an assertion to verify the proclaimed charset.

As usual, tests must be run with a special runner script.

SourceForge.net Logo