krlz ([info]krlz) wrote,
@ 2007-11-05 18:15:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Про Unit-тесты

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

Когда я только начал работать программистом, я практически сразу попал в проект, который писался по большей части test-first. Я прочитал несколько книг про тесты, и постоянно применял знания полученные из них при написании кода. К моему сожалению, от тестов было немного прока. Некоторые из них постоянно ломались, и действительно оправдывали себя, но большинство валились один раз за свою жизнь: в тот момент, когда они есть, но реализация, которую они тестируют еще не написана. Было понятно, что я использую что-то не так.

Когда я начал работать в команде MPS, я был очень удивлен. Программа было достаточно сложной, но тестов на нее не было вообще. При этом код был достаочно прост и понятен, и проблем с качеством и стабильностю не возникало. Было очевидно, что можно писать совершенно работающие программы совсем без тестов, что бы не говорили авторы книжек про TDD. Более того, даже с рефакторингом проблем не возникало, обычно несколько дней после крупной реорганизации кода, все было совершенно работающим. С другой стороны, у нас были компоненты, в которых ошибки находились чаще, чем в других. Более того, часто многие из них повторялись. Вобщем, жить совсем без тестов это не выход. Иногда они могут облегчить жизнь.

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

Другая крайность это не тестировать ничего. Практическ в любой программе есть компоненты, ошибки в которых происходят чаще других. Более того, эти ошибки бывает повтоярются снова и снова. Программисты в проектах, не применяющие тестирования, проверяют то, что эти компоненты работаю руками. На это может уходить много времени, так что как и в тестировании всего подряд, мы имеем проблемы: поток мыслей прерывается на ручное тестирование, при ручном тестировании можно легко допустить ошибку, к тому же, тестирование руками это механическая работа, и тут можно пропустить ошибку, особенно если ты делаешь это 5 раз за день. Вобщем, случаи в который имеет смысл применять тесты, безусловно имеются.

Тесты полезная вещь, но использовать их надо только там где это действительно необходимо. Во первых, стоит использовать статически типизированный язык. Компилятор такого языка может обеспечить вам большое количество проверок бесплатно, и было бы глупо этим не воспользоваться. Не нужно идти на поводу у моды, используя популярные сейчас динамически типизируемые языки. В нех нет ничего такого, чего бы не было в Java или C#. Хуже того, к динамически типизированным языкам очень тяжело написать хорошее IDE, и то что в Java делается при помощи IDE за 2 секунды, в Ruby будет делаться гораздо дольше.

Не стоит тестировать вещи, которые практически никогда не ломаются. Например UI, OR mapping, и другой код, с несложной логикой. Тесты не позволят вам гарантировать, что программа будет работать правильно. Это можно сделать только при помощи доказательства правильности программы, что для 99.9% программ неоправданно и просто нереально.

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

При тестировании часто используют средства code coverage, такие как EMMA и Clover. Они позволяют определить то, какой код выполнялся при исполнении тестов. Обычно они используются для оценки качества тестов. В сложном коде, в том коде, который я призываю покрывать unit-тестами часто бывают сложная структура потока управления. Например, это могут быть вложенные if-ы со сложными условиями. Среди них часто бывают те, которые никогда не исполняются. Понять это бывает трудно, программист может забыть зачем он писал этот кусочек кода, а средства статического анализа могут не распознать недостигаемый код. Вот тут-то и бывают особенно полезны code coverage тулзы. Если у вас есть достаточно полный набор тестов, код который не исполняется будет виден. А когда видно какой код не исполняется, бывает достаточно несложно понять почему это происходит. Таким образом, имея хорошо оттестираванный код, можно упростить его сопровождения, не только через проверку его правильности на тестовых данных.

Вобщем, unit-тесты это мощный и полезный инструмент, но как и всякий инструмент его нужно использовать только там, где это реально оправдано.




(Post a new comment)


[info]pavel_kaplin
2007-11-05 04:01 pm UTC (link)
Похоже на правду. Не согласен по поводу неломающегося UI - один UI другому рознь. В Web-UI очень часто что-то ломается (под разными браузерами, блаблабла). Другое дело, что это трудно протестировать.

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

(Reply to this) (Thread)


[info]krlz
2007-11-05 04:03 pm UTC (link)
С вебом да. Но я писал про тот UI, который пишу я сам. На Swing.

(Reply to this) (Parent)


[info]skavish
2007-11-05 04:16 pm UTC (link)
ага, все правильно изложено :)

(Reply to this)


[info]j2sdk1_4_2_04
2007-11-05 06:01 pm UTC (link)
Ежу понятно что надо разумную достаточность во всём соблюдать.
То же самое и к комментированию кода относится.

(Reply to this) (Thread)


[info]Guerrero de terracota [andrewdashin.com]
2007-11-06 05:19 pm UTC (link)
присоединяюсь

(Reply to this) (Parent)


[info]behrk
2007-11-05 09:10 pm UTC (link)
С тем, что к тестам следует подходить разумно и без фанатизма -- согласен. НО:

Не стоит тестировать вещи, которые практически никогда не ломаются. Например UI, OR mapping, и другой код, с несложной логикой

Похоже на слова человека, которому не случалось писать UI или OR mapping.

Во первых, стоит использовать статически типизированный язык.

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

(Reply to this) (Thread)

Re: совершенно не зависят от используемого языка
[info]alll
2007-11-06 09:21 am UTC (link)
Хотелось бы посмотреть на c++ проекты, охваченные юнит-тестированием. :) Из-за особенностей языка фреймворки для юнит-тестирования под c++ - штука весьма забористая.

(Reply to this) (Parent)(Thread)

Re: совершенно не зависят от используемого языка
[info]krlz
2007-11-06 09:24 am UTC (link)
Да ничего страшного в этом имхо нет. Пишем скрипт, который генерит main метод, вызывающий тесты. Потом его компилируем и прогоняем. Никакой магии тут нет.

(Reply to this) (Parent)(Thread)

Re: совершенно не зависят от используемого языка
[info]alll
2007-11-06 09:26 am UTC (link)
Магии точно нет. Но геморрой присутствует в количестве, достаточном, чтобы начало казаться, что "программисты тратят свое драгоценное время, за которое они могли бы написать полезный код".

(Reply to this) (Parent)(Thread)


[info]behrk
2007-11-06 09:34 am UTC (link)
У меня тут недавно на собеседовании был человек из одного аутсорсного коровника, он рассказал, что им не разрешают рефакторить, если это не предусмотрено контрактом. Т.е. бывают такие проекты, где контрактом предусмотрено только выкатить работающий код, безо всех этих программистских глупостей. Я до сих пор в образе от этого. Мне теперь интересно -- бывают ли контракты, предусматривающие просто написание кода, без компиляции?

(Reply to this) (Parent)(Thread)


[info]alll
2007-11-06 09:40 am UTC (link)
[изподстола] движение "write-only код" выходит в массы!

(Reply to this) (Parent)


[info]alll
2007-11-06 09:42 am UTC (link)
Генри Форд был бы восхищён...

(Reply to this) (Parent)


[info]sergiej
2007-11-06 12:28 pm UTC (link)
Может человек не понял чего, часто запрещается рефакторить уже созданный другими обезьянами код, врапить, воркараундить но не трогать старые файлы. Запрет рефакторить собственный код в процессе девелопмента это фантастическая шиза, просто не верю что такое может быть.

(Reply to this) (Parent)


[info]jeffereywhitac
2008-08-11 04:48 am UTC (link)
USA - Friday, September at (CDT) Я как прочитал, так до сих пор и ржу ((.

(Reply to this) (Parent)

(Reply from suspended user)

(Reply from suspended user)

[info]behrk
2007-11-06 09:25 am UTC (link)
знаете, я человек просто

(Reply to this) (Parent)(Thread)


[info]alll
2007-11-06 09:30 am UTC (link)
за то и любим

(Reply to this) (Parent)(Thread)


[info]behrk
2007-11-06 09:32 am UTC (link)
да, пост оборвался в смешной точке :)

(Reply to this) (Parent)(Thread)


[info]andy1618
2007-11-07 06:21 am UTC (link)
Эх, жаль, весьма глубокое было изречение! :))))))))

(Reply to this) (Parent)


[info]behrk
2007-11-06 09:31 am UTC (link)
знаете, я человек простой и слова "покрытие тестами" не понимаю. Но когда я пишу на C++, то использую CppUnit точно также, как SUnit всё остальное время -- то есть пишу тестик, делаю чтобы он заработал, и так n раз, пока у меня в руках не окажется приемлемое решение.

Я могу долго и с удовольствием ругать C++, но ради бога: что такого есть в C++, что заставляет делать забористые фреймворки? По-моему -- ничего. Вся проблема в головах программистов, которые психологически не готовы органичиться простейшим решением, а будут гордо сооружать навороченный фреймворк.

(Reply to this) (Parent)(Thread)

Re: что такого есть в C++, что заставляет делать
[info]alll
2007-11-06 09:37 am UTC (link)
И тем не менее, корреляция между языком и забористостью, имхо, налицо. :)

(Reply to this) (Parent)


[info]rodrickpotelo
2008-08-06 06:16 am UTC (link)
Но почему же Вы меня упрекали, что я говорю «о своих переживаниях».

(Reply to this) (Parent)


[info]aefimov
2007-11-05 10:26 pm UTC (link)
Ты знаешь, надо, наверное тестировать баги, которые попадают в трекер. Есть бага, на нее должен быть тест.
Ещеб оно все интегрировалось бы круто, ценыб не было :)

(Reply to this) (Thread)


[info]krlz
2007-11-06 06:58 am UTC (link)
Если программа не писалась изначально test first, будут куски, которые будет тяжело оттестировать, так что это трудно выполнимо. Хотя проверка на регресс штука хорошая.

(Reply to this) (Parent)(Thread)


[info]antilamer
2007-11-06 12:17 pm UTC (link)
[info]yakov_sirotkin в каком-то посте резонно заметил, что куда важнее делать программу тестируемой, нежели действительно ее тестировать :)

(Reply to this) (Parent)


[info]vilgeforce
2007-11-06 07:50 am UTC (link)
+многа. Особенно полезно там, где автоматическое тестирование не соорудить, а проверять как-то надо. Проверять все - маразм и трата огромного количества времени. А вот проверка того, что уже когда-то ломалось - реальная альтернатива.

(Reply to this) (Parent)


[info]9000
2007-11-06 11:32 pm UTC (link)
Иногда test first -- это метод сформулировать для себя нужный кусок ТЗ :) Типа как определить интерфейс.
Тогда набор тестов получается "бесплатно", по кр. мере начальный.

(Reply to this) (Parent)(Thread)


[info]aefimov
2007-11-07 06:50 am UTC (link)
Да, но я работал с такими тестами только в вербальном режиме. Т.е. я делал удаленно подработку и мне формировали ТЗ таким образом: на листочке было 15-20 тестов, каждый тест четко сформулирован словами - "идем тудато, жмем пимпу, видем шыш". Удобно.

(Reply to this) (Parent)


[info]yeziz
2007-11-06 08:21 am UTC (link)
Сама мысль статьи +1
А вот детали...
"Тестировать надо то, что надо тестировать." В общем, подсократить раза в три и будет хорошо. :-)
UI, особенно, генерируемый и мультиязыковый, "ломается" на раз-два. Плюс, как было замечено выше, есть проблемы, специфичные для веба.

(Reply to this)


[info]grimkiller
2007-11-06 08:24 am UTC (link)
Согласен про динамически типизируемые языки. Во-первых, я просто не совсем хорошо отношусь к этому. Во-вторых, при юнит-тестах с динамической типизацией возникает час от часу целая куча скрытых тонкостей. И все это бьет по расшатанной психике... )))

(Reply to this)


[info]kmmbvnr
2007-11-06 08:42 am UTC (link)
Хотел было написать длинющий комментарий, дополнение.. а потом вспомнил что обо всем уже писал - http://kmmbvnr.livejournal.com/21946.html

>>Тесты полезная вещь, но использовать их надо только там где это действительно необходимо.
При подходе tests first, ненужных тестов нет по определению. Ведь тест пишется не только ради теста, но и ради того чтобы лучше понять предметную область и то насколько удобными получаются апи описывающие ее.

(Reply to this) (Thread)


[info]vogre
2007-11-06 10:06 am UTC (link)
+1.
Тесты - это скорее документация, чем код.
Соответственно, если приходится менять тесты, это значит, что изменилась спецификация, ожидание от системы. Поэтому время на изменение этих тестов - это время, потраченное не впустую, а на осмысление изменившихся требований.

(Reply to this) (Parent)


[info]alll
2007-11-06 09:24 am UTC (link)
Складывается впечатление, что под видом "юнит-тестов" рекламируются регрессионные тесты. Которые тоже нужны, но несколько из другой оперы.

(Reply to this)


[info]sb16
2007-11-06 09:34 am UTC (link)
Тестами должны заниматься тестеры, программисты - программированием.

(Reply to this) (Thread)

Re: Reply to your post...
[info]eugene_ivanov
2009-01-20 01:30 pm UTC (link)
и ты здесь (-;

(Reply to this) (Parent)


[info]i_love_python
2007-11-06 09:41 am UTC (link)
"Программа было достаточно сложной, но тестов на нее не было вообще. При этом код был достаочно прост и понятен, и проблем с качеством и стабильностю не возникало."

Когда кода много, будь он хоть сто раз прост и понятен, вероятность появления ошибок просто по закону больших чисел возрастает. Что собственно и подтверждается вашими же словами далее:

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

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

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

ЗЫ: было бы неплохо прогонять свои тексты перед публикацией через spellchecker.

(Reply to this)


[info]nicity
2007-11-06 12:54 pm UTC (link)
Код без функциональных тестов является кодом без обязательств в долговременной перспективе (полгода, год, два, три и больше). Для исследовательского проекта (типа MPS) это преимущество в начале его развития. Для платформы (типа MPS) любой существенный регресс приведёт к прекращению её использования частью пользовательской базы. Именно поэтому смотря на исследовательские проекты у меня возникает ощущение, что люди не решают практические задачи, а исследуют возможность предсказания победителя в скачках, ставя опыты на шарообразных моделях коней в вакууме...
ЗЫ Большую часть своей работы я пишу тесты.

(Reply to this) (Thread)


[info]krlz
2007-11-06 12:57 pm UTC (link)
Сейчас у нас есть функциональные тесты, которые покрывают значительную часть функциональности, но юнит тесты есть далеко не везде. Сделать это пришлось, как только у нас появились внутренние пользователи, для которых стабильность работы была критической.

(Reply to this) (Parent)

о чем базар?
[info]chaetal
2007-11-06 12:58 pm UTC (link)
Тут все так интересно обсуждают, а я вот не понял, о чем статья... У меня есть парочка предположений, но их, по-видимому, нужно отмести:

Догадка 1: о тестировании ПО и, более конкретно, об автоматизации этого процесса. Но что нового хотел сказать автор? Что кое-где тесты нужны? Мысль, мягко говоря, не новая. Что кое-где они не нужны? Тоже не ново, к тому же весьма сомнительно... Про "UI, OR mapping и другой код с несложной логикой" --- уже обсуждалось.

В общем, смысл данной заметки как статьи о тестировании от меня ускользнул.

Догадка 2: о TDD. Эта догадка ставится под сомнение после рассуждений о test-first и выводу о бесполезности "неломающихся" тестов. Мысль об опасности траты времени на бесполезые тесты еще больше усиливает эти сомнения. А цепочка "изменения в коде => изменения в тестах => куча проблем" позволяют окончательно отмести эту догадку. Да, статья не про TDD, так как автор сути этой методологии, похоже, не улавливает. Или весьма удачно это скрывает за постоянным обсуждением проблемы вылавливания повторяющихся(!) ошибок.

В общем, мне показалось, что статья даже скорее вредная, чем бессмысленная, ибо еще больше запутывает и без того уже достаточно запутанный вопрос (кстати, непонимание "массами" TDD и гибких методологий, похоже, скоро достигнет объема непонимания ООП), сбивает с толку новичков и ничего не дает (ну, мне так показалось) тем, кто что-то в предмете разговора понимает.

Про "бесплатность" статической проверки типов; про то, что "к динамически типизированным языкам очень тяжело написать хорошее IDE"; про "моду" на "бесполезные" (в них же нет ничего такого, чего нет в C# и Java); про вложенные if-ы и т.п. --- говорить, наверное, уже смысла нет?..


P.S.: без обид --- просто личное мнение о статье (не авторе!); извиняйте, если слишком категоричное :)

(Reply to this)


[info]andy1618
2007-11-07 06:37 am UTC (link)
>"Я прочитал несколько книг про тесты, и постоянно применял знания полученные из них при написании кода."

А какие именно книги? (Интересно было бы узнать впечатления)

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

(Reply to this)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…