简体   繁体   中英

How do you mock a virtual binary file so that exec() / system() / passthru() function output can be tested?

I have an interesting problem and have searched the internet, but haven't yet found an answer.

I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.

Consider the following function:

function get_setting_values_from_file( $parameter )
{
    exec("/usr/var/binary --options $parameter", $output, $return);
    $settings = file( $output[0] );
    foreach( $settings as $setting ) {
        if( strstr( $setting, "color") ) {
            $setting = explode( ":", $setting );
            return $setting[1];
        }
    }
    return false;
}

I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?

All feedback is appreciated.

You could mock exec() by using a function mock library. I made one ( php-mock ) for you which requires you to use namespaces

namespace foo;

use phpmock\phpunit\PHPMock;

class ExecTest extends \PHPUnit_Framework_TestCase
{

    use PHPMock;

    public function testExec()
    {
        $mock = $this->getFunctionMock(__NAMESPACE__, "exec");
        $mock->expects($this->once())->willReturnCallback(
            function ($command, &$output, &$return_var) {
                $this->assertEquals("foo", $command);
                $output = "failure";
                $return_var = 1;
            }
        );

        exec("foo", $output, $return_var);
        $this->assertEquals("failure", $output);
        $this->assertEquals(1, $return_var);
    }
}

Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.

For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.

$stub = $this->getMock('GetSettingsClass');
    $stub->expects($this->any())
         ->method('get_settings_from_file')
         ->will($this->returnValue(0));

This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs

Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions. Assumed Example in the main code:

...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;

function get_user_type($settings)
{
    if($settings !== FALSE)         // Returned from your function if parameter is not found
    {
        switch($settings)
        {
            case 1:
                return 'User';      // Best to use Constants, but for example here only
                break;
            case 2:
                return 'Admin';
                break;
            ...
         }
    }
    else
    {
        return FALSE;
    }
}

Now, in your test, you can simply

$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...

These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.

This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.

I hope this helps to explain what I was thinking.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM