{TDD] Для чего нужны MockObject'ы ?

Гравицапа

elbirret elcno
Для чего нужны MockObject'ы ?

Вопрос скорее для TDD infected...
Собственно, можно пояснить на примере
какая от них польза и где их лучше всего (читать правильно) применять?
 

svetasmirnova

маленький монстрик
Заглушки?
http://www.lastcraft.com/mock_objects_documentation.php
Хорошо и внятно.

А если кратко и по-русски... Вот у меня примерчик образовался. Жизненный и из другой оперы. Надо протестировать код, в котором используется загрузка файлов на сервер, а simpletest не предоставляет возможности эмулировать передачу файлов. Т.е. массив $FILES при запуске тестов будет пустой. Можно подменить класс, загружающий файлы, Mock-объектом.
 

denver

?>Скриптер
Помогите плиз, разбираюсь с TDD, читаю PHPInside, оновременно стараюсь переосмыслить свой код. Должно быть это простой вопрос для вас.

Есть у меня класс XML, работает наподобие:
PHP:
class PageClass {

  function show() {
    $xml = new XML('html');
    $xml->insert(
      XML::element('body',
        XML::element('h1', 'This is a header text'),
        XML::element('p', 'paragraph goes here...'
          XML::attribute('class', 'description')
        )
      )
    );
    echo $xml->output();
  }
}
Узнал что статические методы плохо с точки зрения TDD.
Правильно ли я понимаю следующие Assertions:

1. Cтатические методы XML::element, XML::attribute плохо потому что тестировать PageClass нужно учитывая что методы XML::element и XML::attribute были протестированы и работают 100%, для этого обязательно нужно в тесте PageClass'a эмулировать их моками. Соответственно ну просто необходимо вынести их как отдельные классы типа XMLElement и XMLAttribute и тестировать PageClass с заглушками уже этих XMLElement и XMLAttribute. Это так?

2. Неужели проблема данных статических методов только в том что если вдруг неправильно работает класс XML то в тестах выскочит две ошибки (у обих классов) вместо одной (только у класса XML), это так?

-~{}~ 25.06.06 23:02:

Что смущает: с другой стороны нужно же весь XML делать большой заглушкой, ведь так? Разве нет способа заглушить статические методы?
PHP:
$xml = new MockXML('html');
$xml->insert(
  MockXML::element('body',
  ...
Так поступать нельзя?

-~{}~ 26.06.06 10:21:







ок, сформулирую попроще.

Singletons и static methods это: (выбери один)
1. некачественный ООП, потому что при грамотном ООП всегда грамотно реализуется и TDD (т.е. без повторения кода проверок и т.п.).
2. вполне нормальный ООП однако лучше избегать при TDD т.к. TDD накладывает ограничения на ООП.
3. Свой вариант? Плиз напишите...
 

svetasmirnova

маленький монстрик
denver
во-первых, это уже другая тема.

>Singletons и static methods это: (выбери один)
Само по себе использование их ничего не говорит ни об ООП, ни о TDD. Другой вопрос, что можно понаписать кучу классов, состоящих их одних статических методов и думать, что это такой здоровский ООП.

О классе XML тоже ничего сказать не могу. Делать методы element или attribute статическими нужно, только если они будут вызываться вне связи с объектом класса XML. Из приведённого примера это неясно.
 

denver

?>Скриптер
svetasmirnova
ожно понаписать кучу классов, состоящих их одних статических методов и думать, что это такой здоровский ООП
Я понимаю что класс это не просто набор функций, тем более статичных. Но у меня XML::element() и XML::attribute() это функции возвращающие XML и логичнее их имхо разместить в самом XML и сделать статичными. Можно вообще и без них в ущерб удобства использования, но меня это и интересует, обяхательно ли отказываться от них если нужно внедрить TDD?
 

svetasmirnova

маленький монстрик
Насколько я понимаю, не обязательно. Чем я руководствуюсь при объявлении методов статическими или динамическими, я написала постом выше. А вообще у нас pachanga и syfisher эксперты по TDD.
 

_vampiro_

Новичок
denver
нет, не обязательно. Если у тебя Сингл - то наряду с методом получения ссылки на экземпляр, создай метод для подмены экземпляра моком.
например SetInstance()

-~{}~ 26.06.06 12:50:

и я бы вынес эти методы в объект "ToolKit" или "Tools", содержащий только статику. Назвал бы GetXMLelement() и не трогал отлаженные методы при модифицировании объектов.
 

denver

?>Скриптер
svetasmirnova, _vampiro_
Ок, спасибо.

-~{}~ 05.07.06 16:47:

Плиз помогите мне наконец определиться как делать правильно. Мне нужно тестировать класс User который использует классы DB и LDAP. Без TDD мне по барабану какие User использует классы, но в моем случае в реальную DB лучше лишнего не писать ничего, и действительный ldap логин с паролем в тесте понятно что не должен светиться.

Как правильно делать:

1. реализовать User::setDBInstance() и User::setLDAPInstance() -- странно с точки зрения что мне кроме тестов это не понадобится нигде. Тогда уж можно вместо таких сеттеров сделать public для этих $instance, но это ж нонсенс?

2. User::__construct(..., $db = null, $ldap = null) -- Может все и ок. Однако придется делать то-же самое и со всеми статическими функциями? static User::getUsersList(..., $db = null, $ldap = null), это увеличивает список параметров (хоть и необязательных)

3. Оба варианта канают?
 

svetasmirnova

маленький монстрик
>но в моем случае в реальную DB лучше лишнего не писать ничего, и действительный ldap логин с паролем в тесте понятно что не должен светиться.

А ты что, пароли прямо в классах определяешь? Типа:
PHP:
class DBInstance {
private $password='xxx';
//много методов
}
?
 

denver

?>Скриптер
svetasmirnova
Пароли соединения я храню в синглтоне Config (а ля Registry Pattern). Однако в приведенном выше случае мне нужно реализовать не интеграционный тест, а пока только юнит тест класса User, соответственно не переключать на тестовую базу а вообще замокать DB и LDAP которые использует класс User.

Сейчас еще подумал и ужаснулся. User::setDBInstance() и User::setLDAPInstance() нифига не спасают если в юзере есть статичные функции (которые тоже должны использовать $DBinstance например). Как же правильно реализовать же это всё? Неужто только передавать как необязательные параметры в конструктор и все статичные функции?
 

Wicked

Новичок
Ну (с высоты своего мизерного опыта) я бы сделал DBInstence и LDAPinstance синглтонами. И уже сам класс User вытаскивал бы их в свое распоряжение. А там уже можно этими синглтонами орудовать как заблагорассудится. Например, чтобы они во время тестов возвращали ссылки на мок-объекты.

Поправьте меня, если я не прав :)
 

denver

?>Скриптер
Автор оригинала: Wicked
Поправьте меня, если я не прав :)
PHP:
class DB {

	static private $instance = null;
	
	static function getInstance() {
		if (!self::$instance) {
			if (defined('TEST_MODE') && class_exists('MockDatabase')) {
				self::$instance = new MockDatabase;
			} else {
				self::$instance = new Database;
			}
		}
		return self::$instance;
	}

	...

}
Грязноватенько получается..


Я тут почитал статейку (блин, поздновато попалась), получается в этом случае лучше всего добавить функции User::createDBInstance() и User::createLDAPInstance() а втесте делать Partial Mock этим двум.
Поправьте меня если я не прав :)
 

Necromant

Новичок
А лучше ввести сингелтон реестр и через реестр получать объекты DB и LDAP.
PHP:
Regedit::getObj("db");
Regedit::getObj("ldap");
 

denver

?>Скриптер
Necromant
PHP:
class User {
  function doSomething() {
    $db = Regedit::getObj("db");
    ...
  }
}
А перед тестом делать Regedit::setObj("db", MockDB) что-ль, а это кошерно? И как восстановить Regedit::getObj("db") после теста, например, если остальные тесты должны юзать обычную базу.
 

Wicked

Новичок
Necromant
а в тестовом окружении просто делать
Regedit::setObj("db", $mockDbObj);
Regedit::setObj("ldap", $mockLdapObj);
?
 
Сверху