10

假设您有一种最终归结为的方法

class Pager
{
    private $i;

    public function next()
    {
        if ($this->i >= 3) {
            throw new OutOfBoundsException();
        }

        $this->i++;
    }
}

您将如何对此类进行单元测试。next()即测试是否在使用 PHPUnit的第三次调用时抛出异常?我已经添加了我的尝试作为答案,但我不确定这是否真的是要走的路。

4

5 回答 5

8

如何测试null前两个调用并测试抛出的异常,如下所示:

class PagerTest
{
    public function setUp()
    {
        $this->pager = new Pager();
    }

    public function testTooManyNextCalls()
    {
        $this->assertNull($this->pager->next());
        $this->assertNull($this->pager->next());
        $this->assertNull($this->pager->next());

        $this->setExpectedException('OutOfBoundsException');
        $this->pager->next();
    }
}
于 2012-10-13T17:21:39.567 回答
4

在进行单元测试时,避免测试实现细节非常重要。相反,您希望将自己限制为仅测试代码的公共接口。为什么?因为实现细节经常改变,但你的 API 应该很少改变。测试实现细节意味着随着这些实现的变化,您将不得不不断地重写您的测试,并且您不希望被困在这样做。

那么这对 OP 的代码意味着什么?让我们看一下公共Pager::next方法。Pager使用类 API的代码并不关心如何Pager::next确定是否应该抛出异常。它只关心如果Pager::next出现问题实际上会引发异常。

我们不想测试方法是如何决定抛出的OutOfBoundsException——这是一个实现细节。我们只想测试它是否在适当的时候这样做。

所以为了测试这个场景,我们模拟了一个Pager::next将抛出的情况。为了实现这一点,我们只需实现所谓的“测试接缝”。...

<?php
class Pager
{
    protected $i;

    public function next()
    {
        if ($this->isValid()) {
            $this->i++;
        } else {
            throw new OutOfBoundsException();
        }
    }

    protected function isValid() {
        return $this->i < 3;
    }
}

在上面的代码中,受保护的Pager::isValid方法是我们的​​测试接缝。它在我们的代码中暴露了一个接缝(因此得名),我们可以锁定它以进行测试。使用我们新的测试接缝和 PHPUnit 的模拟 API,Pager::next对 的无效值抛出异常的测试$i是微不足道的:

class PagerTest extends PHPUnit_Framework_TestCase
{
    /**
     * @covers Pager::next
     * @expectedException OutOfBoundsException
     */
    public function testNextThrowsExceptionOnInvalidIncrementValue() {
        $pagerMock = $this->getMock('Pager', array('isValid'));
        $pagerMock->expects($this->once())
                  ->method('isValid')
                  ->will($this->returnValue(false));
        $pagerMock->next();
    }
}

请注意,此测试特别不关心实现方法如何Pager::isValid确定当前增量无效。测试只是模拟方法在调用时返回false,以便我们可以测试我们的公共Pager::next方法是否在应该这样做时抛出异常。

PHPUnit 模拟 API 已在 PHPUnit 手册的 Test Doubles 部分中全面介绍。API 并不是世界历史上最直观的东西,但经过多次重复使用,它通常是有意义的。

于 2012-10-13T17:38:47.947 回答
1

这是我目前所拥有的,但我想知道是否有更好的方法来做到这一点。

class PagerTest
{
    public function setUp()
    {
        $this->pager = new Pager();
    }

    public function testTooManyNextCalls()
    {
        for ($i = 0; $i < 10; $i++) {
            try {
                $this->pager->next();
            } catch(OutOfBoundsException $e) {
                if ($i == 3) {
                    return;
                } else {
                    $this->fail('OutOfBoundsException was thrown unexpectedly, on iteration ' . $i);
                }
            }

            if ($i > 3) {
                $this->fail('OutOfBoundsException was not thrown when expected');
            }
        }
    }
}
于 2012-10-13T16:48:10.960 回答
1

你可以使用类似的东西:

class PagerTest extends PHPUnit_Framework_TestCase {

    /**
     * @expectedException OutOfBoundsException
     */
     public function testTooManyNextCalls() {
         $this->pager = new Pager();

         $this->pager->next();
         $this->pager->next();
         $this->pager->next();

         $this->assertTrue(false);
    }
}

如果在第 3 次方法调用中抛出异常,则永远不应该到达总是失败的断言语句并且测试应该通过。另一方面,如果没有抛出异常,则测试将失败。

于 2012-10-13T17:10:52.717 回答
0

您可以将值 $this->i 传递给异常实例化,这将是异常的消息。

class Pager
{
    private $i;

    public function next()
    {
        if ($this->i >= 3) {
            throw new OutOfBoundsException($this->i);
        }

        $this->i++;
    }
}
$a=new Pager();
$a->next();
$a->next();
$a->next();
$a->next();

//outputs: "Exception: 3"
于 2012-10-13T16:57:54.907 回答