[TDD] Как покрывать тестами private методы в классе?

atv

Новичок
atv не хотелось бы переходить на личности
но всё-таки перешёл

но сдается мне человек, который не знает аргументов getMock похоже вообще тестов не писал
представляешь, кроме PHPUnit есть ещё и SimpleTest, в котором такого метода нет, а у тебя в примере не указано какой тест-фрэймворк ты пользуешь.

Вообще тебе в моем посте адресована только первая строчка.
вообще то, мы на форуме, по существу что-нибудь будет?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Блин, atv, holywar в форумах локалок надо делать, а не в теории.

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

-~{}~ 25.02.08 00:05:

Теперь ближе к телу. PHP - не С и не Java. Почему бы этим не пользоваться?

PHP:
class myClass implements PSO_IOperator { 
...
function runTest(testFramework $Test){
   $method = $Test->method;
   $params = $Test->params;
   return $this->$method($params);
}
}
-~{}~ 25.02.08 00:15:

тест:
PHP:
 public function doRequest(){
        $this->method = 'doRequest';
        $this->params = array(1,2,3);
        $o->expects($this->once())
            ->method('runTest')
            ->will($this->returnValue('<foo></foo>'));
        ...
(методу runTest передать $this)
    }
 

mz

Новичок
Закрытые методы тестировать надо.
Зачем? Тогда вопрос почему они были закрыты, если эти методы так важно тестировать по отдельности? Почему такая сложная логика, нуждающаяся в тестировании, не вынесена в отдельную сущность, как предлагает lastcraft [1]?

1. http://www.sitepoint.com/forums/showthread.php?t=281686
 

Fred

Новичок
Автор оригинала: whirlwind
Разработка на основе тестов, а не тестирование уже написанного. Разницу чуешь?
Вот пример твоего тестового кода:

Автор оригинала: whirlwind
PHP:
    public function testGetBalance(){
        ...
        
        $o = $this->getMock('PSO_LibertyReserve_Operator',
            Array('createBalanceRequest','doRequest',
                'analyseLrResponse','parseBalance'));
        ...
        $o->expects($this->once())
            ->method('doRequest')
            ->will($this->returnValue('<bar></bar>'));
        ...
        $amount = $o->getBalance($acc);
        $this->assertEquals(100500,$amount);
    }
На момент написания теста, если это tdd, у тебя можеть быть лишь интерфейс класс. А то, что ты моком заменяешь целый набор защищенных методов намекает на то, что класс написан раньше теста. К тому же концепция tdd, когда ты пишешь "грязный" код, чтобы тест прошел, а потом начинаешь этот код рефакторить, следя за тем, чтобы тест выполнялся у тебя нереализуема, т.к. у тебя тест связан с внутренней реализацией класса.

Автор оригинала: whirlwind
С чего ты взял что лучше? Этот метод был введен намеренно, для того что бы избавиться от зависимости транспортного уровня в тестах, которые никакого отношения к этому самому транспорту не имеют. Где в коде видно, что в работе не используется впрыск зависимого объекта? Стабы для того и придумали, что бы не заморачиваться эмулированием всей необходимой фикстуры а просто настроить 1 метод.
Ну можно настроить 1 метод "объекта-транспорта" и внедрить его в тестируемый объект. Это не сложнее.

-~{}~ 25.02.08 01:36:

Хочу резюмировать вышесказанное.

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

Ещё одна из целей тестирования - безболезненный рефакторинг. Но как только мы связываем тест с внутренний реализацией класса, он становиться затруднен или совсем невозможен.

Так что я однозначно против тестирования защищенных методов.
p.s. и против использования частичных моков :rolleyes:
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
>то, что ты моком заменяешь целый набор защищенных методов намекает на то, что класс написан раньше теста

Кто тебе сказал, что код пишется с нуля?
Может быть, он меняет существующую систему и большинство методов известны заранее!

>внедрить 1 метод транспорта в тестируемый объект
и получить обоюдную зависимость объекта и транспорта ... замечательная идея

-~{}~ 25.02.08 00:43:

>я однозначно против тестирования защищенных методов

Разве топик называется "надо ли тестировать защищенные методы"?
Вопрос сформулирован со словом "как", а не "зачем" или "стоит ли"!

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

Но я лично считаю, что думать надо о задаче и классах, тесты надо писать под классы, а не классы проектировать под тесты.
 

mz

Новичок
grigori
Разве топик называется "надо ли тестировать защищенные методы"?
Вопрос сформулирован со словом "как", а не "зачем" или "стоит ли"!
А еще топик в теории, что подразумевает некоторое обсуждение и самой проблемы.
В любом случае тестирование private/protected методов является запахом плохих тестов.
 

atv

Новичок
Блин, atv, holywar в форумах локалок надо делать, а не в теории.
Блин, grigori, надо как-то обосновывать свои высказывания.

Если Вы никогда не делали сложные движки с обеспечением целостности данных - не морочте голову, оно вам просто не надо.
Что Вы говорите.

Мне в своей практике удавалось избегать тестирования закрытых методов, поэтому я задал простой вопрос "чем вызвана необходимость тестировать закрытые методы", чтобы, как раз, понять надо оно мне или не надо. А в ответ получил невнятные высказывания в свой адрес.

Если Вы делали "сложные движки с обеспечением целостности данных", так объясните причины, по которым пришлось тестировать закрытый метод.
 

Alexandre

PHPПенсионер
Но я лично считаю, что думать надо о задаче и классах, тесты надо писать под классы, а не классы проектировать под тесты.
в соответствии с методологией TDD, тесты пишутся одновременно с классами.

если, при разработке класса нам необходимо оттестировать "внутренний" метод, почему я не могу это сделать?

Лично мне это видится так: метод временно объявляется паблик и тестируется... Или на приватный метод, пишется паблик враппер.
По истечении какого-то времени, когда данный метод уже разработан и оттестирован, он переходит в "приватный", а тест помечается как прошедшим и направляется в архив ("Не храните устаревших моделей").
Место этого теста, занимает другой тест, который уже "работает" с паблик методами данного класса и поставляется совместно с API класса..
 

zerkms

TDD infected
Команда форума
в соответствии с методологией TDD, тесты пишутся одновременно с классами.
бгггг
программист не может писать одновременно код в двух файлах.
поэтому сначала пишется тест, а потом уже - тестируемый код.

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

Лично мне это видится так: метод временно объявляется паблик и тестируется... Или на приватный метод, пишется паблик враппер.
тесты должны срабатывать всегда. в этом же случае непосредственно перед тестами придётся изменять видимость методов. не говорю уже о том, что местами подобное изменение кода может привести к тому, что он не выполнится вообще (например, понижение уровня видимости в наследнике)
 

whirlwind

TDD infected, paranoid
>внедрить 1 метод транспорта в тестируемый объект
и получить обоюдную зависимость объекта и транспорта ... замечательная идея
+1

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

программист не может писать одновременно код в двух файлах.
Ну я уверен, что он подразумевал кусок кода в тесте, кусок кода в реализации, а не писанина в двух окнах сразу ;)

потому, что из тестов о внутренней реализации ничего не известно. класс для тебя - чёрный ящик, характеризующийся определённым интерфейсом, с описанием списка аргументов, возвращаемых результатов, и логики. всё.
Ты прав, конечно. Но тут речь не о идеологии а о сокращении кода тестов. Согласись что 3 раза (по количеству интерфейсных методов) эмулировать фикстуру для работы doRequest это перебор? Я вот не знаю как вы пишете, но я например делаю отдельный тест на каждую ситуацию внутри тестируемого метода. Это значит что в реальной ситуации это не 3 раза doRequest а все 33.

На момент написания теста, если это tdd, у тебя можеть быть лишь интерфейс класс. А то, что ты моком заменяешь целый набор защищенных методов намекает на то, что класс написан раньше теста.
Во-первых, ты сам себе противоречишь. То тестов на защищенные не нужно делать, то вдруг я не TDD, потому что у меня нет тестов на защищенные. Во-вторых, это не доказывает что на методы нет тестов.

PS.

Я не считаю себя гением. Если тут кто-то предложит вариант когда тесты к примеру

public function testGetBalance_ErrorDoRequest()
public function testGetBalance_ErrorInvalidXml()
public function testGetBalance_ErrorAnalyseLrResponse()
public function testGetBalance_ErrorParseBalance()
public function testGetBalance()
можно понятно написать без стабов при этом каждый раз не повторяя тесты защищенных методов, то я с удовольствием возьму это на вооружение.
 

atv

Новичок
Но тут речь не о идеологии а о сокращении кода тестов. Согласись что 3 раза (по количеству интерфейсных методов) эмулировать фикстуру для работы doRequest это перебор?
Если ты борешься с дублированием кода тестов, так вынеси эмуляцию фикстуры для doRequest в отделный, служебный метод, и вызывай его в тестовых методах.
 

Fred

Новичок
Я уже сильно уклоняюсь от темы, но все же.

Автор оригинала: whirlwind

Я не считаю себя гением. Если тут кто-то предложит вариант когда тесты к примеру


PHP:
public function testGetBalance_ErrorDoRequest()
public function testGetBalance_ErrorInvalidXml()
public function testGetBalance_ErrorAnalyseLrResponse()
public function testGetBalance_ErrorParseBalance()
public function testGetBalance()
можно понятно написать без стабов при этом каждый раз не повторяя тесты защищенных методов, то я с удовольствием возьму это на вооружение.
Если выделить отдельный транспортный объект, то тесты на то, что запрос не вернул ответ, пришла невалидная xml, пришла xml, не удовлетворяющая на dtd, ... можно вынести в тест этого транспорта.
А в тестируемом классе у тебя остается 2 теста:
- с корректными данными, полученными от стаба этого транспорта
- обработка исключения/ошибки, сгенерированное стабом транспорта
 

whirlwind

TDD infected, paranoid
Это же разные ситуации - фикстура разная.

-~{}~ 26.02.08 11:19:

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

Fred

Новичок
Автор оригинала: whirlwind
Это же разные ситуации - фикстура разная.

-~{}~ 26.02.08 11:19:

Fred и что это меняет? Тесты просто переедут в другой класс. Или ты предлагаешь вообще все внутренние вызовы делегировать каким-либо другим объектам, только ради того, что бы сократить код этого конкретного теста?
Это скорее страховка от комбинаторного роста числа тестов. У тебя все 3 публичных метода дергают doRequest. В приведенном выше варианте для getBalance - 5 тестов. 3*5 = 15. А если тестировать удачный+ 1 неудачный сценарий по обработке исключения, то в сумме получается 6 тестовых методов + 3-4 метода для транспорта
 

texrdcom

Новичок
Ради интереса когда то написал был скрипт которые считал сколько классов в некой библиотеки, сколько в данных классах публичных методов, сколько приватных и защищенных, честно говоря результат меня поразил:
10% защищенных, приватных методов, 90% публик.
Практически весь код лимбы покрыт тестами.

Это я к тому пишу, что не нужно закрывать все методы подряд, ради чистоты интерфейса.

p/s
Лимба ZF
 

pachanga

Новичок
Ээрм...что бы это значило? ;)

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

texrdcom

Новичок
http://framework.zend.com/ - Лимба ZF
__
А если посмотреть на другие технологии, то в vs 2008 Unit тестирования разрешает тестировать скрытие методы, пробовал только для C#
Но по идеи все верно иногда нужно протестировать скрытый метод, делать его временно public можно но глупо Unit должны запускаться всегда в полном комплекте.
так что селяви.
 

zerkms

TDD infected
Команда форума
Это я к тому пишу, что не нужно закрывать все методы подряд, ради чистоты интерфейса.
эм... вроде не ошибусь, но кажется что фаулер говорил - что методы всегда нужно создавать приватными и потом уровень доступа повышать при необходимости
собственно - считаю, что это жутко эффективный подход
 

джамшут

Новичок
вместо "private function functioName" пишите "function _functioName" и не выдумывайте себе геморроя.
 
Сверху