【问题标题】:phpunit: Avoid printing very long outputphpunit:避免打印很长的输出
【发布时间】:2020-04-29 09:26:39
【问题描述】:

我有一个 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 响应包含某个错误”。当然,确切的字符串对于找出问题所在可能很重要,但在一半的情况下,消息已经足够好了。

这里推荐的方法是什么?

【问题讨论】:

    标签: php unit-testing phpunit


    【解决方案1】:

    恐怕使用assertDoesNotMatchRegularExpression()时就是这个样子。话虽如此,我建议不要使用正则表达式来验证 HTML 或 XML。改用使用 CSS 选择器或 XPath 表达式的专用断言。

    【讨论】:

    • 您对不使用 HTML 的正则表达式是绝对正确的,我将寻找更好的替代方案来解析 HTML/XML(并因此断言其有效结构)。然而,并非我的所有输出都是 HTML 或 XML,但我找到了一个替代方案,我将很快发布作为附加答案。
    【解决方案2】:

    正如 Sebastian Bergmann 所指出的,通常 HTML 和 XML 验证不应该使用正则表达式来完成。我发现 PHP 的带有 xpath 查询的 XML 解析器很有用。此外,框架通常包含有用的 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_]+\}/".
    

    【讨论】:

      猜你喜欢
      • 2013-12-20
      • 1970-01-01
      • 2020-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-12
      • 1970-01-01
      相关资源
      最近更新 更多