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

标签 php unit-testing mocking phpunit fsockopen

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

现在我想为 barIntrefaceMethod() 编写一个单元测试,并且需要模拟方法 openConnection()。换句话说,我需要一个假的资源

是否可能/如何在 PHP 中“手动”创建 resource 类型的变量(以便伪造像“opened files, database connections, image canvas areas and the like”等句柄)?

<小时/>

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

最佳答案

根据我的评论,我认为您最好稍微重构一下代码,以消除对使用实际资源句柄实际调用的 native 函数的依赖。对于 FooClass::barInterfaceMethod 的测试,您所关心的只是它返回一个响应。该类不需要关心资源是否打开、关闭、写入等。因此这就是应该模拟的内容。

下面我用一些简单且非生产的伪代码重写了您问题中的内容以进行演示:

您的真实类(class):

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 类,您不需要对那些 native 函数包装器的方法进行单元测试。示例:

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 包含一些可以用于测试的数据。

关于php - 如何在 PHP 中为单元测试伪造资源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41300828/

相关文章:

php - PHP 会处理 $_GET 和 $_POST 中变量的字符编码吗? (PHP 5.4)

php - ext-imagick * -> 您的系统缺少请求的 PHP 扩展 imagick

php - Javascript (jQuery) - $.post 的问题

java - 使用 StepVerifier 验证是否至少已经过了一段时间?

java - JUnit、测试和线程

c# - 用 rhino mocks 对一个属性进行 stub 两次

php - 数字的 Laravel 规则验证

spring-boot - 我使用@Sql进行Spring Test时发生FileNotFoundException

javascript - 解释 "jest.advanceTimersToNextTimer "

c# - 在单元测试中模拟 IMemoryCache