使用私有方法正确测试一个简单的类

时间:2015-08-10 23:37:20

标签: php unit-testing symfony phpunit

说明 我有一个简单的类,它创建一个符号链接到上传文件的目录,这些文件只对注册成员可用。它使用当前用户会话ID来为用户生成随机目录。用户注销后,符号链接将被删除。我想对单元的功能进行单元测试。

问题: 我如何对这个课程进行适当的单元测试,因为大部分功能都是私密的,而且我没有看到任何理由让它们公开?

以下是PHP类的代码:

<?php

namespace Test\BackEnd\MemberBundle\Library;


use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\KernelInterface;

class DirectoryProtector
{
/** @var SessionInterface $_session */
private $_session;

/** @var ContainerInterface $_kernel */
private $_kernel;

/**
 * @param SessionInterface $session
 * @param KernelInterface $kernel
 */
public function __construct( SessionInterface $session, KernelInterface $kernel )
{
    $this->_session = $session;
    $this->_kernel = $kernel;
}

/**
 * @param bool|false $protect
 * Public method to symlink directories
 */
public function protectDirectory($protect = FALSE)
{

    if ($protect) {
        if ( ! $this->doesDirectoryExists())
            symlink($this->getAppDir() . '/uploads', $this->getViewableSessionDirectory());
    } else {
        if ($this->doesDirectoryExists())
            unlink($this->getViewableSessionDirectory());
    }

}

/**
 * @return bool
 * Check to see if viewable session directory exists or not
 */
private function doesDirectoryExists()
{
    if (file_exists($this->getViewableSessionDirectory()))
        return TRUE;

    return FALSE;
}

/**
 * @return string
 * Get viewable session full directory path
 */
private function getViewableSessionDirectory()
{
    return $this->getAppDir() . '/../web/files/' . $this->getSessionId();
}

/**
 * @return string
 * Return app root directory
 */
private function getAppDir()
{
    return $this->_kernel->getRootDir();
}

/**
 * @return string
 * Return session id
 */
private function getSessionId()
{
    return $this->_session->getId();
}

}

以下是当前测试类的代码:

<?php

namespace Test\BackEnd\MemberBundle\Tests\Library;

use Test\BackEnd\MemberBundle\Library\DirectoryProtector;

class DirectoryProtectorTest extends \PHPUnit_Framework_TestCase
{

public function testProtectDirectory()
{
    //$this->markTestIncomplete("WIP on protect directory.");
    $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
        ->getMock();

    $container = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')
        ->getMock();

    /** @var DirectoryProtector $dp */
    $dp = $this->getMockBuilder('Test\BackEnd\MemberBundle\Library\DirectoryProtector')
            ->setConstructorArgs(array($request, $container))
            ->setMethods(array(
                'getViewableSessionDirectory',
                'getAppDir',
                'getSessionId'
            ))
            ->getMock();

    $dp->expects($this->once())
        ->method('doesDirectoryExists')
        ->will($this->returnValue(TRUE));

    $dp->protectDirectory(TRUE);

}

}

2 个答案:

答案 0 :(得分:4)

来自https://phpunit.de/manual/current/en/test-doubles.html

  

限制:最终,私有和静态方法

     

请注意,无法对最终,私有和静态方法进行存根或模拟。它们被PHPUnit的测试双重功能忽略,并保留其原始行为。

单元测试私有或受保护的方法不是一个好习惯。您应该测试公开 API 。私有方法应该通过API间接测试。也就是说,您可以使用反射公开方法:

$instance = new DirectoryProtector(...);

$ref = new \ReflectionClass('DirectoryProtector');

$method = $ref->getMethod('doesDirectoryExists');
$method->setAccessible(true);

$this->assertTrue($method->invoke($instance));

答案 1 :(得分:1)

我认为您的方法不正确。你不应该模拟方法,而是通过构造函数中的注入传递的对象。

对于SessionInterface,您可以通过MockArraySessionStorage。对于ContainerInterface,我不知道您正在使用哪个课程,因此我添加了一些演示示例。测试用例:

public function testProtectDirectory()
{
    $session = new MockArraySessionStorage();
    $session->setId('123123');
    $kernel = \Ouzo\Tests\Mock\Mock::create('ContainerInterface');
    \Ouzo\Tests\Mock\Mock::when($kernel)->getRootDir()->thenReturn('/root_dir');
    $directoryProtector = new DirectoryProtector($session, $kernel);

    $directoryProtector->protectDirectory(true);

    //asserts
}

您的方法将返回:

getViewableSessionDirectory - &gt; /root_dir/../web/files/123123

getAppDir - &gt; /root_dir

请记住,更改可访问的方法总是糟糕的主意。

PS。对于mocking I use utils form Ouzo framework。当然你可以使用其他模拟框架。