rick645 Posted May 29 Share Posted May 29 (edited) Many say that is not recommended to print (text, data, info...) in a function/method, because it makes it difficult to test (unless inelegant stratagems are used). Here's a demonstration $ cat FileTest.php <?php function copyFileExample($echoOutput) { $verboseText = "Copying file... please wait...\n"; if ($echoOutput) echo $verboseText; /* * since it is an example, instead of copy(), * the sleep(5) function is used, to simulate copying a file, * which let's pretend it takes 5 seconds */ sleep(5); // replaces copy() function if (!$echoOutput) return $verboseText; } class FileTest extends PHPUnit\Framework\TestCase { static function copyProvider() { return [ [ "echoOutput" => true // not recommended [HIGHLIGHT] ], [ "echoOutput" => false // recommended [HIGHLIGHT] ], ]; } /** * @dataProvider copyProvider */ function testCopy($echoOutput) { $this->assertEquals( "Copying file... please wait...\n", copyFileExample($echoOutput) ); } } I highlight some parts $ grep HIGHLIGHT FileTest.php "echoOutput" => true // not recommended [HIGHLIGHT] "echoOutput" => false // recommended [HIGHLIGHT] So let's launch the tests $ phpunit FileTest.php PHPUnit 10.5.20 by Sebastian Bergmann and contributors. Runtime: PHP 8.1.2-1ubuntu2.17 Copying file... please wait... F. 2 / 2 (100%) There was 1 failure: 1) FileTest::testCopy with data set #0 (true) Failed asserting that null matches expected 'Copying file... please wait...\n '. FileTest.php:37 FAILURES! Tests: 2, Assertions: 2, Failures: 1. As you can see, one of the two tests fails (the one in which true is given for the parameter), for the reason above. For the other test, however, nothing is reported, so it means that it's fine, but... but... but... all of this hides an irregularity. I don't know if I'll be able to explain it.... In practice, if you run copyFileExample(echoOutput: true); // BAD PRACTICE (difficult to test output), BUT GOOD RESULT "Copying file... please wait..." is displayed the process stops for 5 seconds (pretending to copy some file) the process ends normally Instead if you execute echo copyFileExample(echoOutput: false); // GOOD PRACTICE, BUT... the process stops (hangs) for 5 seconds: it seems to have frozen, no output... but then suddenly it says "Copying file... please wait..."; when in reality the waiting is already over (there is nothing left to wait for): the time sequence is not respected So is there a way to reconcile the two? Edited May 29 by rick645 Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/ Share on other sites More sharing options...
requinix Posted May 29 Share Posted May 29 Reconcile what? If you output the message before the pause then you see the message before the pause, and if you output the message after the pause then you see the message after the pause... Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1625885 Share on other sites More sharing options...
Solution Danishhafeez Posted June 1 Solution Share Posted June 1 Here’s how you can achieve that: Separate Logic from Output: Move the output logic outside the core function. Use Dependency Injection: Inject a callable (e.g., a function or a method) that handles the output. This allows you to control the output during testing. <?php function copyFileExample($outputCallback = null) { $verboseText = "Copying file... please wait...\n"; if ($outputCallback) { call_user_func($outputCallback, $verboseText); } // Simulate the file copy with sleep(5) sleep(5); // replaces copy() function return $verboseText; } class FileTest extends PHPUnit\Framework\TestCase { static function copyProvider() { return [ [ "outputCallback" => null // recommended [HIGHLIGHT] ], [ "outputCallback" => function($text) { echo $text; } // not recommended for testing [HIGHLIGHT] ], ]; } /** * @dataProvider copyProvider */ function testCopy($outputCallback) { $expectedOutput = "Copying file... please wait...\n"; ob_start(); $result = copyFileExample($outputCallback); $output = ob_get_clean(); if ($outputCallback) { $this->assertEquals($expectedOutput, $output); } else { $this->assertEquals($expectedOutput, $result); } } } ?> Function Definition: The copyFileExample function now accepts an optional $outputCallback parameter, which can be any callable (function, method, etc.). If $outputCallback is provided, it is called with the verbose text. This decouples the printing logic from the core function. Test Class: The copyProvider method returns different scenarios, including one without any callback and one with a callback that echoes the text. The testCopy method captures any output printed by the callback using output buffering (ob_start, ob_get_clean). The assertions check the output if a callback is provided and the return value otherwise. $ phpunit FileTest.php PHPUnit 10.5.20 by Sebastian Bergmann and contributors. Runtime: PHP 8.1.2-1ubuntu2.17 .. OK (2 tests, 2 assertions) With this approach, both tests should pass, ensuring that the function behaves correctly with and without output, and making it easier to test and maintain. Best Regard Danish hafeez | QA Assistant ICTInnovations Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1626459 Share on other sites More sharing options...
rick645 Posted June 7 Author Share Posted June 7 thanks Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1627209 Share on other sites More sharing options...
rick645 Posted June 7 Author Share Posted June 7 (edited) Nor is it convenient to always return all the output in the end, and then print it all in one fell swoop. Often, in fact, as already mentioned, it is a good thing to have intermediate outputs (avoiding embarrassing mute scenes), to understand what the software is doing running (also respecting the time sequence) fwrite($outputStream, “Downloading...\n”); // do something fwrite($outputStream, “Unpacking...\n”); // do something fwrite($outputStream, “Install...\n”); // do something What do you think? Edited June 7 by rick645 Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1627210 Share on other sites More sharing options...
rick645 Posted June 8 Author Share Posted June 8 12 hours ago, rick645 said: fwrite($outputStream, “Downloading...\n”); // do something fwrite($outputStream, “Unpacking...\n”); // do something fwrite($outputStream, “Install...\n”); // do something Of course, to keep the global namespace in order, it would be preferable to encapsulate everything in a function main(), front_controller(), etc., and call it. Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1627252 Share on other sites More sharing options...
gizmola Posted June 11 Share Posted June 11 A lot of people would probably say that many of these types of issues can be solved when you make use of the Dependency Injection (aka Inversion of Control) pattern. There are logging libraries like the well known "Monolog" component library: https://packagist.org/packages/monolog/monolog You would do something similar in regards to a copy operation, and not put sleep code in, which isn't a simulation of anything. You have a contrived example, and are using a unit test for something that, I guess appears to be a functional test. Unit tests are meant to insure that all code is executed, and the intention you had when writing a function or method can be proven to meet the design expectations. It is not something that people are expected to sit and watch. In fact, quite the opposite in situations where people are using the unit tests in Continuous Integration (CI) and possibly continuous delivery (CD). The very fact that you are trying to simulate the expectation that an end user would be sitting tailing a log or looking at a console tells you that you are in the realm of user experience and not unit testing. Quote Link to comment https://forums.phpfreaks.com/topic/321164-print-the-output-inside-a-functionmethod/#findComment-1627571 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.