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 тестировать закрытые переменные - я не знаю. Но с точки зрения юнит-тестирования вы не должны этого делать.
Изменить (в ответ на комментарий):
Я понимаю ваши опасения по поводу возможного выявления внутреннего состояния, однако я не думаю, что модульное тестирование является правильным инструментом для решения этой проблемы. Вы можете придумать множество возможных сценариев, как что-то может сделать что-то еще, что не было запланировано. Модульные тесты ни в коем случае не являются лекарством от всех и не должны использоваться как таковые.
Я согласен с другими в том, что в целом вы хотите избежать доступа к частным лицам в своих тестах, но в тех случаях, когда это необходимо, вы можете использовать рефлексию для чтения и записи свойства.