phpunit: избегайте печати очень долго - PullRequest
2 голосов
/ 29 апреля 2020

У меня есть тест PHPUnit, который проверяет, что отображаемый вывод HTML не содержит определенной строки, я использую:

public function testSomething() {
    $htmlOutput = ...;
    self::assertDoesNotMatchRegularExpression(
        '/...pattern to detect a certain error.../',
        $htmlOutput,
        'HTML response contained a certain error',
    );
}

Если тест не пройден, PHPUnit печатает очень длинный вывод:

There was 1 failure:

1) MyTest::testSomething
HTML response contained a certain error
Failed asserting that '<!DOCTYPE html>\r\n
<html lang="en">\r\n
<head>\r\n
...
... hundreds and hundreds of lines
....
</body>\r\n
</html>' does not match PCRE pattern "/...pattern to detect a certain error.../".

Это очень раздражает, потому что все части важной информации теперь прокручиваются в моем терминале за пределы досягаемости, что является именем провального теста и фактическим сообщением "HTML ответ содержит определенную ошибку" , Конечно, точная строка потенциально может быть важна, чтобы выяснить, что пошло не так, но в половине случаев сообщение достаточно хорошее.

Какой рекомендуемый подход здесь?

Ответы [ 2 ]

2 голосов
/ 29 апреля 2020

Боюсь, что это то, что есть, когда используется assertDoesNotMatchRegularExpression(). При этом я бы предложил не использовать регулярные выражения для проверки HTML или XML. Используйте вместо этого специализированные утверждения, использующие селекторы CSS или выражения XPath.

0 голосов
/ 05 мая 2020

Как отметил Себастьян Бергманн, в общем случае HTML и XML проверка не должна выполняться с регулярными выражениями. Я обнаружил, что парсер PHP XML с запросами xpath может быть полезен. Также фреймворки часто содержат полезные расширения для PHPUnit (например, symfony).

Тем не менее я нашел решение, которое прекрасно работает даже для контента, отличного от HTML, например, для вывода длинного простого текста. Он включает в себя написание пользовательского ограничения PHPUnit:

use PHPUnit\Framework\Constraint\Constraint;

/**
 * Class RegularExpressionForLongString is a variant of PHPUnit's RegularExpression that
 * does not print the entire string on failure, which makes it useful for testing very
 * long strings.  Instead it prints the snippet where the regex first matched.
 */
class RegularExpressionForLongString extends Constraint {
    /**
     * Maximum length to print
     */
    private const MAX_LENGTH = 127;

    /**
     * @var string
     */
    private $pattern;

    /**
     * @var array|null
     */
    private $lastMatch = null;

    /**
     * RegularExpressionForLongString constructor.
     *
     * @param string $pattern
     */
    public function __construct(string $pattern) {
        $this->pattern = $pattern;
    }

    /**
     * @inheritDoc
     */
    public function toString(): string {
        return sprintf(
            'matches PCRE pattern "%s"',
            $this->pattern
        );
    }

    /**
     * @inheritDoc
     */
    protected function matches($other): bool {
        return preg_match($this->pattern, $other, $this->lastMatch, PREG_OFFSET_CAPTURE) > 0;
    }

    /**
     * @inheritDoc
     */
    protected function failureDescription($other): string {
        if (!is_string($other)) {
            return parent::failureDescription($other);
        }

        $strlen = strlen($other);
        $from = $this->lastMatch[0][1];
        $to = $from + strlen($this->lastMatch[0][0]);
        $context = max(0, intdiv(self::MAX_LENGTH - ($to - $from), 2));
        $from -= $context;
        $to += $context;
        if ($from <= 0) {
            $from = 0;
            $prefix = '';
        } else {
            $prefix = "\u{2026}";
        }
        if ($to >= $strlen) {
            $to = $strlen;
            $suffix = '';
        } else {
            $suffix = "\u{2026}";
        }

        $substr = substr($other, $from, $to - $from);
        return $prefix . $this->exporter()->export($substr) . $suffix . ' ' . $this->toString();
    }
}

Затем в новом базовом классе для тестов:

use PHPUnit\Framework\Constraint\LogicalNot;

/**
 * Class MyTestCase
 */
class MyTestCase extends TestCase {
    /**
     * Asserts that a string does not match a given regular expression.  But don't be so verbose
     * about it.
     *
     * @param string $pattern
     * @param string $string
     * @param string $message
     */
    public static function assertDoesNotMatchRegularExpressionForLongString(string $pattern, string $string, string $message = ''): void {
        static::assertThat(
            $string,
            new LogicalNot(new RegularExpressionForLongString($pattern)),
            $message,
        );
    }
}

Вот пример того, как его использовать:

self::assertDoesNotMatchRegularExpressionForLongString('/\{[A-Z_]+\}/', $content, "Response contains placeholders that weren't substituted");

Вот пример выходных данных об ошибке:

There was 1 failure:

1) <namespace>\SomeClassTest::testFunc
Response contains placeholders that weren't substituted
Failed asserting that …'re will be context printed here\r\n
    {CLIENT_FIRST_NAME}\r\n
    Some other text here.\r\n
    '… does not match PCRE pattern "/\{[A-Z_]+\}/".
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...