Как отметил Себастьян Бергманн, в общем случае 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_]+\}/".