如何在PHP中编写接口测试

If I'm writing an interface I usually specify the behavior the implementations should expose. For ensuring this behavior one should write tests against this interface. How do I best write and organize those tests in a way, that they can easily be used by writers of implementations to ensure their implementations meet the requirements? Is some way of extending (writing subclasses) the interfaces tests the way to go or do I implements some design-pattern like factory?

You could create an abstract class with some basic tests of interface contracts.

Consider following example:

interface FactorialComputer {
    public function compute($input);
}

class RecursiveFactorialComputer implements FactorialComputer  {
    public function compute($input) {
        if ($input < 0) {
            throw new InvalidArgumentException(...);
        }

        if ($input == 0 || $input == 1) {
            return 1;
        }

        return $input * $this->compute($input - 1);
    }
}

class IterativeFactorialComputer implements FactorialComputer  {
    public function compute($input) {
        $result = 1;

        for ($i = 1; $i <= $input; $i++) {
            $result *= $i;
        }

        return $result;
    }
}

And tests for both implementations:

abstract class AbstractFactorialComputerTest extends PHPUnit_Framework_TestCase {
    /**
     * @var FactorialComputer 
     */
    protected $instance;

    protected abstract function getComputerInstance();

    public function setUp() {
        $this->instance = $this->getComputerInstance;
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testExceptionOnInvalidArgument() {
        $this->instance->compute(-1);
    }

    public function testEdgeCases()
    {
        $this->assertEquals(1, $this->instance->compute(0));
        $this->assertEquals(1, $this->instance->compute(1));
    }

    ...
}

class RecursiveFactorialComputerTest extends AbstractFactorialComputerTest
{
    protected abstract function getComputerInstance() {
        return new RecursiveFactorialComputer();
    }

    public function testComputeMethodCallsCount() {
        // get mock and test number of compute() calls
    }
}

class IterativeFactorialComputerTest extends AbstractFactorialComputerTest
{
    protected abstract function getComputerInstance() {
        return new IterativeFactorialComputer();
    }
}

Using this approach every programmer should be capable of creating a complete unit test for interface implementation.

I get why people are telling you you should not need to test interfaces. But that's not what you are asking. You are asking for a simpler way of performing the same unit tests for multiple classes that implement a certain interface. I do that using the @dataProvider annotation (PHPUnit, yes).

Let's assume you have these classes:

interface Shape {
    public function getNumberOfSides();
}

class Triangle implements Shape {
    private $sides = 3;

    public function getNumberOfSides() {
        return $this->sides;
    }
}

class Square implements Shape {
    private $sides = 4;

    public function getNumberOfSides() {
        return $this->sides;
    }
}

And you now want to test that for an instance of Triangle getNumberOfSides is defined and returns 3, while for a Square it returns 4. This is how you write this test using @dataProvider:

class ShapeTest extends PHPUnit_Framework_TestCase {

    /**
     * @dataProvider testNumberOfSidesDataProvider
     */
    public function testNumberOfSides(Shape $shape, $expectedSides){
        $this->assertEquals($shape->getNumberOfSides(), $expectedSides);
    }

    public function testNumberOfSidesDataProvider() {
        return array(
            array(new Square(), 5),
            array(new Triangle(), 3)
        );
    }
}

Running phpunit here produces the expected output:

There was 1 failure:

1) ShapeTest::testNumberOfSides with data set #0 (Square Object (...), 5)
Failed asserting that 5 matches expected 4.

/tests/ShapeTest.php:12

FAILURES!
Tests: 3, Assertions: 2, Failures: 1.

Update Failure is because, the number of sides, of a square, is 4 (as per the square class) and test suit is trying to assert the result equal to 5, which is wrong.