diff --git a/modules/10-basics/10-hello-world/SolutionTest.php b/modules/10-basics/10-hello-world/SolutionTest.php index a2377bf9..7262da00 100644 --- a/modules/10-basics/10-hello-world/SolutionTest.php +++ b/modules/10-basics/10-hello-world/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'Hello, World!'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/10-basics/15-tags/SolutionTest.php b/modules/10-basics/15-tags/SolutionTest.php index 8d77257f..0eeaee75 100644 --- a/modules/10-basics/15-tags/SolutionTest.php +++ b/modules/10-basics/15-tags/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'King in the North!'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/10-basics/20-comments/SolutionTest.php b/modules/10-basics/20-comments/SolutionTest.php index 94a09e37..6523afbf 100644 --- a/modules/10-basics/20-comments/SolutionTest.php +++ b/modules/10-basics/20-comments/SolutionTest.php @@ -8,7 +8,7 @@ class SolutionTest extends TestCase { public function test() { - $this->expectOutputString(''); + $this->assertOutput(''); $content = file_get_contents('Solution.php'); $this->assertStringContainsString('// TODO: добавить функцию приветствия', $content); } diff --git a/modules/10-basics/30-instructions/SolutionTest.php b/modules/10-basics/30-instructions/SolutionTest.php index aede0059..5851ea72 100644 --- a/modules/10-basics/30-instructions/SolutionTest.php +++ b/modules/10-basics/30-instructions/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'RobertStannisRenly'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/10-basics/40-testing/SolutionTest.php b/modules/10-basics/40-testing/SolutionTest.php index bbf48ac1..567f16c7 100644 --- a/modules/10-basics/40-testing/SolutionTest.php +++ b/modules/10-basics/40-testing/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '9780262531962'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/10-basics/50-syntax-errors/SolutionTest.php b/modules/10-basics/50-syntax-errors/SolutionTest.php index 915b3b9f..b1b37d51 100644 --- a/modules/10-basics/50-syntax-errors/SolutionTest.php +++ b/modules/10-basics/50-syntax-errors/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'Программа успешно запущена'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/20-basic/SolutionTest.php b/modules/20-arithmetics/20-basic/SolutionTest.php index a246685b..6abf3fc3 100644 --- a/modules/20-arithmetics/20-basic/SolutionTest.php +++ b/modules/20-arithmetics/20-basic/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '9'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/25-operator/SolutionTest.php b/modules/20-arithmetics/25-operator/SolutionTest.php index a4019612..0229de44 100644 --- a/modules/20-arithmetics/25-operator/SolutionTest.php +++ b/modules/20-arithmetics/25-operator/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '8729'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/27-commutativity/SolutionTest.php b/modules/20-arithmetics/27-commutativity/SolutionTest.php index d219d54f..2cfc86be 100644 --- a/modules/20-arithmetics/27-commutativity/SolutionTest.php +++ b/modules/20-arithmetics/27-commutativity/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '2563'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/30-composition/SolutionTest.php b/modules/20-arithmetics/30-composition/SolutionTest.php index e1df4641..4b1141f1 100644 --- a/modules/20-arithmetics/30-composition/SolutionTest.php +++ b/modules/20-arithmetics/30-composition/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '660'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/40-priority/SolutionTest.php b/modules/20-arithmetics/40-priority/SolutionTest.php index aed274b5..32cc2cfb 100644 --- a/modules/20-arithmetics/40-priority/SolutionTest.php +++ b/modules/20-arithmetics/40-priority/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '160'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/43-float/SolutionTest.php b/modules/20-arithmetics/43-float/SolutionTest.php index 007d6b3a..0ec88aa0 100644 --- a/modules/20-arithmetics/43-float/SolutionTest.php +++ b/modules/20-arithmetics/43-float/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '5.5511151231258E-17'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/20-arithmetics/45-linting/SolutionTest.php b/modules/20-arithmetics/45-linting/SolutionTest.php index b8bfb7bd..6abd7179 100644 --- a/modules/20-arithmetics/45-linting/SolutionTest.php +++ b/modules/20-arithmetics/45-linting/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '4'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/25-strings/10-quotes/SolutionTest.php b/modules/25-strings/10-quotes/SolutionTest.php index 5aaa69b7..0e650a76 100644 --- a/modules/25-strings/10-quotes/SolutionTest.php +++ b/modules/25-strings/10-quotes/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'The file "user\'s_config.json" was not found.'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/25-strings/15-escape-characters/SolutionTest.php b/modules/25-strings/15-escape-characters/SolutionTest.php index c7993076..c7347127 100644 --- a/modules/25-strings/15-escape-characters/SolutionTest.php +++ b/modules/25-strings/15-escape-characters/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "Для разделения строк используйте \"\\n\"\nПример: print_r(\"строка1\\nстрока2\")"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/25-strings/20-string-concatenation/SolutionTest.php b/modules/25-strings/20-string-concatenation/SolutionTest.php index a4ac3688..37b2c84e 100644 --- a/modules/25-strings/20-string-concatenation/SolutionTest.php +++ b/modules/25-strings/20-string-concatenation/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'https://github.com/hexlet/exercises-php'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/25-strings/30-encoding/SolutionTest.php b/modules/25-strings/30-encoding/SolutionTest.php index a19cfb88..a24ebba2 100644 --- a/modules/25-strings/30-encoding/SolutionTest.php +++ b/modules/25-strings/30-encoding/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "~\n^\n%\n"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/10-definition/SolutionTest.php b/modules/30-variables/10-definition/SolutionTest.php index 5ae87dbd..26de7446 100644 --- a/modules/30-variables/10-definition/SolutionTest.php +++ b/modules/30-variables/10-definition/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "https://hexlet.io\nhttps://hexlet.io"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/12-change/SolutionTest.php b/modules/30-variables/12-change/SolutionTest.php index 1cb89bd0..8abcbd24 100644 --- a/modules/30-variables/12-change/SolutionTest.php +++ b/modules/30-variables/12-change/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'доставлен'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/13-variables-naming/SolutionTest.php b/modules/30-variables/13-variables-naming/SolutionTest.php index 0621225e..9d32ddb7 100644 --- a/modules/30-variables/13-variables-naming/SolutionTest.php +++ b/modules/30-variables/13-variables-naming/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "2"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/15-variables-expressions/SolutionTest.php b/modules/30-variables/15-variables-expressions/SolutionTest.php index 61bcaae1..c7422166 100644 --- a/modules/30-variables/15-variables-expressions/SolutionTest.php +++ b/modules/30-variables/15-variables-expressions/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "125\n863.75"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/18-variable-concatenation/SolutionTest.php b/modules/30-variables/18-variable-concatenation/SolutionTest.php index fc4b6d51..0dcd48d7 100644 --- a/modules/30-variables/18-variable-concatenation/SolutionTest.php +++ b/modules/30-variables/18-variable-concatenation/SolutionTest.php @@ -14,6 +14,6 @@ public function test() Ожидаемая дата доставки — 3 рабочих дня. HERE; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/19-naming-style/SolutionTest.php b/modules/30-variables/19-naming-style/SolutionTest.php index f2def806..fed482e2 100644 --- a/modules/30-variables/19-naming-style/SolutionTest.php +++ b/modules/30-variables/19-naming-style/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "2000"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/30-variables/20-magic-numbers/SolutionTest.php b/modules/30-variables/20-magic-numbers/SolutionTest.php index 827829b1..99395244 100644 --- a/modules/30-variables/20-magic-numbers/SolutionTest.php +++ b/modules/30-variables/20-magic-numbers/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "Ящиков на складе:\n102"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/100-unicode/SolutionTest.php b/modules/31-advanced-strings/100-unicode/SolutionTest.php index f0ba2ef6..1424b627 100644 --- a/modules/31-advanced-strings/100-unicode/SolutionTest.php +++ b/modules/31-advanced-strings/100-unicode/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "13\n7\n"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/110-locales/SolutionTest.php b/modules/31-advanced-strings/110-locales/SolutionTest.php index 358fc408..2115dabf 100644 --- a/modules/31-advanced-strings/110-locales/SolutionTest.php +++ b/modules/31-advanced-strings/110-locales/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = setlocale(LC_CTYPE, 0); - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/120-startwith/SolutionTest.php b/modules/31-advanced-strings/120-startwith/SolutionTest.php index 1c62e406..49e2cae1 100644 --- a/modules/31-advanced-strings/120-startwith/SolutionTest.php +++ b/modules/31-advanced-strings/120-startwith/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '18'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/25-interpolation/SolutionTest.php b/modules/31-advanced-strings/25-interpolation/SolutionTest.php index 1be5d35f..28e9474a 100644 --- a/modules/31-advanced-strings/25-interpolation/SolutionTest.php +++ b/modules/31-advanced-strings/25-interpolation/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'Здравствуйте, Анна! Ваш заказ #1337 принят.'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/30-symbols/SolutionTest.php b/modules/31-advanced-strings/30-symbols/SolutionTest.php index f94cbb73..1f12cd25 100644 --- a/modules/31-advanced-strings/30-symbols/SolutionTest.php +++ b/modules/31-advanced-strings/30-symbols/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'grip'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/70-substrings/SolutionTest.php b/modules/31-advanced-strings/70-substrings/SolutionTest.php index 28f2201c..f570616e 100644 --- a/modules/31-advanced-strings/70-substrings/SolutionTest.php +++ b/modules/31-advanced-strings/70-substrings/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'hexlet.io'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/31-advanced-strings/90-multiline-strings/SolutionTest.php b/modules/31-advanced-strings/90-multiline-strings/SolutionTest.php index 094b72d9..1d90bd42 100644 --- a/modules/31-advanced-strings/90-multiline-strings/SolutionTest.php +++ b/modules/31-advanced-strings/90-multiline-strings/SolutionTest.php @@ -14,6 +14,6 @@ public function test() Ожидаемая дата доставки: 3-5 рабочих дней. Спасибо, что выбрали нас! EOT; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/33-data-types/40-primitive-data-types/SolutionTest.php b/modules/33-data-types/40-primitive-data-types/SolutionTest.php index 6d27cb11..119590e2 100644 --- a/modules/33-data-types/40-primitive-data-types/SolutionTest.php +++ b/modules/33-data-types/40-primitive-data-types/SolutionTest.php @@ -15,6 +15,6 @@ public function test() Rating: 4.7 HERE; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/33-data-types/50-data-types-weak-typing/SolutionTest.php b/modules/33-data-types/50-data-types-weak-typing/SolutionTest.php index c3a89d76..4c896844 100644 --- a/modules/33-data-types/50-data-types-weak-typing/SolutionTest.php +++ b/modules/33-data-types/50-data-types-weak-typing/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '13'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/33-data-types/52-data-types-immutability/SolutionTest.php b/modules/33-data-types/52-data-types-immutability/SolutionTest.php index 3239872b..4f61889d 100644 --- a/modules/33-data-types/52-data-types-immutability/SolutionTest.php +++ b/modules/33-data-types/52-data-types-immutability/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'print'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/33-data-types/55-data-types-casting/SolutionTest.php b/modules/33-data-types/55-data-types-casting/SolutionTest.php index 814092d6..d1d6ee82 100644 --- a/modules/33-data-types/55-data-types-casting/SolutionTest.php +++ b/modules/33-data-types/55-data-types-casting/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '36 °C'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/35-calling-functions/100-call/SolutionTest.php b/modules/35-calling-functions/100-call/SolutionTest.php index 1b276e41..6b423923 100644 --- a/modules/35-calling-functions/100-call/SolutionTest.php +++ b/modules/35-calling-functions/100-call/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "12"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/35-calling-functions/135-calling-functions-default-params/SolutionTest.php b/modules/35-calling-functions/135-calling-functions-default-params/SolutionTest.php index 3e48af8e..1de5253f 100644 --- a/modules/35-calling-functions/135-calling-functions-default-params/SolutionTest.php +++ b/modules/35-calling-functions/135-calling-functions-default-params/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "37.8\n2426.76\n607\n"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/35-calling-functions/150-calling-functions-expression/SolutionTest.php b/modules/35-calling-functions/150-calling-functions-expression/SolutionTest.php index a95251eb..84272e95 100644 --- a/modules/35-calling-functions/150-calling-functions-expression/SolutionTest.php +++ b/modules/35-calling-functions/150-calling-functions-expression/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "First: H\nLast: !"; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/35-calling-functions/180-variadic-parameters/SolutionTest.php b/modules/35-calling-functions/180-variadic-parameters/SolutionTest.php index 5cb4d1c2..fb534919 100644 --- a/modules/35-calling-functions/180-variadic-parameters/SolutionTest.php +++ b/modules/35-calling-functions/180-variadic-parameters/SolutionTest.php @@ -9,6 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = '-3'; - $this->expectOutputString($expected); + $this->assertOutput($expected); } } diff --git a/modules/35-calling-functions/270-deterministic/SolutionTest.php b/modules/35-calling-functions/270-deterministic/SolutionTest.php index 8e3b179d..b9463a4e 100644 --- a/modules/35-calling-functions/270-deterministic/SolutionTest.php +++ b/modules/35-calling-functions/270-deterministic/SolutionTest.php @@ -8,7 +8,7 @@ class SolutionTest extends TestCase { public function test() { - $randomRoll = $this->getActualOutputForAssertion(); + $randomRoll = $this->solutionOutput; $this->assertThat( $randomRoll, $this->logicalAnd( diff --git a/modules/40-define-functions/100-define-function/SolutionTest.php b/modules/40-define-functions/100-define-function/SolutionTest.php index 887bfae6..760273e7 100644 --- a/modules/40-define-functions/100-define-function/SolutionTest.php +++ b/modules/40-define-functions/100-define-function/SolutionTest.php @@ -9,7 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = 'Hello, World!'; - $this->expectOutputString($expected); - sayHello(); + $this->assertOutputOf(fn () => sayHello(), $expected); } } diff --git a/modules/50-loops/10-while/SolutionTest.php b/modules/50-loops/10-while/SolutionTest.php index e92c2077..870166af 100644 --- a/modules/50-loops/10-while/SolutionTest.php +++ b/modules/50-loops/10-while/SolutionTest.php @@ -9,7 +9,6 @@ class SolutionTest extends TestCase public function test() { $expected = "4\n3\n2\n1\nGo!"; - $this->expectOutputString($expected); - printCountdown(4); + $this->assertOutputOf(fn () => printCountdown(4), $expected); } } diff --git a/src/TestCase.php b/src/TestCase.php index 6b7a8411..81f1e46e 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -5,13 +5,78 @@ use PHPUnit\Framework\TestCase as BaseTestCase; use ReflectionObject; +/** + * Базовый класс тестов упражнений. + * + * Подключает решение студента (Solution.php) и следит за тем, чтобы напечатанный + * им результат попадал на панель «вывод». Панель показывает дословный stdout + * команды `make test`, поэтому вывод решения нужно не только сверить с ожидаемым, + * но и явно вернуть в реальный stdout — в обход буферизации PHPUnit. + */ abstract class TestCase extends BaseTestCase { + /** + * Вывод, напечатанный решением на верхнем уровне при подключении Solution.php. + */ + protected string $solutionOutput = ''; + + /** + * Подключает решение студента и перехватывает его вывод. + * + * require Solution.php оборачивается в собственный буфер вывода: напечатанное + * сохраняется в {@see self::$solutionOutput} и сразу же печатается в настоящий + * stdout через fwrite(STDOUT, ...), чтобы студент увидел результат на панели + * «вывод». Перехват идёт в отдельный буфер (а не в буфер PHPUnit) — иначе + * PHPUnit счёл бы вывод «рискованным» при failOnRisky. + */ protected function setUp(): void { parent::setUp(); $solutionPath = dirname((new ReflectionObject($this))->getFileName()) . '/Solution.php'; + + ob_start(); require $solutionPath; + $this->solutionOutput = (string) ob_get_clean(); + + if ($this->solutionOutput !== '') { + fwrite(STDOUT, $this->solutionOutput . "\n"); + } + } + + /** + * Сверяет с ожидаемым вывод, напечатанный решением на верхнем уровне Solution.php. + * + * Подходит для большинства уроков, где решение печатает результат сразу при + * подключении файла (вывод уже перехвачен в {@see self::setUp()}). + * + * @param string $expected Ожидаемый вывод программы студента + */ + protected function assertOutput(string $expected): void + { + $this->assertSame($expected, $this->solutionOutput); + } + + /** + * Сверяет с ожидаемым вывод, который решение печатает при вызове функции. + * + * Нужен для уроков, где Solution.php лишь ОБЪЯВЛЯЕТ функцию, а печать + * происходит при её вызове из теста (а не при подключении файла). Перехватывает + * вывод переданного вызова, печатает его на панель «вывод» и сверяет с ожидаемым. + * + * @param callable $callback Вызов решения, печатающий результат (например, fn () => sayHello()) + * @param string $expected Ожидаемый вывод + */ + protected function assertOutputOf(callable $callback, string $expected): void + { + ob_start(); + $callback(); + $output = (string) ob_get_clean(); + + if ($output !== '') { + fwrite(STDOUT, $output . "\n"); + } + + $this->assertSame($expected, $output); } }