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

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

Во второй ветке Symfony было решено использовать таки phpUnit (http://www.symfony-project.org/blog/2010/03/04/symfony-2-0-and-the-php-ecosystem), несмотря на разрабатывающийся сейчас lime2.
Свежая новость. Приятно слышать.
> При этом, код может быть совершенно любым (но пригодным для тестирования)
ага, давайте запихнем все в один файл, на каждое действие сделаем функцию, и будем тестировать. тестировать можно, но код будет ужасен.

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

Крайность — воспринимать TDD как крайность.
вовсе нет, если следовать всем идеалам TDD, то невозможно выстроить красивый код с точки зрения архитектуры. Приведем пример простой

Надо написать игру ну например морской бой. Там есть корабли, 4-х палубные, 3-х и тд. С точки зрения TDD сначала нужно написать тест на создание 4-х палубного корабля. Написали тест примерно такой

$korabl4palub = new 4PalubKorabl();
assert(..., $korabl4palub);

все верно, однако архитектура 4PalubKorabl не продумана, не ясно изначально что он будет принимать в конструктор, какие у него будут методы и тп. Поэтому я думаю, что лучше сначала продумать полностью архитектуру, какие будут методы, какие классы, какие зависимости, записать это все, а вот затем создавать тесты на уже спроектированные классы, а потом их реализовывать.
Вы забыли про непрерывный рефакторинг в процессе TDD. Поэтому, если в начале Вы сделали предположение о «4PalubKorabl», то в конце работы от него может вообще ничего не остаться.
верно, но лично меня не устраивает такая схема работы, когда архитектура перетрушивается с ног на голову по 10 раз на день. Я лично предпочитаю сначала прорабатывать архитектуру, потом тесты, потом код.
Когда вы пишете тест, вы пишете вариант использования. Т.е. по сути проектируете интерфейс, сразу же его используете и убеждаетесь подгодит он вам или нет. В этом случае вы никогда не добавите ничего лишнего. Код получается максимально простым.
Я знаю, что это довольно непревычно писать тест сначала, но никто не отменяет минимальную фазу проектирования перед каждой итерацией. Почитайте, как описывает Мартин пример парной разработки в «Быстрой разработке ПО».
И еще, чем большую работу вы проведете по предварительной проработке архитектуры, тем больше вероятность того, что вы сделаете ошибку. Если Вы гениальный архитектор — отлично, а я стремлюсь сэкономить время и минимизировать кол-во ошибок.
Каскадный процесс разработки не тру подход. История неоднократно это подтверждала.
Если следовать идеалам TDD, то основное время будет тратиться на проектирование, проектирование архитектуры, ибо без этого TDD не работает.
— «Тесты надо писать в начале. Тест, написанный после кода, ничего не стоит и является только обузой.»
И очень помогает портировать или модифицировать код.

Не, TDD слишком много времени отнимает, нужно продумывать поведения кода в различных ситуациях, да и не обязательно 100% обвешивать тестами, лучше только сложные места.

И да, вы псих)
не согласен с Вами. Тесты вне зависимости от методики написания должны покрывать от 80 и больше % кода (все зависит от сложности проекта и корпоративных стандартов). Я привык покрывать тестами до 98% кода. Тогда я могу быть уверен что мой код не падает никогда. Писать тесты это не «вы псих», это нормально.
не согласен с Вами. Test coverage в процентах — вводящая в заблуждение характерстика, она абсолютно ничего не гарантирует. Не стоит на нее полагаться и стремиться ее улучшить просто для того, чтобы ее улучшить. Можно достичь 98% каким-нибудь сложным интеграционным тестом, который будет абсолютно бесполезен на практике. Привязывать что-то к test coverage мне кажется очень сомнительной практикой, которая закрывает суть проблем, а не помогает их решать. Поэтому и советы вроде «тесты должны покрывать от 80 и больше % кода», «лучше 98% кода» — очень сомнительные, скорее всего, даже вредные.

Сам этим страдал, пока не понял, что мои 90% code coverage мало что тестируют на самом-то деле, и что можно было написать тесты, которым утилита бы показала процентов 50 code coverage, но они проверяли бы значительно больше потенциальных мест для ошибки. Эти инструменты полезны для того, чтоб понять, какие куски кода не выполняются во время тестов, а не для получения удовлетворения от цифр. Решение о том, для чего и какой тест писать — это решение разработчика, в котором ему следует руководствоваться, к примеру, правилом о том, что тест должен падать.
Вы, конечно же, абсолютно правы. Code coverage чисто ради цифр это бесполезная информация. Однако лично я такого понятия, что тест должен покрывать свыше 80% тестируемого метода. Лучше все 100. если оно того требует. Это вовсе не означает — что вот ага 100% — все я крут. Тест в первую очередь должен проверять максимальное количество ситуаций где метод может упасть. Если вы написали допустим 10 тестов на метод, покрыли скажем 50% кода — как вы можете быть уверены в том, что остальные 50% не упадут? Полемику о качестве самих тестов разводить думюа не стоит. Будем исходить из идеала что каждый тест проверяет идеально свой участок кода.

Code coverage позволяет увидеть что не покрыто тестами и что надо еще покрыть чтобы быть уверенным что метод будет работать идеально в любых ситуациях.
Тест в первую очередь должен проверять максимальное количество ситуаций где метод может упасть. Если вы написали допустим 10 тестов на метод, покрыли скажем 50% кода — как вы можете быть уверены в том, что остальные 50% не упадут. Будем исходить из идеала что каждый тест проверяет идеально свой участок кода.


Вы и с тестами не можете быть уверены, что остальные 50% не упадут.

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

Мне кажется, что тесты — это крайне полезный инструмент, который позволяет разгрузить голову программиста, облегчить рефакторинг, совместную разработку и автоматизировать процесс отладки, но не самоцель. Писать тесты ради тестов кажется неправильным. Мне думается, что тесты стоит писать именно исходя из их назначения. Что тесты стоит писать только тогда, когда их стоит писать) Ведь если писать слишком много тестов, то появляется большая вероятность, что то, что нужно, как раз останется непротестированным. Мне поэтому TDD в чистом виде кажется хуже, чем какой-нибудь прагматичный компромиссный подход, когда тесты не пишутся для тривиальных мест кода или тогда, когда их написание требует несоразмерно больших усилий. Тесты для сложных мест + тесты при исправлении багов — такой подход уже дает процентов 90 всех плюсов, которые можно получить от тестирования, при небольших усилиях.
> Мне поэтому TDD в чистом виде кажется хуже
TDD — догма, и никто не говорит, что тесты надо писать для всего кода. Для тривиального кода достаточно «физического» покрытия тестами, которое обычно закрывается другими тестами.
Основной критерий «логического» покрытия у меня — это уверенность/страх.
Я вот перечитал 2 раза, а понять не могу: либо вы неправильно свои 90% посчитали, либо я дурак. Если тестами покрыто 100% кода, то это может значит только то, что тестами покрыто 100% кода. То есть весь код. Целиком. Если хоть где-то хоть что-то вдруг станет работать не так — я узнаю сразу. Как можно покрыть тестами 90% кода так, чтобы они мало что тестировали?

Буквально сегодня меня товарищ спрашивал, как поступить. Он пилит redmine и наткнулся на непонятку (разработчики идиоты, проще говоря). Если бы это был целиком мой код — я мог бы сказать: напиши вот это. А так — кто знает, что повлечет за собой банальная замена маленького метода в большой такой системе? А если есть тесты, то метод меняется, тесты запускаются. Если тесты проходят — все отлично. Не проходят — уже нужно думать, либо метод еще пилить дальше, либо менять еще и то место, где тест не прошел.
А без тестов как в такой ситуации? Менять и сидеть все-все действия прощелкивать? Я день на это потрачу, а то и больше.
Я вот перечитал 2 раза, а понять не могу: либо вы неправильно свои 90% посчитали, либо я дурак. Если тестами покрыто 100% кода, то это может значит только то, что тестами покрыто 100% кода. То есть весь код. Целиком. Если хоть где-то хоть что-то вдруг станет работать не так — я узнаю сразу.

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

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

Такое условие не может ничего гарантировать кроме того, что строка при каких-то условиях (в каком-то состоянии системы и окружения) выполнится без ошибки. Это не может гарантировать того, что код без ошибок. И 100% покрытие этой гарантии не даст. Гарантию даст только математическое доказательство правильности алгоритмов (всех!) и полное знание состояния окружения, но такая гарантия никому не нужна, тесты пишут не ради гарантий, а ради удобства рефакторинга. Эти 100% — точно такая же цифра, как 90%, и как 50%: принципиально других преимуществ 100% по сравнению с 90% или 50% не дает.

Оно даст только гарантию того, что при каком-то состоянии системы и окружения код выполняется без ошибок (не «упадет») и при этом удовлетворяются некоторые условия (совсем не обязательно связанные с выполненными строками). Покрытие само по себе не гарантирует ни множество состояний системы/окружения, ни того, что удовлетворяются те условия, которые важны для выполненных строк (из которых эти проценты формируются).

Оно, к тому же, в моем случае не проверяет все варианты работы стороннего кода (и «стыковку» стороннего кода с нашим). Вот ниже пример с делением. По сути, деление можно рассматривать как вызов библиотечной функции. Некоторая комбинация входных параметров — ошибочна. Код операции деления не попадает в coverage, т.к. он зашит в микропроцессор, и мы не видим, что ветка, связанная с обработкой 0 в знаменателе, не оттестирована.

Еще пример: тестируем веб-приложение. Пишем бота, который обходит все страницы подряд и постит формы с данными. Проверяет, что в ответ приходит статус 200. Имеем от этого test coverage, близкий к 100%. Это полезные тесты, их цель — смотреть, не поломалось ли чего глобально. Но они не очень помогут в отладке и рефакторинге какого-то конкретного места, для этого полезнее написать юнит-тест, которые будет проверять API на программном уровне, а не на уровне интерфейса пользователя. Юнит-тест (вероятно, более важный и полезный в конкретной ситуации) вполне может не быть написан, если код учтен в coverage, а программист, привыкший следить за code coverage и принимать на его основе решения, видит это.

Как можно покрыть тестами 90% кода так, чтобы они мало что тестировали?

Я оказался на это способен) Рецепт примерно такой: пишем тесты, максимально увеличивающие code coverage. Это стремление приводит к тому, что проверять условия становится трудно, и трудно понять, какие именно условия стоит проверять. Количество тех, которые имеется возможность проверить, растет гораздо быстрее количества тех, которые есть смысл проверять. В итоге многие условия остаются непроверенными, теряются среди других, работа некоторых кусков кода проверяется однообразно по многу раз, работа других не проверяет совсем.

Не стоит, короче говоря, интеграционными тестами заменять юнит-тесты.

Тут можно бы подумать, что если писать хорошие тесты, то при code coverage 100% все будет здорово. И действительно, это так. Другое дело что все будет здорово и задолго до того, как покрытие будет 100%, если писать хорошие тесты. Нам тесты нужны ведь не только и не столько для самоуспокоения, а требуются они для практических целей — для удобства проведения рефакторинга, и это удобство будет обеспечено гораздо раньше.
ого-го ответище:)
я говорил в своем комментарии именно (и только!) про юнит-тесты, то есть тестирование моделей (в рельсах, в PHP-MVC фреймворках вроде все так же). Если правильно отделять логику от всего остального, то большинство «умного» кода будет именно в модели (у меня там было). Эту модель и тестировать. А на долю контроллера останется только, по сути, пара присвоений и вызовов методов из модели)

То есть то же добавления в друзья: нужно попробовать добавить в друзья себя, кого-то второй раз, удалить два раза. сделать все те же вещи, которые я лично проверяю в рельсо-консоли при разработке логики (то есть я уже писал весь этот код тестов, пока тестировал модель!). Всякие там user.friends.size проверять, добавляется ли друг или нет. Вот такие вещи обязаны быть в приложении. На 100%. Я щетаю)
> Я привык покрывать тестами до 98% кода. Тогда я могу быть уверен что мой код не падает никогда. Очень и

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

public double div(double a, double b)
{
return a / b;
}

a=4, b=2 тест покрыт 100%. Да можно сказать что надо проверять b=0. Что это не недостаток TDD а недостаток тестов. И даже если всегда все по дефолту все проверять на 0 — что будет если return a/(b-c-5) или сложнее? 100% покрытие кода — а гарантий что не попадется набор параметров при котором b-c-5=0 никаких.
И согласно TDD при написании теста мы НЕ ДОЛЖНЫ ПРЕДПОЛАГАТЬ как метод будет реализован — а без таких предположений 100% качество кода можно обеспечить только если знаешь о реализации и знаешь граничных частных случаях.

Я сам люблю TDD и применяю его практически постоянно — но как и любую методику о которой только что узнали — ставить его во главу угла и применять его фанатично и слепо доверять не стоит.
«Джерард Мессарош»
Массаракш!
И чем PHPUnit лучше/хуже SimpleTest?
Ну в нем намного больше заготовлено ассертов это то, что я с набегу увидел. :)
Это уже стандарт де-факто для PHP. Я давно не смотрел на SimpleTest, но последний релиз был в 2008г. PHPUnit растет в сторону «промышленной» разработки. Ну и он намного глубже.
Да я и не вижу смысла обсуждать разницу, эти ребята в разных весовых категориях.
Я думаю, что понимаю автора, согласен, что тесты — хорошо, сам писал и пишу их, но есть ощущение, что его взгляд ограничен опытом динамических языков. Поэтому добавлю небольшое замечание, просто как намёк на то, что картина шире, чем может показаться…

Языки с хорошей (развитой и удобной) статической типизацией требуют гораздо меньше тестов. Там программа пишется так, чтобы тестированием занимался компилятор, а результат — такой же приятный и так же придаёт уверенности в коде, как и рукописные тесты.

Проверено на собственном опыте.
НЛО прилетело и опубликовало эту надпись здесь
А о каком языке конкретно идет речь?

По моему опыту просто наоборот было, статическая типизация в классике вроде C, C++, (чур меня) Object Pascal давала программисту очень мало (усложняя при этом реализацию различных паттернов проектирования), да и ошибки на неправильное соответствие типов довольно редко встречались.

Еще можно путать динамическую типизацию со слабой типизацией. Динамическая — значит, тип определяется во время выполнения (при присвоении переменной значения), а слабая — это значит, что неправильная операция над переменными 2х разных типов не вызовет ошибки, как-то так, и может быть одно без другого.
В моём случае речь идёт о Scala (traits, function types, variance, higher order type polymorphism, Option и т.д.). Все детали расписывать не буду, при желании найти несложно.

Могу сказать только, что, даже после Java, у меня первое время уходило довольно много времени на дизайн интерфейсов. За то потом не раз приятно удивлялся тому, что если что-то сложное начало компилироваться, оно с первого раза работает правильно.
Более правильные ссылки:

Кент Бек. Экстремальное программирование: разработка через тестирование.
Кент Бек. Экстремальное программирование
Джерард Месарош / Gerard Meszaros. Шаблоны тестирования xUnit: рефакторинг кода тестов (пока недоступна), на англ: Gerard Meszaros xUnit Test Patterns: Refactoring Test Code
Главная ценность этой книги это предлагаемый продуманный язык шаблонов и четко организованная терминология в целом для данной предметной области.
Джерард Мессарош «Шаблоны тестирования xUNIT»
Автору нужно сделать рефакторинг книги и убрать добрую половину местоимений. Не знаю как в переводе, но в оригинале слово We чуть ли не в каждом параграфе. Для технической литературы это явный перебов. Все таки, имхо, она ближе к научной статье по стилю должна быть.
Ну а так стоит почитать :)

PHPUnit
Документация не обновлялась уже с год наверное. Датабазы мне пришлось по частям вытаскивать с разных форумов, блогов и презентаций. Довольно часто создателей же теста.
Хотя конечно без него было бы гораздо хуже :)
Кстати, английская версия книги Мессароша имеется в олнайн-версии: xunitpatterns.com/
А я псих полностью вам противоположный. Я НЕ делаю тестов. Но я тоже постоянно делаю рефакторинг. Согласен, вычислять ошибки сложнее, зато я лучше учусь на своих ошибках, и через некоторое время перестаю совершать… в итоге, быстрее пишу код, быстрее рефакторю, и баги тоже практически исчезают.

Конечно, есть одно НО — я работаю на одном и том же языке, и в одной связке технологий… Так что, если придётся перейти на что-то новое, мне, наверно, надо будет учиться писать тесты, чтобы писать так же качественно, как сейчас…

Так что вам респект за статью =)
Попробуйте сейчас — это интересно, ничего не потеряете, и обязательно чему-нибудь научитесь.
для этого придётся переписывать весь свой фреймворк… так что как-нибудь в следующий раз =)
Неужели, он настолько ужасен, что его нельзя протестировать? В любом случае, тесты можно писать для нового кода и аккуратно рефакторить существующий код.
нельзя протестировать !=> ужасен
не имеет смысла тестировать !=> нельзя протестировать
>> Согласен, вычислять ошибки сложнее, зато я лучше учусь на своих ошибках, и через некоторое время перестаю совершать…

В какой то момент кол-во сущностей и связей в системе становится настолько большим что система перестает умещаться в голове. Не совершить ошибку в такой ситуации практически невозможно.
И еще, судя по всему Вы работаете в одиночку. В командах на больших системах такой подход просто не работает.
Я вот что хочу добавить к списку рекомендованной литературы. Когда я читаю статьи по TDD, у меня складывается впечатление, что все всё пишут с нуля. И примеры в большинстве книжек по TDD — покрывание тестами сферических коней в вакууме. Попробуйте написать тест для кода, которому 10 лет, в котором всё зависит от всего и не пилится на запчасти. Если вы работаете со старым кодом — почитайте Working Effectively with Legacy Code, весьма полезная книжка.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории