PHPUnit: выполнение утверждений для непубличных переменных

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

Конечно, я могу проверить установщик, используя получатель, чтобы убедиться, что объект хранит правильное значение, и наоборот для проверки получателя. Однако это не гарантирует, что будет установлена ​​частная собственность.

Скажем, у меня был следующий класс. Я создал свойство, геттер и сеттер. Но я сделал опечатку в имени свойства, чтобы геттер и сеттер фактически не манипулировали свойством, которым они должны манипулировать

class SomeClass
{
    private 
        $mane = NULL; // Was supposed to be $name but got fat-fingered!

    public function getName ()
    {
        return ($this -> name);
    }

    public function setName ($newName)
    {
        $this -> name = $newName;
        return ($this);
    }
}

Если я запускаю следующий тест

public function testSetName ()
{
    $this -> object -> setName ('Gerald');
    $this -> assertTrue ($this -> object -> getName () == 'Gerald');
}

Я бы получил пропуск. Однако на самом деле произошло нечто очень плохое, чего я не хочу. Когда вызывается setName(), он фактически создает новое свойство в классе с именем, которое, как я думал, было у моего частного свойства, только то, которое создает сеттер, является открытым! Я могу продемонстрировать это с помощью следующего кода:

$a  = new SomeClass;

$a -> setName('gerald');
var_dump ($a -> getName ());
var_dump ($a -> name);

Это вывело бы:

Строка (6) "Джеральд"

Строка (6) "Джеральд"

Есть ли какой-нибудь способ получить доступ к закрытым свойствам из PHPUnit, чтобы я мог писать тесты, которые удостоверяются, что свойства, которые, на мой взгляд, получают и устанавливают, действительно получают и устанавливают?

Или есть что-то еще, что я должен делать в тесте, чтобы поймать подобные проблемы, не пытаясь получить доступ к закрытому состоянию тестируемого объекта?

4 ответа

Решение

Для тестирования свойств я бы привел те же аргументы, что и при обсуждении частных методов.

You usually don't want to do this,

Это о тестировании наблюдаемого поведения.

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

В общем, вы теряете ценность своего набора тестов!


Достаточно было бы просто протестировать комбинации get / set, но обычно не каждый сеттер должен иметь геттер, и просто создавать их для тестирования нехорошо.

Обычно вы устанавливаете некоторые вещи, а затем говорите методу DO (поведение) что-то. Тестирование для этого (то, что класс делает то, что должно сделать) является лучшим вариантом для тестирования и должно сделать тестирование свойств излишним.


Если вы действительно хотите сделать это, есть setAccessible функциональность в API отражений PHP, но я не могу составить пример, где я нахожу это желательным

Поиск неиспользуемых свойств для обнаружения ошибок / проблем, подобных этой:

Детектор беспорядка PHP как UnusedPrivateField Rule

class Something
{
    private static $FOO = 2; // Unused
    private $i = 5; // Unused
    private $j = 6;
    public function addOne()
    {
        return $this->j++;
    }
}

Это сгенерирует два предупреждения для вас, потому что переменные никогда не доступны

Вы также можете использовать Assert::assertAttributeEquals('value', 'propertyName', $object),

См. https://github.com/sebastianbergmann/phpunit/blob/3.7/PHPUnit/Framework/Assert.php

Я просто хочу указать на одну вещь. Давайте на минутку забудем о приватных полях и сосредоточимся на том, что заботится о клиенте вашего класса. Ваш класс выставляет контракт, в данном случае - возможность изменять и извлекать имя (через геттер и сеттер). Ожидаемая функциональность проста:

  • когда я устанавливаю имя с setName в "Gerald"Я ожидаю получить "Gerald" когда я звоню getName

Это все. Клиент не будет (ну, не должен!) Заботиться о внутренней реализации. Использовали ли вы частное имя поля, хэш-сет или вызванный веб-сервис через динамически генерируемый код - для клиента не имеет значения. Ошибка, с которой вы сталкиваетесь, с точки зрения пользователя, вовсе не является ошибкой.

Позволяет ли PHPUnit тестировать закрытые переменные - я не знаю. Но с точки зрения юнит-тестирования вы не должны этого делать.

Изменить (в ответ на комментарий):

Я понимаю ваши опасения по поводу возможного выявления внутреннего состояния, однако я не думаю, что модульное тестирование является правильным инструментом для решения этой проблемы. Вы можете придумать множество возможных сценариев, как что-то может сделать что-то еще, что не было запланировано. Модульные тесты ни в коем случае не являются лекарством от всех и не должны использоваться как таковые.

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

Другие вопросы по тегам