繁体   English   中英

如何在PHP中为单元测试伪造资源?

[英]How to fake a resourse for a unit test in PHP?

我有一个方法,打开一个套接字连接,使用然后关闭它。 为了使其可测试,我将处理连接的方法移至单独的方法(请参见下面的代码)。

现在,我想为barIntrefaceMethod()编写单元测试,并需要模拟方法openConnection() 换句话说,我需要一个伪造的resource

是否有可能/如何在PHP中“手动”创建类型resource的变量(以伪造诸如“ 打开的文件,数据库连接,图像画布区域等之类的句柄)?


FooClass

class FooClass
{
    public function barIntrefaceMethod()
    {
        $connection = $this->openConnection();
        fwrite($connection, 'some data');
        $response = '';
        while (!feof($connection)) {
            $response .= fgets($connection, 128);
        }
        return $response;
        $this->closeConnection($connection);
    }

    protected function openConnection()
    {
        $errno = 0;
        $errstr = null;
        $connection = fsockopen($this->host, $this->port, $errno, $errstr);
        if (!$connection) {
            // TODO Use a specific exception!
            throw new Exception('Connection failed!' . ' ' . $errno . ' ' . $errstr);
        }
        return $connection;
    }

    protected function closeConnection(resource $handle)
    {
        return fclose($handle);
    }
}

根据我的评论,我认为您最好对代码进行一些重构以消除对使用实际资源句柄实际调用的本机函数的依赖。 对于FooClass::barInterfaceMethod的测试,您关心的只是它返回了一个响应。 该类不必关心资源是否打开,关闭,写入等。因此,这应该被嘲笑。

下面,我以一些简单且非生产性的伪代码重写了您要解决的问题:

您的真实课堂:

class FooClass
{
    public function barInterfaceMethod()
    {
        $resource = $this->getResource();
        $resource->open($this->host, $this->port);
        $resource->write('some data');

        $response = '';
        while (($line = $resource->getLine()) !== false) {
            $response .= $line;
        }

        $resource->close();

        return $response;
    }

    // This could be refactored to constructor injection
    // but for simplicity example we will leave at this
    public function getResource()
    {
        return new Resource;
    }
}

您对本课程的测试:

class FooClassTest
{
    public function testBarInterfaceMethod()
    {
        $resourceMock = $this->createMock(Resource::class);
        $resourceMock
            ->method('getLine')
            ->will($this->onConsecutiveCalls('some data', false)); // break the loop   

        // Refactoring getResource to constructor injection
        // would also get rid of the need to use a mock for FooClass here.
        // We could then do: $fooClass = new FooClass($resourceMock);
        $fooClass = $this->createMock(FooClass::class);
        $fooClass
            ->expects($this->any())
            ->method('getResource');
            ->willReturn($resourceMock);

        $this->assertNotEmpty($fooClass->barInterfaceMethod());
    }
}

对于实际的Resource类,您不需要然后对那些封装本机函数的方法进行单元测试。 例:

class Resource
{
    // We don't need to unit test this, all it does is call a PHP function
    public function write($data)
    {
        fwrite($this->handle, $data);
    }
}

最后,如果您想保持代码不变,另一种选择是设置实际连接到测试的测试夹具资源。 就像是

fsockopen($some_test_host, $port);
fopen($some_test_data_file);
// etc.

$some_test_host包含一些您可以用来进行测试的数据。

资源模拟需要一些魔术。

大多数I / O功能都允许使用协议包装器 vfsStream使用此功能来模拟文件系统。 不幸的是,诸如fsockopen()类的网络功能不支持它

但是您可以覆盖该功能。 我不考虑RunkitAPD ,没有PHP扩展也可以做到。

您在当前名称空间中创建具有相同名称和自定义实现的函数。 另一个答案中描述了此技术。

还有去! 在大多数情况下, AOP允许覆盖任何PHP函数。 Codeception / AspectMock使用此功能功能嘲讽 Badoo SoftMocks也提供此功能。

在您的示例中,您可以通过任何方法完全覆盖fsockopen()并使用vfsStream调用fgets()fclose()和其他I / O函数。 如果您不想测试openConnection()方法,则可以对其进行模拟以设置vfsStream并返回简单的文件资源。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM