Comments 71
Для меня тесты скорее не лакмусовая бумажка, а форма для литья кода, даже если тесты только в голове. То есть я уже думаю по TDD, даже если собственно тесты не пишу. Думаю не «как я это буду тестировать?», а «какой тест должен пройти код и каким код для этого должен быть?»
Для меня тесты — это то, что колит меня в жопу при не аккуратных и необдуманных телодвижениях и не дает сломать код. И это хорошо.
Рекомендую при наличии такой точки зрения посмотреть в сторону дымовых тестов (например, phpt). В некоторых случаях (особенно для небольших, но емких библиотек, которые несовместимым образом меняются редко) они пишутся в десятки раз проще.
Я Java девелопер. В любом случае с помощью библиотек семейства xUnit + Mocking фреймворка тесты пишутся на ура.
А как правильно тестируются такие подсистемы приложения как, к примеру, работа с Open GL?
OpenGL — это view. Его всегда сложно автоматически тестировать, т.к. он выдает «очень человеческие» данные.
И вдогонку вопрос к мэтрам ТДД — как правильно тестировать работу с 3rd party сервисами?
Это уже будут функциональные тесты, если я правильно понял вопрос.
Если тестировать и результат сторонних сервисов, то функциональные (полностью приложение) или интеграционные (только связку). Если мокать вызовы сторонних систем, то вполне себе юнит.
Тогда наверное всё таки интеграционные автору того комментария нужны.
Насколько я понял, он как раз мокать ничего не хочет.
Изоляцией от внешних систем — моки и стабы. Тестируем, что функции/методы внешней системы вызываются с нужными параметрами, считая что в ней багов нет и раз передали нужные значения, то получим нужное побочное действие, даже если проконтролировать его не можем.
Как тестировать, например, сшивку jpeg-ов в панораму? Если подготовить тестовый пример и сравнивать результат с «авторским», то при изменении (улучшении) алгоритма результат изменится (станет лучше), но не перестанет быть правильным. А тестирующая функция будет писать ошибку. Внешней системы тут как бы нет, изолироваться не от чего. Как вообще тестируют эвристики?
А при отладке/настройке алгоритма? Вообще отключать, до получения очередной стабильной версии?
Если на результат сшивки влияет только алгоритм, то я не вижу смысла в тесте в данном случае. Алгоритм пишется, отлаживается, настраивается и всё. Тест всегда будет вам говорить что всё хорошо, какой в нём смысл?
При отладке возможно и отключать тест т.к. при отладке всё равно глазами смотреть, а потом тест будут регрессионным — при рефакторинге будет гарантировать что ничего не поломалось
Можно тестировать минимум тот факт что панорама сшилась, что её размер больше (или кратен) размера отдельных jpeg-ов. Тестировать качество цветопередачи и отсутствие артефактов на картинке тут довольно сложно.
Если есть желаемый результат для двух образцов, то можно и автоматически. Хотя бы для примитивных случаев типа два пикселя склеить.
Как приучить себя писать тесты?

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

Пример: вы пишите онлайн игру с GUI (например шахматы). Сначала запрограммируйте общие правила игры: как ходят фигуры, возможность хода, детект мата и шаха. Соответственно тесты для них. Затем напишите игровой движок, который представляет простейший конечный автомат. В тестах к нему забейте различные сценарии игры (партии). Далее по той же схеме нужно написать network layer для передачи ходов с тестами к нему. Затем GUI. Тестами для GUI может быть автоматическое проигрывание партии по заданному сценарию.
Представьте что вы разрабатываете ПО для медицинских учреждений и от вашей малейшей ошибки будет зависеть жизнь человека. Тогда вы будете думать о том как бы протестировать тот код, который вы написали.
1. Тесты экономят нервы и время на отладке. Если почти весь код покрыт хорошими тестами, не страшно писать новый код и изменять старый, есть уверенность, что это будет несложно, и обычно можно будет быстро локализовать ошибку.
2. Тесты в продуктах, которые я использую, но не разрабатываю, позволяют понять, как правильно использовать инструмент (т.е. как разработчик задумывал взаимодействие со своей системой).
Свойства 2, 3 и 4 имеют и дымовые тесты тоже (зачастую в гораздо больших масштабах и с большей простотой, кстати). Пример дымовых тестов — phpt.

С вот одно свойство юнит-тестов забыли написать. Бывают такие задачи, которые без юнит-тестов решить на 2-3 порядка сложнее (т.е. на практике — невозможно, если только кодирует не упорная бессмертная мартышка), чем без юнит-тестов. Т.к. юнит-тест дает обратную связь, которую в таких задач получить больше не от кого (или получить которую на 2-3 порядка сложнее, чем без них). Пример такой задачи — сложный алгоритм биллинга на этапе, когда еще до конца не понятно, как он должен работать. Можно привести и другие примеры. Т.е. юнит-тест заменяет упорного, дотошного и никогда не устающего.тестировщика, который общается с вами и постоянно высказываетсвое мнение. Некоторые задачи в одиночку, без такого тестировщика, не решить (если только вы не Кармак).
UFO landed and left these words here
В проекте, где важна быстрота, юнит-тесты помогают не наступать на одни и те же грабли. После того как был выявлен какой-то баг, который можно воссоздать юнит-тестом, то такой тест пишется быстро (баг уже выявлен и его в любом случае устранять), это не сильно отнимает времени, но повышает качество кода. Ну и какие-то граничные условия и проверку внешних побочных эффектов я пишу сразу. В проекте, где очень много чистых функций и строгая типизация, общее количество тестов гораздо меньше.
У меня как-то наоборот — чистые функции или методы, изменяющие состояние объекта, стимулируют покрыть тестами все их нюансы, а вот когда надо проверять побочные эффекты (консоль, ФС, БД и т. п.), то как-то влом становится, максимум что делаю с энтузиазмом — застабиваю вызовы, мокировать не хочется…
Для меня тесты — бесполезная ботва, которая никак не помогает мне писать мой код.

Все что может ломаться — ломается и пишет в логи при запуске сервера.
Остальное ломается спустя сотни часов тестирования пользователями.
Другие способы у меня не работают — слишком сложная архитектура. И это не смотря на ежемесячную ревизию всего кода.

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

Сам факт того, что вы не можете что-то потестить в большинстве своем говорит о проблемах (а не о сложностях) архитектуры. Есть типы задач, которые протестировать сложнее, но они точно не находятся в back end-е, который по своей природе может быть разбит на отдельные тестируемые куски.
А вы не задумывались что хорошо структурированная и декомпозированная система не имеет тенденции к появлению ошибок в отдельных давно написанных и хорошо отлаженных кусках кода, которые выполняют свою изолированную четко поставленную задачу?
При переходе на другую платформу, или, например, при распараллеливании вычислений, даже хорошо отлаженный кусок может слегка испортиться. И хорошо бы перехватить это на ранней стадии.
При переносе старого кода на новую платформу, боюсь, одними тестами проблема не решится, но в целом вы правы, согласен.
Кстати, часто тесты находят ошибки не в своем коде а в чужом: в двжиках БД, новых версиях ОС, сторонних библиотек. Сам сталкивался не раз.
При сложной архитектуре юниты помогают «закрепить» то, что уже работает. Часто бывает так, что при добавлении нового функционала или рефакторинга отваливается то, что уже работало. Особенно при команде из нескольких человек.

> Все что может ломаться — ломается и пишет в логи при запуске сервера.
Это долгий и нудный способ: при каждом изменении запускать сервер и делать деплой (разработчики Java меня поймут). Если какой-то код невозможно запустить без сервера в stand-alone debug режиме, то нужно пересмотреть используемые средства

> Остальное ломается спустя сотни часов тестирования пользователями.
Не все, а только часть. Остальное годами фантомно глючит на продакшне без возможности отловить. Справедливости ради, отмечу, что подобное юнит тестами как правило тоже не ловится ;)

> У меня прямо комплексы насчет этих тестов.
Не стоит. Вы должны понимать, что TDD это такая модная теоретическая концепция, и соответственно:
— далеко не всегда практична
— со временем уйдет или перерастет во что-то другое
>>Не все, а только часть. Остальное годами фантомно глючит на продакшне без возможности отловить. Справедливости ради, отмечу, что подобное юнит тестами как правило тоже не ловится ;)

«Девушка наполовину беременна»? Они либо ловятся, либо не ловятся потому что, покрытие тестами недостаточно.
>> TDD это такая модная теоретическая концепция
Как хорошо что вы пока ещё так думаете. Это позволяет нам делать более качественные и удобнорасширяемые системы уже сегодня.

>> со временем уйдет или перерастет во что-то другое
Уйдет… как всё уходят и уходят паттерны, рефакторинг и т.п. Более того, в некоторые конторы луч света даже не проникал. Сайты-визитки можно и без тестов копипастить.

PS а в компании всё ещё приходят устраивться на должность сеньора люди, которые про всё это только слышали, но отлично за 10 лет научились конструировать пятиколесные велосипеды и God object классы. Ведь упрямые и нетестируемый God object будет жить вечно, не то чтобы новомободные тенденции

> «Девушка наполовину беременна»? Они либо ловятся, либо не ловятся потому что, покрытие тестами недостаточно.

Есть очень много ошибок, которые практически невозможно покрыть тестами:
— ошибки concurrency
— системные ошибки
— ошибки совместной модификации (concurrent modifications)
— ошибки финализации и освобождения ресурсов (leaks)
— third-party ввиду разных версий библиотек или компонентов системы на local и production
— real-time код

Кроме того есть те вещи, которые принципиально невозможно протестировать автоматически, и можно только наблюдать лишь визуально: правильно ли отображается графический элемент, правильно ли рендерится сцена, не уехал ли лейаут, etc…
Так что не панацея.

> которые про всё это только слышали, но отлично за 10 лет научились конструировать пятиколесные велосипеды и God object классы.
TDD-шники впадают в другую крайность как тотальная декомпозиция кода, которая суть есть не меньшее зло, чем God object. Вместо емкого, но понятного класса создается куча компонентов со слабыми зависимостями. Каждый из компонентов тестируется отдельно своим набором тестов вплоть до теста геттеров и сеттеров. И тем не менее при дальнейшей их компоновке возникают ошибки. В итоге все-равно приходится писать сложный top-level test, который тестирует всю задачу целиком. Усилий в 10 раз больше, кода в 5 раз больше, а результат тот же, что и с God object.
Юнит тесты — не серебряная пуля.
Помню внедряли мы их в очень бородатый проект с огромным количеством гуя, сервисов и баз данных. На простую задачу тест выходил в несколько раз более сложный самого кода. Тесты постоянно ломались и не хотели собираться, вместе с тем они помогали иногда найти глупые но незаметные с первого взгляда ошибки. При этом тесты не могли найти ошибок в сложной бизнес-логике.
Сопровождение тестов требовало ресурсов, сравнимых с сопровождением самого продукта: база данных постоянно наращивала структуру, GUI вообще был гибко настраиваемым. Да, тесты безусловно приносили пользу, но она и близко не оправдывала затраты. Через год мучений тесты забросили.

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

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

Возможно они были бы лучше, если делать всё правильно, но ведь никто не хочет рисковать, и правильно делают.
С большой вероятностью при такой организации никто не проверяет зависимость финансовых показателей от качества кода, количества багов и так далее.
Да и мне кажется, что финансовые показатели от этого не сильно зависят на самом деле и не особо нужно их оценивать в зависимости от этого. На мой взгляд, в разработке нужно оценивать зависимость затраченного времени и количества багов от качества и количества написанного кода. В общем, это уже совсем другая стезя, но активно развивающиеся компании должны приходить и к этому, потому что иначе погрязнут в своём коде.
Опять же, есть зависимость от того, B2C или B2B проект. Во случае B2B никуда от этого не деться, потому что сроки/качество/количество поджимают условиями договоров обычно.
>>Да и мне кажется, что финансовые показатели от этого не сильно зависят на самом деле
В том-то и дело. Бизнес не интересуется качеством кода, бизнес делается для извлечения прибыли.

Поставьте себя на место топ-менеджера: есть продукт который приносит хорошие деньги. Снизу поступает реквест:
— а давайте сдвинем все планы на полгода и проведём рефакторинг
— WTF? не занимайтесь ерундой, вы и так вечно сроки срываете.
— Но если мы сейчас отрефакторим через год-два начнем укладываться в сроки
Топ думает: годами срывали, почему я должен им сейчас поверить, что изменилось? Если сейчас начнут рефакторить есть риск вообще пролететь со сроками и профукать намечающиеся крупные контракты. Лучше наймем еще пару недорогих разработчиков и пусть пашут в том же ключе.
— Отставить, все силы на новый функционал.
Не должно быть отдельной задачей рефакторинга. Рефакторинг должен протекать одновременно с разработкой.
Не вижу необходимости полгода заниматься тупо рефакторингом. Код работает, вроде даже без ошибок — зачем его трогать? А вот если понадобилось поменять и находим баги или трудно вносить изменения туда, куда нужно — то почему бы не порефакторить? Потратить день-два-неделю, в зависимости от того, сколько нужно изменений внести помимо рефакторинга, но не полгода, занимаясь тупо переписыванием с нуля.
Вы возможно не сталкивались с проектами, которые практически невозможно отрефакторить частично. Слишком большая связность.
И тут получается замкнутый круг: чтобы безопасно отрефакторить и нигде ничего не отъехало — нужны автоматические тесты. Тесты натянуть не получается, т.к. слишком связанный код, который нужно рефакторить.
Ну не бывает безвыходных ситуаций. Бывает, что не хватает опыта и квалификации.
Не представляю, что может помешать вынести пару методов из одного класса в отдельный класс и покрыть их тестами. Это конечно всё сильно упрощённо, но так оно мне и представляется на самом деле.
Это хорошо если классы есть :) Самое интересное, имхо, когда нужно проект в процедурном стиле со всеми прелестями спагетти перетащить в ООП парадигму. В функциональную наверное ещё интересней.
Да запросто. На исходный класс 100500 ссылок в другом коде. Методы работают напрямую с БД и для тестов им нужно соответствующее окружение.
Стив Макконнелл писал, что, согласно исследованиям, юнит-тесты помогают избежать меньшего числа ошибок, чем обзоры и инспекции кода. Так что, если вы думаете о том, что внедрить в вашей команде в первую очередь, подумайте о code review.
Подпишусь. В отличие от юнит-тестов внедряется элементарно и работает сразу. Можно внедрить в любой, даже очень запущенный проект.
Собственно «внедрение» юнит-тестов — не самое удачное решение. Ресурсов на себя оттянет уж слишком много. Не говоря уже о том, что писать тесты будет не зачаствую автор кода и с большим лагом после написания самого кода, и тяжело будет выявить — эт тест криво написан, или тест падает, ибо где-то в коде тестируемого класса ошибка.

ИМХО юнит тесты имеют смысл только тогда, когда они пишутся (и апдейтятся) одновременно с кодом. До или сразу после написания кода — зависит от религиозных убежденийличных предпочтений
Просмотрел ещё раз 4 свойства юнит-тестов в статье. Ни одно из них не говорит, что юнит-тесты позволяют избежать большего числа ошибок, чем обзоры и инспекции кода.
В общем, не об этом автор хотел нам рассказать.
Я лишь хотел дополнить, что для повышения качества есть еще более эффективные методики, чем юнит-тесты.
Думаю, их стоит применять вместе, а не считать какие-то методики более важными над другими.
В идеале следует применять комбинации методик, но, к сожалению, это не всегда возможно. Потому вполне приемлемо расставить приоритеты.
Обзоры и инспекции кода не являются автоматизируемым процессом, как следствие, не могут быть интегрированы в buildline. Поэтому если нам нужен быстрая оценка работоспособности кода, юнит-тесты подходят на эту роль лучше, нежели code review.

Хотя, конечно же, лучше иметь И то, И другое.
Как кто-то уже писал, юнит-тесты — как утренняя зарядка: попробовал, понравилось, не прижилось
Всегда считал что TDD оправдано использовать только в критически важных проектах, ну или гики хотят позабавиться опыта ради.
Для меня юнит-тесты это инструмент для разработки.
Когда я создаю какой-то новый компонент системы, то я его сразу-же могу использовать в тестах.
Самое ценное для меня это возможность мгновенной отладки без необходимости внедрять компонент в большую систему и запускать всю систему для того, чтобы посмотреть выполнение пару строчек кода под отладчиком.
Второе ценное свойство юнит-тестов, это возможность контроля кода во время выполнения программы.
Я пишу на C++, где многие вещи можно контролировать во время компиляции, и я активно пользуюсь этим свойством, но при этом многие вещи так сделать не получается. И только с появлением юнит-тестов добавляется новая возможность контролировать код во время выполнения. Но это можно реализовать только при условии, что проект не будет собираться при сбое одного из юнит-тестов и юнит тесты запускаются при каждой сборке проекта.
Не вижу смысла зацикливаться на юнит-тестах.
Тестируйте что считайете необходимым и чтобы это было ненапряжно по времени.

Не получается написать юнит-тест под фичу, напишите интеграционный.
Не получается интеграционный, ну хоть функциональный напишите.
С ним беда? Пиши приемочный…

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

Эх, жалко, что чистые методы остались в мире процедурно-ориентированного программирования. В ООП метод зависит не только от параметров, но и от состояния объекта. А если он от состояния объекта не зависит, то должен быть статическим и, в большинстве случаев, вынесен в отдельный статический класс. Хотя, про статическую часть, я может и передергиваю, на платформах отличных от .Net, уже давненько не писал…
А жизнь она такая, все зависит от предыстории состояния (далеко вы уедете только на Марковских процессах), ну нет такого в жизни! И хваленное функциональное программирование обреченно на смерть именно поэтому. То, что противоестественно жизни и не естественно программируется — отмирает. Можно лишь контролировать сложность, и не усложнять там где не надо — но не сводить все к простоте (читай процедурно-ориентированному программирований и функциональному как его расширению)
Тесты это список требований и относится надо к нему соответствующим образом. Если Вы пишете код, не имея на него требований(от себя самого как минимум), то Вы явно не нацелены на успех, не подразумеваете, что код потом будет работать.
Требования меняются = меняются тесты, и если код не удовлетворяет новым требованиям, то тесты не проходят.
Требования меняются 20 раз до выхода проекта в свет, и поддерживать тесты в актуальном состоянии — это нечто… это трата времени и нервов, вместо того, чтобы на выходе отдел тестирование провел альфа тестирование и ОДИН раз исправить. А дальше уже бета пользователи будут ПОЛУЧШЕ любого комплекта юнит-тестов.
Вот кстати фраза, которая мне лучше всего прочистила мозг и поставила мысли на место. Не в юнит-тестах соль, а в требованиях. Вот их точно нужно для каждого момента формулировать. Не обязательно в коде тестов, хотя бы просто на бумаге нужно иметь список формальных требований. Проверять соответствование требованиям можно по-разному, главное — они сами, не писать код с бухты-барахты, а определиться с условиями, вариантами, случаями и граничными состояниями его работы и под это уже писать.
Тесты это инструмент. TDD — методология. Для того, чтобы ими эффективно пользоваться, необходимо учиться, вырабатывать навыки. Это как если вы всю жизнь ползаете на четвереньках, а затем решаете ходить на двух ногах — сначала передвигаться будет медленнее и набьете много шишек. Какие-то прежние привычки придетя забыть. Кто-то может решить, что такой вариант ему не подходит. Но шанс научится бегать есть у всех. :)
Ну, или рожденный ползать, летать не будет… поэтому пока вы ползаете на костылях, мы расправляем крылья и летим :)
Я с уважением и пониманием отношусь к первой категории разработчиков, хотя лично я отношусь ко второй, которые считают юнит-тесты пустой тратой времени.
Для меня автоматические тесты — это ежедневный обязательный рабочий инструмент, такой же как Java, ANT, IDE, Firebug. Не всегда тесты пишутся до кода. Не всегда имеет смысл писать именно юнит-тест — иногда проще написать Acceptance test. Как правило, никогда не получается добиться 100% покрытия кода. Но инструмент очень удобный и полезный.

Тот, кто считает юнит-тесты пустой тратой времени, просто не разбирается в вопросе.
Only those users with full accounts are able to leave comments. Log in, please.