问题
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