Pull to refresh

Comments 103

добавил бы еще 1 вариант:
— да, но только там, где это необходимо.

Если пояснить, то нет смысла писать тесты на простейшую логику трансляции одного объекта в другой, нет смысла писать тесты на простые геттеры и сеттеры в объектах.

На 100 процентов обязана быть покрыта доменная логика.

А почему, кстати, этот опрос в разделе .NET, а не в разделе TDD?
Блогом я ошибся, да. Причем я сам думал, что выбрал TDD, а оказалось… :)

Я в первых вариантах потому и написал «покрытие стремится к 100%» (а не равно 100%). К слову, геттеры-сеттеры в классическом ТДД у вас тоже будут «покрыты». На них не будет отдельных тестов, но они покроются другими.

Если вещей, которые «не надо тестировать» у вас больше чем просто геттеры/сеттеры, то можно выбрать вариант 3, который подразумевает, что есть некоторые вещи, которые в компании «писать по TDD не принято», по причинам того, что это не окупается. Хорошей иллюстрацией к третьему пункту была бы «классическая» картинка:

C TDD как с зарядкой по утрам. Пробовал, понравилось, но ни как не могу заставить себя делать это каждый день.
Я, к сожалению, не очень в курсе деталей BDD.
Как мне кажется, наиболее близкий к BDD вариант в опросе — второй. Поясните, пожалуйста, различия?
TDD крутится вокруг тестов, BDD — вокруг поведения, если кратко.
апологеты TDD, конечно, поспорят, вокруг чего крутится TDD :)
английская вики пишет, что BDD включает в себя TDD и расширяет его «тестами на человеческом языке» — по сути некоторым аналогом «среднеинтеграционных» тестов (как я понимаю).
Собственно, плюсы интеграционных тестов как раз в том, что они «более понятны человеку», потому что тестируют не мелкие абстракции, а какие-то слегка более крупные композиции объектов, соответственно, операции с ними (при правильном дизайне) выглядят вполне человечно.

Я более-менее правильно понял идею «поведенческих тестов»?
UFO just landed and posted this here
Вы мешаете BDD, функциональные и спецификационные тесты в одну кучу.

Так же как есть юнит и функциональное тестирование, есть Specificational BDD (http://rspec.info/, www.phpspec.net/) и Scenario BDD (http://cukes.info/, behat.org/).

Спецификационное BDD пришло на замену юнит-тестов и решения проблем технического уровня (описания внутренних спецификаций), а то, о чем говорите вы — Scenario BDD — для замены функциональных тестов и решения проблем бизнесс-уровня (описания поведения приложения).
UFO just landed and posted this here
BDD — это правильный TDD. Понимая и используя TDD правильно, вы уже делаете BDD.
Столько проголосовавших, и лишь мой +1 голос. Давайте выведем на главную, я что зря за попкорном сбегал?
Какой термоядерный звиздец, я просто не могу в это поверить — 50% людей не используют автоматизированные тесты! Не могли бы проголосовавшие за последний пункт немного рассказать, почему это происходит и как они дошли до такой жизни.
К нам идут, в основном, после института, практически не приходят опытные программисты, на обучение не посылают, поэтому используем то, что сами осилили по книжкам да по статьям на Хабре.
Ну тем более — новым программистам не надо ломать привычки плохо программировать, их можно учить писать сразу правильно!
А у нас и учить-то некому. Поэтому «от случая к случаю», и задумываемся о большем.
где ж вы такие живете? может приехать к вам, пару семинаров провести? ;)
Не поверите, практически в центре Москвы. Только начальники наши не разрешат. У нас очень консервативное госпредприятие.
Если хотите расти как специалист — меняйте работу срочно. Судя по вашим словам, на этой вы будете лишь деградировать.
Спасибо. Я это уже почти сделал.
Почти деградировали? :-D
зовите, приедем ;)
UFO just landed and posted this here
Дело не в критичности ошибок для пользователей. Дело в самоощущении разработчиков в рамках проекта. Мне было бы крайне стремно править ошибку в проекте с полным отсутствием тестов. И даже после исправления бага я бы все-равно продолжал себя чувствовать обкакавшимся, потому что нет никаких гарантий, что моя правка ничего не поломала. Вот такие ощущения и приводят к депрессиям, постоянным стрессам и нежелению программистов развивать проект. Каждый программист на таком проекте либо находится в постоянном стрессе либо пофигистичный идиот. Просто задумайтесь!
К 50% можно смело прибавлять 20% «Пишем тесты от случая к случаю». Так что можно считать что 70% не пишут тесты. Это еще ничего, я думал что тесты пишут от силы 10%.
Так на Хабре еще и выборка относительно продвинутая. Так что на самом деле ситуация еще печальнее
Зато при приеме на работу «пользуетесь ли вы тестами» спрашивают почти 100% ))
Возможно потому что 50% софта нахрен никому не сдались и пишутся исключительно с целью отпилить бабла?
По статистике 87.964 % статистических данных берется с потолка.
У нас тесты полностью покрывают весь проект, благо их предоставляет заказчик.
Это, видимо, acceptance-тесты? Готов поспорить, что acceptance-тесты не могут покрывать весь проект
Ну вот смотрите. У вас есть такой метод:

public void Function1()
{
if (lablabla)
Function2();
else
Function3();
}

В Function1 две независимые ветки исполнения. Теперь представим, что Function2 и Function3 имеют ту же структуру. Получаем уже 4 независимые ветки исполнения. В реальных системах глубина вызова таких функций не менее 10. А это уже 2^10 = 1024 независимых ветки исполнения. И это только для одной публичной функции. Покрыть такое количество кейсов acceptance-тестами на Function1 невозможно.
Использую юнит-тесты (gtest) и автоматизированные тесты (с помощью lua) в проектах, которые разрабатываю/поддерживаю.
Братцы, используйте TDD. Даже если оно вам не нужно чтобы тестировать (странно:), то хоть научитесь писать правильный ООП, да и код в целом. TDD как тетрадка в линейку — научишься писать ровно и красиво, а потом подумаешь продолжать или нет. Начните с простого класса и не бойтесь переучиваться.
Вы действительно считаете, что TDD — лучший способ проникнуться SOLID'ом?
Не знаю насчет лучший ли, я полагал что (из практических) он единственный. А что еще есть?
Рассуждаю исходя из личного опыта. Я просто ознакомился с этими принципами, осознал, что действительно когда я их раньше нарушал, было плохо. И в итоге стал им следовать. Без какого-либо TDD.

Да, TDD подталкивает к использованию SOLID'а, но по-моему SOLID не такая уж сложная штука, чтобы ее нельзя было осилить без TDD.
Я бы сказал, что SOLID'ом можно проникнуться если вообще писать тесты — хотя бы пост-фактум. Ибо плохой дизайн очень сложно тестировать.
Согласен, для новичков «чистый ТДД», имхо, почти идеален.

Не очень люблю примеры с «катами программиста», но здесь он будет очень к месту. Следуя ТДД легко понять и плюсы тестирования, и обучится Солиду. И уже потом адаптировать ТДД под себя, убирая моменты, на которые тратится черезчур много времени (и которые не окупаются в дальнейшем).
ООП — не единственный метод писать программы.
Метод: совокупность теоретических принципов и практических приёмов для осуществления чего-либо.

Синонимы: способ, система, подход
У нас ребята пишут юнит-тесты, но у меня в основном рендер отображения на Canvas. Такое особо тестами не покроешь =(
Использую BDD, до сих пор ломает — не привыкну никак.
Примерно 40% времени работы над проектом ушло на писание тестов, пришлось изучить Scapy, D-Bus и прочие забавные штуки, зато теперь не заморачиваемся вопросом, кто залил в SVN нерабочий код или где что-то поломалось.
Имхо, писание тестов таки экономит время разработки, даже с учетом дополнительных затрат. Мне жаль нижних 52%
Перевод там не очень хорош: словосочетание «в итоге» отсутствует в первоисточнике — а значит, является отсебятиною переводчика.
Надоела уже эта приставка «хабра»… Неужели мало слов в русском языке.
И хватит уже распространять вот эту байку: «Есть интеграционные тесты, а тесты на отдельные функции/классы в изоляции почти не пишем». Почти ВСЕ классы, которые бы ни тестировали зависят от других классов. Поэтому в ходу байка юнит-тесты = тесты на моках.
Юнит тест — это тест «минимально тестируемого» участка (функции/метода) в изоляции. То есть в тесте только один класс с реальной реализацией, всё остальное «симуляция».

Под интеграционным тестом я понимаю тест, в котором более чем один класс «реален». Естественно, вся эта система из 2-3-5 или более классов имеет в свою очередь внешние зависимости и они тоже стабятся.

Крайний случай интеграционного теста — это acceptance тест — в нём реальна вся система и стабов нет.
Вот моя классификация:
1. Юнит-тестирование: мы тестируем один юнит. Юнит — это не что, находящееся в нашей области ответственности и под нашим контролем. Юнит может быть размером от одного класса до веб-сервиса.
2. Интеграционное тестирование. Тестирование юнита вместе с ВНЕШНИМИ зависимостями.

Таким образом, тестирование кода, который лезет в базу, являющуюся дампом продакшен базы — это интеграционное тестирование. Если мы поднимаем базу у себя в памяти и заполянем своим же кодом, то эта зависимость становится внутреней. По классификации Фаулера — это fake-object.
И тестирование на fake-базе — это юнит-тестирование.
Это мое мнение. Фаулер называет такой тест mini-integrational, но это уже жесть какая-то ИМХО.
Терминология тут не очень устоявшаяся, поэтому и возникают недопонимания. Поэтому и «байки» из-за того, что разные люди под одним и тем же термином подразумевают разные вещи.

«A unit is the smallest testable part of an application.» — это из вики. То есть веб-сервис целиком это в классическом понимании таки не юнит.

Ну и понятие «внешней зависимости» оно тоже слегка неоднозначное.

Для вас интеграционный тест — это когда вся система целиком и полностью реальна, получается. А это тоже не всегда так.
>A unit is the smallest testable part of an application

Ну ок, у меня веб-сервис как раз smallest testable part, потому как если тесты сформированы в терминах http-вызовов, то менее тестируемой части у меня нет.
Если у вас веб-сервис — smallest testable part, то ваша архитектура — говно
То, что для вас юнит, обычно называют интеграционным тестом. А то, что для вас интеграционный тест, обычно называют acceptance-тестом.
Видите ли я не вижу принципиальной разницы между классом-сервисом, который внутри зависит от in-memory базы от класса-стека, который внутри использует скажем, класс-Integer для подсчет элементов с точки зрения тестов. Поэтому тест и для первого и для второго я называю юнит-тестом.
А я не вижу принципиальной разницы между production-базой и in-memory базой. И я в упор не понимаю, как использование in-memory базы данных вместо production делает интеграционный тест юнит тестом.
Базу в памяти создает и наполняет наше приложение. Причем перед каждым запуском. Фактически база является некой производной от кода. Состояние продакшен базы нам неизвестно и неподконтрольно, поэтому результат теста зависит от ее состояния.
Я все-таки имел в виду production-like базу данных, а не прям production базу (кто же тестирует на реальной production-базе?).

Допустим, ваше приложение использует MySQL в качестве базы данных. У вас есть два похожих теста. Единственное их отличие заключается в том, что первый тест будет работать со специальной тестовой базой MySQL, а другой — с in-memory базой данных SqlLite. Правильно ли я понимаю, что согласно вашей терминологии первый тест будет интеграционным, а второй — юнит?
Если база MySQL стоит на том же хосте, ее схема перед каждым тестом создается с нуля, то тест так же назову юнит-тестом. Если на другом хосте — получаем зависимость от сети (сеть может лагать, сеть может лежать) — интеграционные тест.
Ну это уже просто смешно. Как будто в случае одного хоста никаких зависимостей нет. Их миллиард!
Ну тут по ситуации смотреть. Если они критические, то нужно уже либо на in-memory переходить, либо не запускать этот тест при каждой сборке проекта, а запускать как интеграционный через сервер CI, например.
Похоже, у вас на все есть свое мнение, и вы не утруждаете себя тем, чтобы синхронизировать это мнение с сообществом (а поставить мне минус в карму при этом, видимо, не поленились). Позвольте откланяться.
Во-первых минус никому не ставил :) Во-вторых готов свою классификацию и далее отстаивать.
Я вас совсем не понял.

Да, все классы зависят от других классов. Да, юнит-тестирование предполагает тестирование в изоляции. Как следствии, юнит-тесты — тесты на моках и стабах. Почему вдруг это стало байкой?
Вы не под веществами? :)

Если вам показалось, что я некорректно использовал термины мок и стаб, постарайтесь объяснить это, а не кидаться ссылкой (пусть даже такой ссылкой).

Ваш изначальный комментарий все еще вызывает у меня недоумение.
А ссылку-то зачем кидали?
Это контраргумент к тому, что юнит-тесты — это только только тесты на моках.
Процитируйте, пожалуйста, где в этой статье об этом говорится
«The classical TDD style is to use real objects if possible and a double if it's awkward to use the real thing. So a classical TDDer would use a real warehouse and a double for the mail service. The kind of double doesn't really matter that much.

A mockist TDD practitioner, however, will always use a mock for any object with interesting behavior. In this case for both the warehouse and the mail service.»

Т.е. он констатирует, что есть два сложившихся подхода к написанию тестов. И моки — не единственный подход.
Фаулер здесь говорит о двух подходах к TDD: один подход предполагает использование реальных реализаций там, где где можно обойтись без стабов/моков, а другой подход предполагает стаббинг/моккинг всего и вся. Так вот в первом подходе мы получаем слегка интеграционные тесты, а во втором — классические юнит-тесты.

И мы вновь приходим к тому, что юнит-тесты — это тесты на стабах и моках. И это не байка, это действительность.
А где написано, что мы получаем слегка интеграционные тесты? И что значит слегка?
Ненене. Прежде чем спрашивать, вы сперва ответьте. Вы привели эту ссылку, в качестве доказательства своей фразы про юнит-тесты. Но при этом Фаулер ничего не говорит о юнит-тестах, он говорит о разных подходах к TDD
Как раз на основании той фразы, что вы мне наверное хотели написать: In essence classic xunit tests are not just unit tests, but also mini-integration tests.

Здесь я подразумеваю, что mini-integration != integration
То есть в статье, которую вы использовали в качестве пруф-линка, вы нашли подтверждение моим словам (кстати, я назвал тесты мини-интеграционными отсебя, и был приятно удивлен, что Фаулер использует ту же терминологию), и вместо того, чтобы признать мою правоту, решили подменить понятия. Конструктивно — ничего не скажешь.
Если вы не хотите признавать, что есть проблема в терминологии, не признавайте. Но лично я

а) Не хочу тестировать все моками для TDD
б) Не хочу признавать, что я делаю TDD на интеграционных тестах
в) mini-integration — это уже что-то странное. Хотя возможно имеет право на жизнь. Тут еще подсказывают, что такие тесты по сути спеки BDD.
Собственно я не написал, но весь сыр-бор классификации в том, что только юнит-тесты применимы для TDD, так как запускать integrational тесты при каждой сборке или изменении в коде — идиотия. Может кто-то и делает TDD на интеграционных тестах, но это уже тема для другого спора :)

В общем годится и такая классификация: если можно разрешить запускать тест при сборке проекта и после каждого изменения — это юнит-тест. Если требуется спец-окружение и/или тест долгий — это интеграционный тест, и его запускать можно только из спецокружений вроде CI сервера.
Моки завязаны на реализации. Тесты тоже получаются завязаные на реализации. От сюда куча неподдерживаемых, малополезных тестов. Отсюда куча криков — тесты зло, с тестами только хуже, отнимают время и т.д. Все юнит-тесты зло? Или их все таки неправильно пишут?

Надеюсь я привел достаточно контраргументов, что бы вы задумались, перестали повторять неправильные вещи, передаваемые из уст в уста и познали тестирование. Удачи.
Моей голове уже давно нарисовался некоторый когнитивный диссонанс:
1. Судя по подобным опросам, 50% людей вообще на знают, что такое тесты, и еще 30% «что-то где-то слышали и пару раз пробовали». И того минимум 80% людей на TDD плюют с высокой колокольни.
2. И всё-таки, процент удачно завершенных проектов и работающего ПО заметно выше 20%. Т.е. факт отсутствия TDD мало кому мешает.

Есть какая-то убедительная статистика, что вот мол TDD помогло в таком-то проекте найти на 15% больше багов или там, что удалось быстрее закончить проект? Или только голые рассуждения «нам нравиться», «это правильно» и «такая сейчас мода»?

Мне вот кажется верным разумный компромисс — тесты на ключевые компоненты, сложные места, на найденные баги и т.д. Но 90-100% покрытия кода тестами? Ну разве что для кардиостимуляторов, автопилотов и ПО атомных электростанций.
Есть некая статистика, что TDD не помогает :) Но это, конечно, именно о ТДД, а не о тестах в принципе.

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

Крайности плохи, как и везде. По поводу оптимальности компромисса согласен полностью.
в жизненном цикле программы можно условно выделить два периода: разработки и развития. в период разработки, когда все требования и код запросто помещаются в голове, эффект от TDD на фоне других методик не очень заметен. просто держите себя в руках, следуя классическим методикам программирования — ООД, паттерны, и что там ещё проходили в школе — и вы обязательно достигнете желаемого результата. преимущества от TDD появляются на втором этапе, когда, условно говоря, часть предыдущих разработчиков уже уволилась, а у оставшихся нюансы требований стёрлись из памяти. в отличие от людей, тесты не бросают работу и ни чего не забывают.

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

мы начали использовать TDD в тот момент, когда развитие одного из наших сервисов пошло вразнос — когда каждое изменение стало откликаться появлением десятка багов в разных местах — стоимость модификаций перешла все разумные пределы и проект оказался на грани закрытия. начали с очевидного — стали описывать тестами требования высокого уровня (т.е. с приёмочных и интеграционных тестов). позже пришло понимание зачем нужно юнит-тестирование. к настоящему времени в проекте сменились два состава разработчиков. месяц назад мы закончили рефакторинг, затронувший почти половину кода. думаю, что без тестов это было бы сделать практически не реально, поскольку все требования наизусть уже не помнит ни кто.
С точки зрения относительно крупного рефакторинга, как мне кажется, интеграционные и приемочные тесты намного полезнее мелких юнит-ТДД-тестов. В процессе рефакторинга важны тесты, которые не надо будет менять/переписывать, которые останутся после рефакторинга нетронутыми. И это обычно таки интеграционные/приемочные тесты (потому что при рефакторинге происходит перераспределение ответственностей между классами, а то и вовсе замена некоторых участков целиком).

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

Но в контексте результатов опроса, спор о том «какие же тесты лучше» можно и не затевать, а остановиться на утверждении, что «тесты нужны» :) А то посмотрят люди, что в стенах сторонников тестов «согласья нет», и не задумаются о тестах вообще :)
80% людей плюют на здоровое питание, и доживают до 60 лет.
Мне почему-то такая аналогия пришла в голову Ж)
Я сам не апологет TDD, но может быть это ПО, которое завершено и работает, было бы завершено быстрее или работало лучше, я не знаю.
А может быть, и нет…
Для меня близкое к 100% покрытие кода юнит-тестами, означает, прежде всего, что я могу без лишнего страха «всё сломать» изменить часть функциональности или реализацию определенной функциональности. Да, этого можно добиться и без тестов, но с тестами субъективно проще. Во-вторых, написание сначала тестов, а затем кода позволяет сконцентрироваться на решении конкретной задачи не вводя лишних сущностей «на всякий случай», а если уж кажется, что это необходимо, то для этого пишу новый тест, когда текущий уже выполняется.

В общем не сказал бы, что ТДД помогает найти баги (если юзкейс пришел в голову, то либо покрою его тестами, либо проверю ручками, а если не пришёл, то не покрою и не проверю) или сильно сокращает сроки разработки (возможно даже увеличивает), но помогает организовать работу и сильно упрощает поддержку и развитие после релиза. Несомненно, этого можно добиться и другими методиками, но лично у мня они не прижились, тупо забиваю на них, когда нужно «здесь и сейчас», а так только одно простое правило «сначала тест, потом минимальный код, обеспечивающий его успешное выполнение» и всё. И следование этому правилу даёт и приличную архитектуру (иногда с оверхидом, но всегда понятную), и удобство рефакторинга, и документацию, и управляемость проектом.
Код, покрытый юнит-тестами — качественный код.
Во-первых, как уже везде отметили, при рефакторинге ничего не сломается.
Во-вторых, написание «тестабельного» кода автоматически исключает огромные простыни методов и if'ы с десятком веток, потому что такой код сложно тестировать, а если сложно тестировать — нужно рефакторить, в результате получая более качественный код.

В итоге это дает более высокое качество кода и хорошую поддерживаемость.

факт отсутствия TDD мало кому мешает.


Ну да, действительно, сложно подсчитать человеко-века, потраченные на фикс ошибки, найденной тестировщиком на этапе UAT вместо упавшего билда на CI-сервере.
В моём понимании значительный плюс TDD — невозможность сломать и не заметить этого при рефакторинге.
Чаще всего проекты небольшие (< 100k LOC), да и стараемся правильно изначально проектировать, чтобы не напороться через пару лет на то, что нужно всё глобально рефакторить.
В общем — TDD редкость у меня :)
Справедливо (а для первоначального теста существует пошаговый отладчик).
Правда, жизнь не всегда укладывается в схему идеального с самого начала проектирования.
Мы стараемся подходить к unit тестам как одному из инструментов который может сохранить множество нервных клеток в будущем. Обычно начальный процесс разработки происходит так:
1. Детально проектируется вся архитектура конечного продукта
2. Создаются интерфейсы и документируются (java), по сути скаффолдинг.
3. Пишутся тесты для всех публичных методов, до того как сами методы были реализованы.
4. Пишется бизнес логика продукта.
5. Пишутся интеграционные тесты.

Тем самым гарантируется что в ходе разработки продукт(с точки зрения public api) будет всегда вести себя так, как это было заложено в изначальной архитектуре.
А доменную модель создаете? Или придерживаетесь bottom-up подхода от юз-кейсов?
Думаю особой спецификой отличается написание научного кода.
Тут совсем не нужно полное покрытие теста. Поскольку 90% кода пишется временно, для проверки тех или иных идей, алгоритмов. И тесты играют роль чек пойнтов в некоторых частях алгоритма. Т.е. меня обычно мало волнует, если моя программа упадет при неком хитром параметре командной строки, зато важно знать, что подав на вход некой функции рандомную и единичную матрицы, я получу на выходе первую. И что это всегда верно.
Такие чекпоинты занимают от силы процентов 10% от полного покрытия.
Когда алгоритм закончен его можно вылизывать до посинения. Но это уже в принципе может делать и другая команда.
Sign up to leave a comment.

Articles

Change theme settings