Is a method which composes and executes a CLI command testable?

别来无恙 提交于 2020-01-05 04:39:06

问题


This is the first time I'm getting into unit tests, and I would like to know if there is a way I can test the following method from a simple class.

I basically need to check if the command contains certain parameters for each argument passed through the method.

The external tool controlled by the command is not returning anything meaningful as far as checking for commands in the returned console output goes.

I've read that the body of a method is implementation detail and that it's therefore not testable. If that's true I assume I cannot test a method like this?

Method Example

public function doSomething(array $params)
{
    $command = ($this->x64 ? 'test_x64' : 'test_enc') . ' '
             . $params['a'] . ' '
             . $params['b'] . ' '
             . $params['c'];

    if (isset($params['d'])) {
        $command .= ' -d=' . $params['d'];
    }

    if (isset($params['e'])) {
        $command .= ' -e=' . $params['e'];
    }

    if (isset($params['f'])) {
        $command .= ' -' . $params['f'] . 'bit';
    }

    return shell_exec($command);
}

回答1:


You can mock global php functions if you are using namespaces.

Or you could create class ShellExecutor with the execute($command) method. Then you have to inject the instance into your function somehow. So, either directly in function call:

public function doSomething(array $params, ShellExecutor $executor = null) {
    if (!$executor) $executor = new ShellExecutor();
    ...
    return $executor->execute($command);
}

Or in the constructor of your class:

public function __construct(ShellExecutor $executor = null) {
    if (!$executor) $executor = new ShellExecutor();
    $this->executor = ShellExecutor;
}

public function doSomething(array $params) {
    ...
    return $this->executor->execute($command);
}

Then in your test, you can mock the ShellExecutor and test it's method call, something like this (I'm using Mockery in this example):

use Mockery as m;

class YourTestedClassTest {

   ...
   public function testParsesArguments() {
       $mockExecutor = m::mock('ShellExecutor');
       $mockExecutor->shouldReceive('execute')
           ->with(/*command arguments validation*/)
           ->andReturn('fakeReturnValue');

       $yourTestedInstance->doSomething(['a' => 'foo', 'b' => 'bar'], $mockExecutor);

   }

For argument validation options, see mockery documentation.




回答2:


I'd recommend wrapping the shell_exec call by a method on a different helper class. This way you can mock or fake the helper class. Mocking would imply injecting the dependency, so I'll exemplify the faking approach as it's simpler (although I'm a fan of DI and mocking):

// the class used in code
class ExecUtil {
    public static function shellExec($command) {
        return shell_exec($command);
    }
}

// the class used in the unit tests
class ExecUtil {
    public static $lastShellExecCommand = null;
    public static function shellExec($command) {
        static::$lastShellExecCommand = $command;
    }
}

//your test
public function testDoSomethingLaunchesTheProperProgramWithTheProperArguments() {
    //call doSomething()
    $expectedCommand = '<the command you expect here>';
    $this->assertEquals(ExecUtil::$lastShellExecCommand, $expectedCommand);
}

Just make sure you load the classes via an autoloader to be able to decide at runtime which class to load.

As a side done I'd also recommend you refactor the code in your method as you currently have a little code redundancy when building the arguments list:

$arguments = array($this->x64 ? 'test_x64' : 'test_enc'));

array_push($arguments, $params['a'], $params['b'], $params['c'];

if (isset($params['d'])) {
    array_push($arguments, '-d=' . $params['d']);
}

if (isset($params['e'])) {
    array_push($arguments,'-e=' . $params['e']);
}

if (isset($params['f'])) {
    array_push($arguments, '-' . $params['f'] . 'bit');
}

$command = implode(' ', $arguments);


来源:https://stackoverflow.com/questions/29886932/is-a-method-which-composes-and-executes-a-cli-command-testable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!