Как стать автором
Обновить

Комментарии 87

Да в питоне и правда есть такая фишка, хотя помоему и не особо используемая.
Посмотрел код — вы парсите код каждого тестируемого скрипта? Как-то не очень хорошо. Почему бы не использовать Reflections для извлечения комментариев?
Ничего странного в этом нет. Вполне типично для PHP — все писать с нуля наступая на те же грабли и делая те же ошибки что и другие разработчики. Почему вообще не использовать например Doctrine Annotations
Вы сейчас пытаетесь разжечь холивар? Не понимаю, зачем все постоянно ругают php.
Несомненно, есть языки лучше и хуже, почти все преследуют свою цель. Если бы php был бы таким плохим — на нем бы не писали. Людям тут помогать надо, а не говорить, что они — недопрограммисты.
Я не ругаю сам PHP как язык ) Я говорю про странную тенденцию PHP программистов не использовать готовые решения.
А они всегда применимы?
Мы вот узнали, что рефлексии для всего хватает. Не уверен, что Doctrine Annotations нужен для чего-то кроме доктрины.
Вы ошибаетесь. Он используется например в Symfony 2 в встроенном валидаторе. Почему-то ведь разработчики S2 не стали пилить свой велосипед, а использовали Doctrine Annotations. Так же он поддерживает кеширование, неймспейсы и много чего ещё.
Ок. Только вопрос: зачем это здесь? ;)
Как с помощью Doctrine Annotations написать тест, показанный на картинке? Покажите на примере.
Doctrine Annotations это просто парсер. Я полагаю, что комментатор уровнем выше просто предложил использовать его, вместо своей имплементации
Посмотрите как сделан Lime2 от создателей symfony.
Там для аннотаций где-то такой же парсер должен был быть
github.com/bschussek/lime/blob/master/lib/lexer/LimeLexerAnnotationAware.php
Впрочем, от него в итоге отказались.

Не подскажите где почитать про это в пайсоне?
вы хотели сказать в пуфоне? :D
писон же
Какой еще пайсон?..
Как с помощью Reflection получить код приватного метода класса? Покажите, пожалуйста.
Приватный метод можно сделать видимым. А из него по идее можно вытащить комментарии. Или я не прав?
Сори, не проверял на приватных методах.
Зачем весь код метода? Doc-тест задается в комментарии. Получить комментарий можно просто:
<?php
class A {
    /**
     * Inline test example
     * @assert (2,2) == 4
     */
     private function _add($a,$b) {
          return $a + $b;
     }
}

$refl    = new ReflectionClass('A');
$comment = $refl->getMethod('_add')->getDocComment();
echo $comment;
Да, но нужно же еще выполнить сам тест, т.е. по сути вызвать приватный метод класса.
Да, это решение. Спасибо.
Но в защиту token_get_all хочу сказать, что:
1. Эффективнее по ресурсам: обработка файлов выполняется по очереди, для Reflection пришлось бы сделать 100500 инклюдов.
2. Быстрее: тут только парсинг скрипта, а в случае Reflection еще и выполнение всего кода.
3. Если в разных файлах будет функция или класс с одним и тем же названием, то при использовании инклюдов будет конфликт имен.
А зачем нам его код? Нужен комментарий к методу и возможность его вызвать. Первое решается с помощью ReflectionMethod::getDocComment(), второе — ReflectionMethod::invoke() или ReflectionMethod::invokeArgs() с предварительным ReflectionMethod::setAccessible() для приватных или защищённых методов.
Упс, не успел.
Ха! И кто из нас больший хабразадрот? ;)
Да, это решение. Спасибо.
А зачем вообще тестировать приватные классы?
ерр, приватные методы
С той же целью, что и публичные.
Публичные тесты пишутся, чтобы зафиксировать поведение класса (в общем случае).

Поведение определяет интерфейс.

Приватные методы интерфейс не определяют.

Так что «с той же целью» не работает, по крайней мере с моей точки зрения.
Всякие случаи бывают.
Если там, допустим, какая-то сложная логика, которую лучше отдельно протестировать.
Ну или если, неочевидно когда именно этот метод будет вызван публичным.
Ну так а вам, как клиенту, который использует этот класс, не всё ли равно, какие приватные методы и как работают?

Важен ведь результат, а не процесс, верно?
Как клиенту мне пофиг какие классы используются, лишь бы сайт работал =)

Я конечно согласен, что приватные методы тестировать зачастую не стоит, ибо они не отвечают за связь между юнитами, но нельзя же говорить, что их никогда тестировать не надо. Ситуации бывают разные.
Эти «ситуации» — отличный сигнал, что что-то делается неправильно :-)
Возможно я чего-то не понимаю, но существуют же разные уровни тестирования с разными целями.

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

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

При покрытии тестами и тех, и других — мы узнаем, что сломались и публичные, и приватные.

Во втором случае «сразу» узнать что было причиной не получится, потому что вы увидите гораздо больше сломанных тестов, чем в первой ситуации. И точно так же придётся искать, что именно вызвало какие проблемы

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

> это равносильно приемочному тестированию класса

Не совсем. Одна из главных характеристик модульных тестов «изоляция» всё ещё осталось. Так что это те же самые модульные тесты. Которые, повторюсь, преследуют своей целью покрытие поведения тестируемого класса, а не его реализации.
В том, что вы говорите, определённо есть смысл. Но думаю, что, всё-таки, иногда тестировать приватные методы может быть полезно.

Например, представьте, что у класса есть простой публичный интерфейс, состоящий из нескольких методов (так сказать, coarse-grained). За ним скрывается множество приватных методов (fine-grained), которые вызывают друг друга.
Так вот, иногда удобнее протестировать такой приватный метод напрямую, чтобы учесть все corner case'ы (простите, забыл перевод), потому что через публичный интерфейс сложно подобрать подходящие параметры.

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

Если писать тесты до реализации — тогда реализация в принципе может появиться только после теста, написанного специально для неё.

ps: все edge cases учесть невозможно даже с тестированием приватных методов, а на очевидные ошибки укажет отчёт о покрытии кода тестами
Все очень логично. Автор ниасилил доку по doctest, автор по какой-то причине (будем вежливыми) не набрал «doctest php» в гугле и решил делать свою реализацию (хотя мне странно, что никто из собравшихся не спросил автора, чем его реализацию лучше ХХХ) =>
в его восхитительной реализации можно тестировтаь только изолированнве методы => код приватного метода сложно покрыть вызовом публичного =>
УРА! Инлайн тесты можно использовать для тестирования закрытых членов класса!

Хотелось бы посоветовать автору перед тем, как что-то делать самому или написать о чем-то своем на хабре, произвести поиск готовых аналогов.
Ну а мой вопрос первоначальный был — зачем вообще покрывать приватные методы тестами. Лично я не вижу в этом никакого смысла, и даже считаю это плохой практикой.
Ну код-то дожен быть протестирован, а если ты не умеешь это делать через публичный интерфейс по причине кривизны твоего самодельного фремворка, то что остается делать?
В том-то и причина, что по моим убеждениям «код-то дожен быть протестирован» неприменимо к приватным методам.

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

У всех разные подходы в программировании. Я предпочитаю не делать лишнюю работу, от которой никакого профита нет :-)
Ну, главное, что есть возможность это реализовать. А есть или нет польза — это уже в каждом отдельном случае будет решать автор кода. Если метод приватный, это не значит, что от его тестирования нет пользы.
Я считаю, что нужно тестировать поведение (интерфейс), а не реализацию, которая завтра может поменяться.


Постойте, это как посмотреть. Unit-тесты как раз и тестируют реализацию. А вот функциональные — тестируют интерфейс. :)

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

В том-то и причина, что по моим убеждениям «код-то дожен быть протестирован» неприменимо к приватным методам.


Приватные методы — это не код? (вопрос риторический, прошу вас, не отвечайте на него)
Давайте тезисно:

Unit-тесты
1. Тестируют поведение в изоляции (потому они и называются unit).
2. Поведение определяется реакцией интерфейса на вызовы.
3. Поведение не определяется способом реализации.
4. Приватный метод деталь реализации.

Функциональные тесты
1. Тестируют взаимодействие компонент

> Inline-тесты могут тестировать блоки, из которых состоят первые блоки.

Зачем? Всё равно модульные тесты покрывают весь функционал класса.
Если модульные тесты покрывают весь функционал класса, inline-тесты теряют свою практическую ценность, так как по сути дублируют существующие тесты, НО остается еще ценность их как наглядной документации.

Если модульные тесты НЕ покрывают весь функционал класса, inline-тесты имеют свою нишу, так как их проще добавить.
Если модульные тесты не покрывают весь функционал класса — нужно дописать недостающие тесты, чтобы они покрывали весь класс.

Про документацию — очень частично согласен.
Ну зачем же так? Разумеется, я искал готовые решения.
Но на мой взгляд, вот это:

* php> factorial(0)
* 1

хуже, чем это:

* @assert factorial(0) == 1
«Приватный» или «закрытый» — это понятие относительное. Например, для конечного юзера, весь программный код является приватным, потому что юзер не может в него залезть.

С точки зрения моего класса, все его приватные методы являются открытыми для класса-обладателя. В чём же принципиальная разница? Приватные методы никогда не глючат? А если я напишу приватную функцию вычисления синуса?

class myClass {
   private function _sin($arg) { ... } // Почему бы не протестировать???
}
> Например, для конечного юзера, весь программный код является приватным, потому что юзер не может в него залезть.

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

> В чём же принципиальная разница?

Принципиальная разница в том, что для клиента этого класса неважно, как этот класс реализован. А лишь важно, что известные вызовы приводят к известным результатам.
Приватность определяется не доступом к сорсам, а программным доступом к членам.

myClass имеет доступ к приватному члену _sin(), значит метод _sin является открытым для класса myClass. То есть открытость/закрытость — относительны. Для одного класса этот метод открыт, для другого закрыт.

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

Внутреннее тестирование нисколько не противоречит этому утверждению и даже помогает реализовать его на практике. Точно так же, конечному пользователю не важно что вы используете: _tdd, паттерны, agile, парное программирование. Ему важно только что при нажатии на кнопку в вашей программе он получает правильный результат.
Начинаю читать про тестирование, как раз задаюсь вопросом тестировать ли приватные методы. Вы придерживаетесь точки зрения, что не нужно.

Как поступать с классами, приватные методы которых ходят в БД за информацией? + в зависимости от входящих данных будут ходить в разные места.
Спасибо.
> Вы придерживаетесь точки зрения, что не нужно

Так точно — я тестирую поведение, а не реализацию. Т.е. мне важно, что делает класс, а не как он это делает.

> Как поступать с классами, приватные методы которых ходят в БД за информацией?

Перед вызовом заливать в базу тестовый набор данных, который на известные запросы будет возвращать известный результат.
Крайняя мера — моки, но это если вы от хранилища абстрагированы и работаете не напрямую с БД, а через какой-то дополнительный слой доступа к данным.
>С точки зрения моего класса, все его приватные методы являются открытыми для класса-обладателя.
Конечно, ибо эти методы относятся только к нему и актуальны только для него. У наследника доступа нет, но есть своя реализация и приватные методы, ему нет дела до приватных методов своего родителя, потому что они его не касаются.

>А если я напишу приватную функцию вычисления синуса?
Раз она приватная, значит она нужна (касается) только для этого класса и он ее использует для своей реализации. Лучше протестировать то, для чего она используется. Завтра синус менеджеры заменят на косинус и в чем смысл теста?)

При рефакторинге кода, написанные тесты на приватные методы составят незабываемое «удовольствие». Простейшее перемещение или изменение сигнатуры приватного метода доставит еще и кучу проблем с его тестами. В итоге, они просто будут скорее всего удалены, либо рефакторинг отложен со словами «ну вот, покрыл тестами, работает, да ну и фиг с ним, с дублированием кода».
Приватные методы они на то и приватные, что в любой момент могут быть удалены, переписаны, дополнены, перемещены, раздроблены на более мелкие части. Если проект развивается здоровым путем, то это для него нормальная практика.
У наследника доступа нет, но есть своя реализация и приватные методы, ему нет дела до приватных методов своего родителя

А протектед-методы надо тестировать?

Завтра синус менеджеры заменят на косинус и в чем смысл теста?)

Завтра менеджеры заменят весь класс myClass и в чём смысл?

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

Абсолютно всё то же самое актуально и для публичных методов и даже для целых классов. Рефакторинг — он такой.
> А протектед-методы надо тестировать?
Надо, через тесты на публичные методы конкретных реализаций класса

>Абсолютно всё то же самое актуально и для публичных методов и даже для целых классов.
Опубликованные методы и классы не так просто выпилить, особенно если ты не владеешь всем кодом проекта и публикуешь API. Тут поможет только deprecated и делегирование

Ну да, спорить не буду, рефакторинг он разный… )
Надо, через тесты на публичные методы конкретных реализаций класса


В этом и суть! Протестировать один публичный метод гораздо сложнее, чем каждый метод по отдельности! Смотрите сами:

Допустим, что у публичного метода есть граф, по которому можно пройти пятью разными путями. Значит надо написать 5 тестов, что бы покрыть 100%. Но публичный метод использует приватные методы: _sin(), _cos(), _tan(). У каждой из них есть ещё по 3 пути в графе. Значит, у одного публичного метода есть 5*3*3*3=135 путей. Т.е. надо написать 135 тестов, что бы покрыть все пути графа и протестировать всю эту комбинацию хитросплетения публичного метода с приватными.

Но было бы гораздо проще, написать по 3 теста для каждой из приватных функций и 5 для публичной. В итоге получим 5 + 3 + 3 + 3 = 14 тестов.

Хотите писать135 тестов для одного «большого» паблика или 14 отдельных тестов на каждый маленький метод?
Ну значит надо по любому зарефакторить такой публичный метод, он слишком много на себя берет. ну, например, применить к нему выделение стратегии и тестировать каждую стратегию изолированно и не зависимо от класса. Пусть одна зависит от синуса, другая от косинуса, третья от звезд на небе, на каждую будет нормальный тест.

На практике еще никогда не возникало желания и необходимости тестировать приватные методы. На мой взгляд, в этом случае явный признак — проблема в архитектуре.
Метод может состоять из 10 строчек кода.
public function ($arg) {
if () {
    $arg = $this->_sin($arg);
    $arg = $this->_cos($arg);
} elseif () {
    $arg = $this->_cos($arg);
    $arg = $this->_sin($arg);
} elseif () {
    $arg = $this->_sin($arg);
    $arg = $this->_tan($arg);
} elseif () {
    $arg = $this->_cos($arg);
    $arg = $this->_tan($arg);
}
}

Как его ещё разбивать на стратегию?

Если тестировать все методы отдельно, то на паблик хватит четырёх тестов и ещё по три теста на каждый приватный. Итого: 4+3+3+3=13 тестов.

А сколько тестов написали бы вы?
Хм, интересно. А пусть мы написали 4 теста. 1 из них провалился, который выявил ошибку в _sin(). Мы ее поправили. Ошибок в _sin() больше нет. Поэтому нет необходимости писать 135 тестов.
Разве не так?
«Необходимость тестировать приватные методы — проблема в архитектуре»? В статье нет слова «необходимо», там написано «можно».

Если вам нужно отрефакторить один класс, вы же не бросаетесь покрывать тестами весь проект. Так почему при рефакторинге одного метода не ограничиться тестами только для этого метода?
НЛО прилетело и опубликовало эту надпись здесь
Здесь я имел в виду методы классов, которые обращаются к внутренним свойствам — для таких inline-тест написать не получится.
На мой взгляд не очень хорошо, что тесты будут «раскиданы» по коду, т.е., например, в случае с phpunit их можно сложить в отдельную папочку, а в вашем случае все равно будут phpunit тесты в отдельной папке, плюс тесты в комментариях.
При запуске скрипта выполнения тестов они все станут видны.
ну тут холиваром попахивает, не буду спорить ;) Просто мне не очень нравятся параллельные иерархии
Кстати, хабровчанин Slivu похожий подход для руби реализовал github.com/slivu/spine

Inline-тесты могут быть написаны даже для закрытых методов класса

Нифига, рефлексия наше всё )

Имхо, для простых тестов действительно отличное решение. Я уже боялся, что придется код в аннотациях писать )

А вообще никто не запрещает писать тесты действительно в самом классе. Только в PHPUnit странное ограничение, чтобы класс был наследником PHPUnit_Framework_TestCase. Но допустим, в том же Codeception, ну может и в других фреймах вполне можно бы такое сделать. Минусы правда тоже налицо: класс становится излишне толстым и появляются методы, которые висят и не работают.

Короче, отличная тема для дискуссии. Нужны такие тесты и насколько.
Ваши — точно нужны. Но вам явно нужна интеграция с фреймворком, чтобы можно было все тесты вместе гонять.
Ваши — точно нужны. Но вам явно нужна интеграция с фреймворком, чтобы можно было все тесты вместе гонять.

Если вы имеете в виду PHPUnit, то да, inline тесты по сути дела дополняют его.
Как-то можно их будет запустить внутри phpunit?
Когда же программисты начнут писать доктайпы для людей? Пишут или для IDE, или для какого-нибудь парсера, или лицензии, или просто не пишут.
Отлично, идея класс.

Теперь изучите Reflection и сделайте «более лучшую» реализацию :)
Он кроме примитивных типов с чем-нибудь еще работает? Если нет, то вряд ли его можно считать адекватной заменой юнит-тесту.
Это не замена unit-тестам, это дополнение.
Простота и скорость добавления тестов: что и говорить, иногда хочется написать тест, но когда вспомнишь, что для этого придется создавать всю иерархию папок для этого класса (напр. App_Module_Class), потом создавать файл и класс теста, писать один и тот же занудный код проверки, потом искать, где же этот PHPUnit находится и как его корректно запускать, — руки опускаются. Да и не жалко это сделать, если тест сложный и важный, а вот когда простой...


У меня есть такое впечатление, что такие проблемы решены в IDE. В частности NetBeans позволяет легко и непринуждённо делать то, что вы описали.
Если в функциюgetAsserts изменить
function getAsserts($comment) {
	$asserts = array();
	$lines = preg_split("#\n|\r#", $comment);
	foreach ($lines as $line) {
		$line = trim($line, "\r\n/* \t");
		if (substr($line, 0, 7) == '@assert') {
			$assert = trim(substr($line, 7));
			if ($assert) $asserts[] = $assert;
		}
	}
	return $asserts;
}

на
function getAsserts($comment) {
    $asserts = array();
    if( preg_match("/<assert>[\s\S]+?<\/assert>/", $line $tmpAasserts) )
    {
        $asserts[] = $tmpAasserts;
    }
    return $asserts;
}

То можно будет писать многострочные ассерты, А учитывая что тесты проверяются с помошью eval то этот вариант еще и гибче будет в разы.
А как тестировать многострочные тесты?
Это подходит только для простых тестов.
Минус системы в том, что раньше мы могли отрефакторить и удалить полсистемы к чертям, и проверить, что тесты не упали.
Сейчас мы удаляем полсистемы вместе с самими тестами.
Все верно: если метода нет, то и тест для него не нужен. Inline-тесты тестируют не систему, а конкретные методы, к которым привязаны. Заметьте, что это не замена unit-тестов, а лишь их дополнение.
В таком виде это всё же не «inline-тесты» — а всё же ближе к программированию по контракту, в том смысле что в комментариях да без аквтокомплита имеет смысл записывать только короткие проверки. Те же самые проверки с тем же успехом может быть внесено в код метода + в php есть assert

1. Да, только простые тесты.
2. assert в коде принципиально отличается от inline-теста: первый будет проверен уже при выполнении скрипта, тесты же можно прогнать заранее.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории