用于 fopen/fwrite 链的 PHPUnit 测试


PHPUnit Test for chain of fopen/fwrite

在一个项目中,我发现了这样的代码行:

protected function save($content, $path)
{
    // ...
    if (($handler = @fopen($path, 'w')) === false) {
        throw new Exception('...');
    }
    // ...
    if (@fwrite($handler, $content) === false) {
        throw new Exception('...');
    }
    // ...
    @fclose($handler);
}

我想用 PHPUnit 测试这种方法,但我有点坚持正确的测试用例。如果我将通过不正确的$path或具有不正确权限的正确$path(例如 0444 ),那么一切都将在第一个异常处停止。如果我传递具有正确权限的正确$path,PHP 也将能够写入文件,并且不会达到第二个异常。

那么有没有办法在不重写此方法的情况下测试第二个异常呢?

或者最好在一个条件下同时检查fopenfwrite,并且只对两者使用一个异常?

或者最好的选择是将此方法分成两个 - 一个用于打开,一个用于写入 - 并分别测试它们?

实现目标的最佳方法是使用模拟文件系统。我建议使用 vfsStream:

$ composer require mikey179/vfsStream

首先我必须提到,如果你使用无效的参数调用这个函数,fread 只会返回 false。如果发生任何其他错误,它将返回已写入的字节数。因此,您必须添加另一个检查:

class SomeClass {
    public function save($content, $path)
    {
        // ...
        if (($handler = @fopen($path, 'w')) === false) {
            throw new Exception('...');
        }
        $result = @fwrite($handler, $content);
        // ...
        if ($result === false) { // this will only happen when passing invalid arguments to fwrite
            throw new Exception('...');
        }
        // ...
        if ($result < strlen($content)) { // additional check if all bytes could have been written to disk
            throw new Exception('...');
        }
        // ...
        @fclose($handler);
    }
}

该方法的测试用例可能如下所示:

class SomeClassTest extends 'PHPUnit_Framework_TestCase {
    /**
     * @var vfsStreamDirectory
     */
    private $fs_mock;
    /**
     * @var vfsStreamFile
     */
    private $file_mock;
    /**
     * @var $sut System under test
     */
    private $sut;
    public function setUp() {
        $this->fs_mock = vfsStream::setup();
        $this->file_mock = new vfsStreamFile('filename.ext');
        $this->fs_mock->addChild($this->file_mock);
        $this->sut = new SomeClass();
    }
    public function testSaveThrowsExceptionOnMissingWritePermissionOnFile() {
        $this->expectException('Exception::class);
        $this->file_mock->chmod(0);
        $this->sut->save(
            'content',
            $this->file_mock->url()
        );
    }
    public function testSaveThrowsExceptionOnMissingWritePermissionOnDirectory() {
        $this->expectException('Exception::class);
        $this->fs_mock->chmod(0);
        $this->sut->save(
            'content',
            $this->fs_mock->url().'/new_file.ext'
        );
    }
    public function testSaveThrowsExceptionOnInvalidContentType() {
        $this->expectException('Exception::class);
        $this->fs_mock->chmod(0);
        $this->sut->save(
            $this,
            $this->file_mock->url()
        );
    }
    public function testSaveThrowsExceptionOnDiskFull() {
        $this->expectException('Exception::class);
        $this->fs_mock->chmod(0777); // to be sure
        $this->file_mock->chmod(0777); // to be sure
        vfsStream::setQuota(1); // set disk quota to 1 byte
        $this->sut->save(
            'content',
            $this->file_mock->url()
        );
    }
}

我希望我能帮上忙...