【问题标题】:How to write tests for an Interface in PHP如何在 PHP 中为接口编写测试
【发布时间】:2015-03-20 10:51:50
【问题描述】:

如果我正在编写一个接口,我通常会指定实现应该公开的行为。为了确保这种行为,应该针对这个接口编写测试。我如何最好地编写和组织这些测试,以便实现的编写者可以轻松地使用它们来确保他们的实现满足要求? 是某种扩展(编写子类)接口测试的方法还是我实现了一些设计模式,比如工厂?

【问题讨论】:

  • 由于 PHP 接口不能包含实际代码,您要测试什么?实际上,您所能做的就是确保定义了特定的方法,并以某种方式让它们接受某些参数,也许使用反射
  • 我想测试实现的行为,但我不想为每个实现一次又一次地编写相同的测试,因为对于外部世界,它们看起来都应该是一样的,因为它们是实现相同的接口。例如。可以编写接口XYZ 并根据测试,所以每个想要声明“我正在实现接口 XYZ”的人只需将测试应用于这里的实现。
  • 如果您定义了接口,并且定义了将接口作为参数的方法,那么您已经在那里了。遗憾的是,php 接口没有指定完整的方法签名(例如缺少返回类型),因此您永远无法确定如何使用代码:a_function(my_interface $obj) { $obj->method_defined_in_interface(); /*what's the return type here, can I used it with str_* functions as I wanted or did it return void/int/obj/....? */}。另一方面:如果您在相同的代码库中有实现:改为测试它们,接口已经类型提示。
  • 这个想法是不要求所有实现都在同一个代码库中。尤其是正确的返回类型可以通过这些测试用例进行测试。
  • FWIW,我明白你想要做什么,我认为这是一个好问题。接口只是定义了一个方法应该的样子,它不能确认它实际上工作超出了表面的方法签名之外的你想要的方式。将实现测试与接口捆绑在一起似乎非常明智,因此实现者可以确认他们的工作;特别是如果该方法应该计算一些复杂的东西。也许您可以使用配置文件,例如“在此处添加您的课程,我们将为它运行测试”。

标签: php unit-testing testing interface


【解决方案1】:

我明白为什么人们告诉你不需要测试接口。 但这不是你要问的。 您要求一种更简单的方法来为实现某个接口的多个类执行相同的单元测试。 我使用@dataProvider 注释(PHPUnit,是的)来做到这一点。

假设你有这些类:

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;
    }
}

现在您要测试 Triangle 的实例 getNumberOfSides 的定义并返回 3,而 Square 的实例返回 4。 这就是您使用@dataProvider 编写此测试的方式:

class ShapeTest extends PHPUnit_Framework_TestCase {

    /**
     * @dataProvider numberOfSidesDataProvider
     */
    public function testNumberOfSides(Shape $shape, $expectedSides){
        $this->assertEquals($shape->getNumberOfSides(), $expectedSides);
    }
    
    public function numberOfSidesDataProvider() {
        return array(
            array(new Square(), 5),
            array(new Triangle(), 3)
        );
    }
}

在这里运行 phpunit 会产生预期的输出:

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: 2, Assertions: 2, Failures: 1.

【讨论】:

  • 数据提供者的名称无需以test 开头。失败是因为您断言正方形的边数 == 5,这是错误的。在您的 square 类中,您已将其定义为 4
  • @JTheDev 这是代码的意图,有一个失败测试的例子。修复了数据提供者的命名,感谢输入。
【解决方案2】:

您可以创建一个带有接口契约的一些基本测试的抽象类。

考虑以下示例:

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;
    }
}

并测试两种实现:

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();
    }
}

使用这种方法,每个程序员都应该能够为接口实现创建完整的单元测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 2023-01-31
    • 2011-03-25
    • 2018-02-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多