Пути vfsstream и реальный путь

Я экспериментирую с vfsStream для модульного тестирования взаимодействий файловой системы и очень быстро столкнулся с серьезным препятствием. Одной из проверок проверки, которую выполняет тестируемый код, является выполнение realpath() по предоставленному входному пути, чтобы проверить, что это фактический путь, а не нонсенс. Однако realpath всегда терпит неудачу на пути vfsstream.

Следующий код демонстрирует проблему за пределами какого-либо конкретного класса.

$content    = "It rubs the lotion on its skin or else it gets the hose again";
$url        = vfsStream::url ('test/text.txt');
file_put_contents ($url, $content);
var_dump ($url);
var_dump (realpath ($url));
var_dump (file_get_contents ($url));

Вывод следующий:

string(27) "vfs://FileClassMap/text.txt"
bool(false)
string(61) "It rubs the lotion on its skin or else it gets the hose again"

Очевидно, vfsStream создал файл и записал в него заданный контент, но я не могу проверить, что путь к нему правильный с помощью realpath. Поскольку realpath используется внутри реального кода, мне нужен способ поработать над этим.

Я действительно не думаю, что удаление realpath - это разумный подход, потому что он выполняет важную функцию внутри кода, а устранение важной проверки только для того, чтобы сделать код тестируемым, кажется довольно плохим решением. Я мог бы также поставить if вокруг теста, чтобы можно было отключить его для целей тестирования, но опять же я не думаю, что это также хорошая идея. Также я не хотел бы делать это в каждой точке кода, где я мог бы сделать вызов realpath (). Третий вариант - настроить RAM-диск для модульных тестов файловой системы, но это тоже не идеально. Вы должны убирать за собой (именно это, как предполагается, vfsstream поможет вам избежать необходимости), и то, как на самом деле это сделать, будет отличаться от ОС к ОС, поэтому модульные тесты перестанут быть независимыми от ОС.

Так есть ли способ получить путь vfsstream в формате, который на самом деле работает с realpath?

Для полноты ниже приведен фрагмент кода из класса, который я пытаюсь на самом деле проверить.

if (($fullPath = realpath ($unvalidatedPath))
&& (is_file ($fullPath))
&& (is_writable ($fullPath))) {

Рефакторинг на следующее (согласно потенциальному решению 2) позволяет мне тестировать с vfsStream, но я думаю, что это может быть проблематично в производственной среде:

// If we can get a canonical path then do so (realpath can fail on URLs, stream wrappers, etc)
$fullPath   = realpath ($unvalidatedPath);
if (false === $fullPath) {
    $fullPath   = $unvalidatedPath;
}

if ((is_file ($fullPath))
&& (is_writable ($fullPath))) {

1 ответ

Если вы используете пространства имен, вы можете переопределить функцию realpath только в тестовом классе. Я всегда использую канонические пути в моих тестовых случаях vfsStream, потому что я не хочу тестировать саму функцию realpath().

namespace my\namespace;

/**
 * Override realpath() in current namespace for testing
 *
 * @param string $path     the file path
 *
 * @return string
 */
function realpath($path)
{
    return $path;
}

Хорошо описано здесь: http://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/

Я обнаружил ошибку с реализацией метода Себкрюгера на vfsStream: https://github.com/bovigo/vfsStream/issues/207

В ожидании их отзывов, вот мой рабочий realpath():

/**
 * This function overrides the native realpath($url) function, removing
 * all the "..", ".", "///" of an url. Contrary to the native one, 
 * 
 * @param string $url
 * @param string|bool The cleaned url or false if it doesn't exist
 */
function realpath(string $url)
{
    preg_match("|^(\w+://)?(/)?(.*)$|", $url, $matches);
    $protocol = $matches[1];
    $root     = $matches[2];
    $rest     = $matches[3];

    $split = preg_split("|/|", $rest);

    $cleaned = [];
    foreach ($split as $item) {
        if ($item === '.' || $item === '') {
            // If it's a ./ then it's nothing (just that dir) so don't add/delete anything
        } elseif ($item === '..') {
            // Remove the last item added since .. negates it.
            $removed = array_pop($cleaned);
        } else {
            $cleaned[] = $item;
        }
    }

    $cleaned = $protocol.$root.implode('/', $cleaned);
    return file_exists($cleaned) ? $cleaned : false;
}
Другие вопросы по тегам