7

我在对具有在构造函数中调用的方法的类进行单元测试时遇到问题。我不明白如何嘲笑这个。也许我应该使用 phpUnit 的 'setUp' 方法?

我正在使用 Mockery 库。还有比这更好的工具吗?

class ToTest
{

   function __construct() {

       $this->methodToMock(); // need to mock that for future tests 

   }

   // my methods class

}

任何建议,将不胜感激。

4

6 回答 6

7

如果您的类难以实例化以进行测试,那是您的类做太多或在构造函数中工作的代码味道。

http://misko.hevery.com/code-reviewers-guide/

缺陷 #1:构造函数做实际工作

警示标志

  • 构造函数或字段声明中的新关键字
  • 在构造函数或字段声明中调用静态方法
  • 除了构造函数中的字段赋值之外的任何东西
  • 构造函数完成后对象未完全初始化(注意初始化方法)
  • 构造函数中的控制流(条件或循环逻辑)
  • 代码在构造函数中执行复杂的对象图构造,而不是使用工厂或构建器
  • 添加或使用初始化块

无论您的methodToMock函数在构造函数中做什么,都需要重新考虑。正如其他答案中提到的,您可能希望使用依赖注入来传递您的班级正在做的事情。

重新考虑您的课程实际上在做什么并重构,以便更容易测试。这还具有使您的类在以后更易于重用和修改的好处。

于 2014-04-15T22:03:57.767 回答
2

这里的问题是该方法不能被模拟,因为对象还没有被实例化。sectus 答案是有效的,但可能不是很灵活,因为在不同的测试中改变模拟方法的行为可能很困难。

您可以创建另一个与您要模拟的方法相同的类,并将该类的实例作为构造函数参数传递。这样你就可以在考试中通过模拟课程。通常你遇到的问题是班级做太多事情的味道。

于 2014-04-15T09:14:43.010 回答
2

要测试这个类,您将模拟内部对象(methodToMock),然后使用依赖注入来传递模拟的服务而不是真实的服务。

班级:

class ToTest{
    private $svc;

    // Constructor Injection, pass the Service object here
    public function __construct($Service = NULL)
    {
        if(! is_null($Service) )
        {
            if($Service instanceof YourService)
            {
                $this->SetService($Service);
            }
        }
    }

    function SetService(YourService $Service)
    {
        $this->svc = $Service
    }

    function DoSomething($request) {
        $svc    = $this->svc;
        $result = $svc->getResult($request);        // Get Result from Real Service
        return $result;
    }

    function DoSomethingElse($Input) {
         // do stuff
         return $Input;
    }
}

测试:

class ServiceTest extends PHPUnit_Framework_TestCase
{
    // Simple test for DoSomethingElse to work Properly
    // Could also use dataProvider to send different returnValues, and then check with Asserts.
    public function testDoSomethingElse()
    {
        $TestClass = new YourService();
        $this->assertEquals(1, $TestClass->DoSomethingElse(1));
        $this->assertEquals(2, $TestClass->DoSomethingElse(2));
    }

    public function testDoSomething()
    {
        // Create a mock for the YourService class,
        // only mock the DoSomething() method. Calling DoSomethingElse() will not be processed
        $MockService = $this->getMock('YourService', array('DoSomething'));

        // Set up the expectation for the DoSomething() method 
        $MockService->expects($this->any())
                    ->method('getResult')
                    ->will($this->returnValue('One'));

        // Create Test Object - Pass our Mock as the service
        $TestClass = new ToTest($MockService);
        // Or
        // $TestClass = new ToTest();
        // $TestClass->SetService($MockService);

        // Test DoSomething
        $RequestString = 'Some String since we did not specify it to the Mock';  // Could be checked with the Mock functions
        $this->assertEquals('One', $TestClass->DoSomething($RequestString));
    }
}
于 2014-04-15T15:53:14.267 回答
1

我也想知道这就是我发现你的问题的方式。最后我决定做一些有点脏的事情......使用反射。

这是我要测试的方法:

/**
 * ArrayPool constructor.
 * @param array $tasks Things that might be tasks
 */
public function __construct(array $tasks)
{
    foreach ($tasks as $name => $parameters) {
        if ($parameters instanceof TaskInterface) {
            $this->addTask($parameters);
            continue;
        }
        if ($parameters instanceof DescriptionInterface) {
            $this->addTask(new Task($parameters));
            continue;
        }
        $this->addPotentialTask($name, $parameters);
    }
}

出于此测试的目的,我不想实际运行->addTaskor ->addPotentialTask,只知道它们会被调用。

这是测试:

/**
 * @test
 * @covers ::__construct
 * @uses \Foundry\Masonry\Core\Task::__construct
 */
public function testConstruct()
{
    $task = $this->getMockForAbstractClass(TaskInterface::class);
    $description = $this->getMockForAbstractClass(DescriptionInterface::class);
    $namedTask = 'someTask';
    $parameters = [];

    $arrayPool =
        $this
            ->getMockBuilder(ArrayPool::class)
            ->disableOriginalConstructor()
            ->setMethods(['addTask', 'addPotentialTask'])
            ->getMock();

    $arrayPool
        ->expects($this->at(0))
        ->method('addTask')
        ->with($task);
    $arrayPool
        ->expects($this->at(1))
        ->method('addTask')
        ->with($this->isInstanceOf(TaskInterface::class));
    $arrayPool
        ->expects($this->at(2))
        ->method('addPotentialTask')
        ->with($namedTask, $parameters);

    $construct = $this->getObjectMethod($arrayPool, '__construct');
    $construct([
        0=>$task,
        1=>$description,
        $namedTask => $parameters
    ]);
}

神奇的是,getObjectMethod它接受一个对象并返回一个可调用的闭包,该闭包将调用对象实例上的方法:

/**
 * Gets returns a proxy for any method of an object, regardless of scope
 * @param object $object Any object
 * @param string $methodName The name of the method you want to proxy
 * @return \Closure
 */
protected function getObjectMethod($object, $methodName)
{
    if (!is_object($object)) {
        throw new \InvalidArgumentException('Can not get method of non object');
    }
    $reflectionMethod = new \ReflectionMethod($object, $methodName);
    $reflectionMethod->setAccessible(true);
    return function () use ($object, $reflectionMethod) {
        return $reflectionMethod->invokeArgs($object, func_get_args());
    };
}

而且我知道循环和条件都可以正常运行,而无需进入我不想在此处输入的代码。

在此处输入图像描述

TL;博士:

  1. 禁用__construct
  2. 设置模拟
  3. __construct对象实例化后使用反射调用
  4. 尽量不要为此失眠
于 2016-02-11T14:24:54.207 回答
0

只需扩展此类并覆盖您的方法(如果它是公开的或受保护的)。

于 2014-04-15T09:04:53.907 回答
0
class ToTest
{
   function __construct(){
       $this->methodToMock(); // need to mock that for future tests 
   }
   // my methods class
    public function methodToMock(){}
}

class ToTestTest{
    /**
     * @test
     * it should do something
     */
    public function it_should_do_something(){
        $ToTest = \Mockery::mock('ToTest')
        ->shouldDeferMissing()
        ->shouldReceive("methodToMock")
        ->andReturn("someStub")
        ->getMock();

        $this->assertEquals($expectation, $ToTest->methodToMock());
    }
}
于 2017-07-26T15:56:48.197 回答