Создание более тестируемого менеджера сеансов - PullRequest
0 голосов
/ 26 февраля 2012

Я работаю над набором компонентов (которые, надеюсь, станут полноценными фреймворками) и сейчас работаю над одним из них, чтобы обеспечить абстракцию PHP-сессий.

Я пытаюсь сделать код как можно более тестируемым, но сессионный класс по определению будет полагаться на глобальное состояние в форме суперглобального $ _SESSION.

Я пытался реализовать мой класс сеанса таким образом, чтобы функции $ SESSION и сеанс * вызывались только в одном месте, которое затем я могу переопределить в PHPUnit для целей тестирования, но я могу Я не думаю, что есть лучший способ сделать это.

Если вы можете предложить лучший подход к созданию тестируемого сеанса, то я был бы признателен за любой вклад, который вы можете иметь.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

namespace gordian\reefknot\storage\session;

use gordian\reefknot\storage;

/**
 * Session management
 * 
 * Each instance of the Session object represents a "namespace" within the PHP
 * $_SESSION system.  This allows sessions to be easily managed and organized
 * within an application
 * 
 */
class Session implements storage\iface\Crud, iface\Session
{

    protected
        $name       = '',
        $storage    = NULL;


    /**
     * Add a new item to the session
     * 
     * @param mixed $data 
     * @param string $key
     * @return Session
     * @throws \InvalidArgumentException Thrown if no name is provided
     */
    public function createItem ($data, $key)
    {
        if (!empty ($key))
        {
            $key    = (string) $key;
            if (($this -> storage === NULL)
            || (!array_key_exists ($key, $this -> storage)))
            {
                $this -> storage [$key] = $data;
            }
        }
        else
        {
            throw new \Exception ('No valid key given');
        }
        return ($this);
    }

    /**
     * Delete the specified key
     * 
     * @param string $key 
     * @return Session
     */
    public function deleteItem ($key)
    {
        unset ($this -> storage [$key]);
        return ($this);
    }

    /**
     * Retrieve the data stored in the specified key
     * 
     * @param type $key 
     * @return mixed
     */
    public function readItem ($key)
    {
        return (array_key_exists ($key, $this -> storage)? 
            $this -> storage ['key']: 
            NULL);
    }

    /**
     * Update a previously stored data item to a new value
     * 
     * @param mixed $data 
     * @param string $key
     */
    public function updateItem ($data, $key)
    {
        if ($this -> storage === NULL)
        {
            throw new \RuntimeException ('Session contains no data');
        }

        if (array_key_exists ($key, $this -> storage))
        {
            $this -> storage [$key] = $data;
        }
        return ($this);
    }

    /**
     * Clear the session of all stored data
     * 
     * @return Session 
     */
    public function reset ()
    {
        $this -> storage = NULL;
        return ($this);
    }

    /**
     * Retrieve all data stored in the session
     * 
     * @return array 
     */
    public function getAll ()
    {
        return ($this -> storage);
    }

    /**
     * Return whether there is data stored in this session
     * 
     * @return bool 
     */
    public function hasData ()
    {
        return (!empty ($this -> storage));
    }

    /**
     * Initialize the back-end storage for the session
     * 
     * This method provides access for this class to the underlying PHP session
     * mechanism.  
     * 
     * @return bool Whether the newly initialized session contains data or not
     * @throws \RuntimeException Will be thrown if the session failed to start
     */
    protected function initStorage ()
    {
        // Check that storage hasn't already been initialized
        if ($this -> storage === NULL)
        {
            // Attempt to start the session if it hasn't already been started
            if ((session_id () === '')
            && ((headers_sent ()) 
            || ((!session_start ()))))
            {
                throw new \RuntimeException ('Unable to start session at this time');
            }
            // Alias our instance storage to the named $_SESSION variable
            $this -> storage    =& $_SESSION [$this -> name];
        }
        return ($this -> hasData ());
    }

    /**
     * Class constructor
     * 
     * @param string $sessName
     * @throws \InvalidArgumentException Thrown if no session name is provided
     */
    public function __construct ($sessName)
    {
        if (!empty ($sessName))
        {
            $this -> name   = $sessName;
            $this -> initStorage ();
        }
        else
        {
            throw new \InvalidArgumentException ('Session must have a name');
        }
    }
}

Для тестирования текущий план состоит в том, чтобы заменить initStorage () методом, который просто устанавливает внутренний массив. Если вы можете предложить лучший подход, я бы хотел услышать это.

Ответы [ 2 ]

1 голос
/ 26 февраля 2012

Поскольку $_SESSION является обычным массивом, нет необходимости обращаться к нему через ссылку. Различные методы чтения / записи должны работать с ним напрямую. Вы можете очистить массив в методе setUp() вашего теста. Использование ссылки чрезмерно усложняет класс без вознаграждения.

При модульном тестировании не стоит беспокоиться о тестировании встроенных функций PHP, таких как session_id() и headers_sent(). Создайте частичное макетирование класса или подкласса только для тестирования, который переопределяет этот метод.

1 голос
/ 26 февраля 2012

если я правильно понимаю ..

создайте абстракцию собственного управления сеансом, чтобы вашему помощнику хранилища сеансов не требовалось фактически выполнять какие-либо вызовы сеанса _ * или получать прямой доступ к $ _SESSION.

Имеют две его реализации, одна, которая на самом деле делает правильные вещи, другая, кроме ложных сессий _ * () и $ _SESSION, и в вашем конструкторе вы просто вызываете SESSIONCLASS :: start () и SESSIONCLASS :: getVar (name).тогда вы можете полностью протестировать «Сеанс».

...