HPUNIX Сайт о ОС и не только!

Об использовании модульных тестов и TDD

1 августа 2011 - unix
Об использовании модульных тестов и TDD

В протяжении последних нескольких месяцев я практиковал TDD. Сейчас я понимаю — эта техника (не путать технику и методологию!) работает. Ее просто использовать и она отлично решает полностью определенные задачи.

SECON.Посиделки #11 - Модульное тестирование

Эта заметка адресована программерам и, в наименьшей степени, руководителям проектов.

Сможете далее не читать, если…

Работать с вашим кодом просто и приятно? Вы не боитесь создавать рефакторинг, точно зная, что нигде ничего не взорвется? Самые суровые баги в релиз-версии вашего проекта можно охарактеризовать, как «мелкие недочеты»?

Все задачки производятся в заблаговременно обсужденные сроки?

Об использовании модульных тестов и TDD

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

Наибольшая тупость — это делать тоже самое и надежды на другой итог // Альберт Эйнштейн

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

Что такое модульные испытания?

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

  • Модульное тестирование происходит по принципу белоснежного ящика, его задачка — показать, что все модули либо классы корректно работают по отдельности;
  • Интеграционное тестирование инспектирует, что те же модули либо классы верно ведут взаимодействие вместе, притом употребляется уже принцип темного ящика;
  • Системное тестирование инспектирует, что весь продукт соответствует начальным требованиям, сюда относятся альфа- и бета-тестирование;
Об использовании модульных тестов и TDD

Некие другие виды тестирования — юзабилити тестирование, нагрузочное тестирование, тестирование безопасности, тестирование сопоставимости. Невзирая на то, что дальше в этой заметке пойдет речь только о модульных тестах, другие виды тестирования также важны. Примеры модульных тестов вы сможете отыскать в исходниках к заметкам Эллиптическая тайнопись на практике, Реализация хэш-таблиц, практически как в Perl, Очереди заданий и пулы процессов в Erlang, также Пример использования Common Test, EUnit и Meck.

Модульное тестирование позволяет выявить огромное количество ошибок, но не все. Чтоб убедиться, что ваше приложение корректно работает как с MySQL, так и с PostgreSQL, нужно тестирование сопоставимости. Чтоб убедиться, что парсер логов довольно стремительно обрабатывает огромные файлы, нужно нагрузочное тестирование.

C# Удаляем дубликаты строк из массива + unit tests

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

Что такое TDD?

Об использовании модульных тестов и TDD

Test-driven development либо разработка через тестирование — это техника программирования, в согласовании с которой написание кода происходит по последующему методу:

  1. Напишите тест. Да, написание тестов происходит перед написанием хоть какого, даже самого обычного, кода. К примеру, если вы желаете объявить новейшую функцию, напишите тест, который просто ее вызывает и инспектирует итог.
  2. Удостоверьтесь, что испытания не проходят. В примере с функцией тест даже не будет компилироваться, так как функция еще не объявлена.
  3. Напишите код. Это должен быть более обычной код, обеспечивающий срабатывание теста, в нашем примере — объявление функции, возвращающей константу.
  4. Удостоверьтесь, что испытания проходят. Сейчас, когда код написан, все испытания, включая последний написанный, должны компилироваться и проходить. Если это не так, следует возвратиться к шагу 3.
  5. Произведите рефакторинг и вернитесь к шагу 1. Страшиться нечего, так как весь код покрыт тестами. Откладывать рефакторинг на позже не стоит. Позже вы будете ужаснее держать в голове код и вообщем для вас будет некогда.

Чем меньше времени необходимо на выполнение одной таковой мини-итерации, тем лучше. В эталоне это время не должно превосходить пары минут.

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

Да это не будет работать, поэтому что…

Я знаю, о чем вы на данный момент думаете. В первый раз услышав о TDD, я тоже был настроен очень скептически. Это сколько же излишнего времени необходимо издержать и сколько дополнительного кода написать!

Но распробовав TDD на практике, я удостоверился, что это один из числа тех случаев, когда нельзя полагаться на «это же очевидно».

Вот некие пользующиеся популярностью заблуждения в отношении TDD:

  1. Написание тестов отбирает время. В реальности написание тестов сберегает время. Допустим, вы пишите сайтик. Как убедиться, что новенькая фича работает и не привела к появлениям новых багов? Не отвалилось ли что-то в итоге рефакторинга? Если автоматических тестов нет, необходимо вручную потыкать во все ссылочки и выслать все формочки, на что уйдет много времени. А если у нас есть испытания, довольно запустить их и узреть итог через несколько секунд.
  2. Но ведь приложение работает с СУБД, сетью, файлами… Эта неувязка тривиально решается при помощи mock-объектов. Вы без усилий отыщите много готовых классов для вашего языка программирования. В качестве примера заслуживает внимания Perl-модуль DBD::Mock.
  3. Это работает, только если писать проект с нуля. Ничто не мешает писать новый код для данного проекта по TDD. Если у вас есть старенькый, испытанный временем код, который просто работает, для чего покрывать его тестами? Если же с кодом повсевременно появляются трудности, для вас все равно придется его переписать, а новый код в согласовании с TDD будет покрыт тестами.
  4. Об использовании модульных тестов и TDD
  5. Лучше мы напишем код, а позже испытания. Это не работает. Во-1-х, позже у вас не будет времени. Во-2-х, даже если позже вы напишите какие-то испытания, они будут намного ужаснее покрывать код, чем испытания, написанные во время разработки через тестирование. В-3-х, если при написании кода не волноваться о том, как вы будите его тестировать, позже вы просто не можете написать для него испытания.

Работая по TDD, вы:

  1. Получаете более полные испытания. При помощи Devel::Cover я узнал, что степень покрытия тестами кода, написанного по TDD (96.5% строк кода было выполнено хотя бы один раз), значительно превосходит степень покрытия кода, испытания для которого писались в конце разработки (70.4% строк кода).
  2. Сходу выявляете огромную часть ошибок. Откладывая поиск и исправление ошибок на позже, вы значительно рискуете сорвать сроки. Для вас кажется, что разработка идет полным ходом и на исправление ошибок пригодится некоторое количество дней. Горьковатый опыт указывает, что эти ожидания никогда не оправдываются.
  3. Не боитесь делать рефакторинг. В хоть какой момент времени весь код покрыт тестами. Если в итоге рефакторинга что-то отвалится, вы сходу узнаете об этом и или убрите делему, или сделаете git reset. Вы ничем не рискуете.
  4. Стремительно выявляете задачи при деплое. Не так давно я выкатывал в бой один сайтик, написанный на Perl. Его разработка велась под FreeBSD, но боевой сервер работал под CentOS. В итоге прогона тестов на боевом сервере выяснилось, что модуль File::fgets работает не совершенно потому что ожидалось. Я издержал минут 5 на переписывание кода так, чтоб он не использовал этот модуль. Если б у меня не было тестов, неувязка выявилась бы только через некоторое количество дней. Жутко пошевелить мозгами, сколько времени мне пригодилось бы на ее устранение!
  5. Получаете более обмысленный код. Я имел дело как с «обычными» проектами, так и с проектами, написанными по TDD. В первом случае код нередко припоминает спагетти и даже неясно, как написать для него испытания. Во 2-м случае код намного более структурирован, классы делают обыкновенные вещи и имеют понятный интерфейс, их просто использовать повторно.
  6. Имеете документацию в виде тестов. Обычно программеры не очень обожают писать документацию, зато с написанием кода заморочек не появляется. Модульные испытания представляют собой замечательно описание интерфейсов классов и примеры их использования.
  7. И не только лишь! Решили собрать проект другим компилятором (либо, к примеру, перейти с Python Два на Python 3)? Прогоняем испытания и смотрим, какие участки кода не стали работать. Хороший человек прислал для вас pull request? Мерджим, прогоняем испытания и здесь же узнаем — сломалось чего-нибудть либо нет. В общем, применений у автоматических тестов уйма.

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

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

Будьте внимательны.

Какими должны быть испытания

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

Представим, что у вас есть программка, содержащая Шестьдесят четыре оператора if. Если вы стремитесь к 100%-му покрытию кода, для вас необходимо написать более Двести шестьдесят четыре тестов, чтоб обеспечить все вероятные композиции выполнения и не выполнения критерий в операторах if.

Притом некие проверки могут быть для случаев, которые в теории никогда не должны произойти. Таким макаром, 100%-ое покрытие кода фактически невыполнимо.

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

Чем подольше производятся испытания, тем пореже вы будете их запускать, потому отличные испытания должны работать стремительно. Если писать испытания так, чтоб они не зависели друг от друга, их можно будет запускать параллельно (ключ -j утилиты prove), что ускорит их выполнение. Не считая того, независящие испытания можно запускать в случайном порядке (ключ -s), что время от времени позволяет отыскать пару излишних ошибок.

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

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

В некий момент написание новых тестов становится неэффективным. Я стараюсь писать испытания так, чтоб более 95% всех строк кода производилось хотя бы один раз. Этой степени покрытия достаточно просто достигнуть и при всем этом код можно смело рефакторить не боясь, что что-то взорвется.

Если вы пишите сайтик на Mojolicious, то сможете найти степень покрытия кода последующим образом:

HARNESS_PERL_SWITCHES=-MDevel::Cover prove -l -r ./t
cover # создаем отчет
cover -delete # удаляем отчет

Необходимо подчеркнуть, что при написании маленьких (в границах нескольких сотен строк кода) и обычных программ, не имеет особенного смысла покрывать их тестами.

У нас вправду нет времени!

Если относительно маленькую задачку вправду необходимо сделать стремительно (беспочвенное обещание управления сделать к такому-то числу — это недостающая причина!), при всем этом вы понимаете опасности и убеждены, что задачку нельзя распараллелить меж несколькими программерами, тогда:

  • Заблаговременно выделите время на рефакторинг и написание тестов. Это должно быть не только лишь словестно — должен быть таск и у него должен быть исполнитель. После выполнения срочных задач программеры обычно на пару дней «выбывают из строя», так что не скупитесь на время для рефакторинга.
  • Не ломайте уже написанные испытания. Если какие-то испытания перестают работать, лучше закомментируйте их и напишите TODO-комментарий с информацией о том, как починить тест.
  • Напишите хоть некий минимум тестов. Как ранее говорилось, даже несколько обычных тестов могут быть очень действенными.

Если задачку можно выполнить впору, расслабленно поработав над ней в выходные, лучше пойти этим методом, чем вкалывать по Двенадцать часов в день. Следует также отметить, что срочность в 99% случаев оказывается выдуманной.

Языки программирования и модульные испытания

Если вы возьмете случайную библиотеку на языке программирования Haskell, то вероятнее всего обнаружите, что она не покрыта никакими тестами. В наилучшем случае совместно с библиотекой будет идти несколько тестовых утилит, которые, но, не производятся перед установкой библиотеки. Модули, написанные на Perl, напротив, всегда покрыты тестами, которые автоматом гонятся перед установкой.

Дело в том, что Haskell — компилируемый язык со серьезной статической типизацией, программки на котором просто переносятся меж разными ОС и архитектурами микропроцессора. Если программка на Haskell компилируется, то она практически наверняка делает то, что должна.

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

Язык C++ занимает среднее положение меж Haskell и Perl. Это компилируемый язык, но типизация в нем, вопреки всераспространенному заблуждению, не является серьезной:

char c = 1;
int i = c;

Не считая того, у этого языка не так отлично кроссплатформенностью, как у Haskell. К примеру, при переходе с i386 на amd64 изменяются sizeof(long) и sizeof(char*).

Если вы пишите на Haskell либо языке с схожими качествами, то испытания для вас необходимы только для участков кода с очень сложной логикой. Если вы пишите на С++, C#, Java, Perl, Python, PHP либо Ruby, то в ваших интересах позаботиться о написании добротных модульных тестов. Споры о том, на что следует растрачивать время — на написание тестов либо на то, чтоб просто вынудить программку компилироваться, ведутся до настоящего времени.

Вопросы читателям

Скажите, на чем вы пишите и практикуете ли вы TDD? Если нет, планируете ли вы испытать TDD в не далеком будущем? Практикуют ли эту технику у вас на работе?

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

Похожие статьи

  • Пример использования Common Test, EUnit и Meck

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

  • Примеры использования wxWidgets

    Как и обещал, пишу продолжение собственной заметки про wxWidgets. В этом посте будет описана установка wxWidgets и Code::Blocks под разными операционными системами, базы сотворения GUI при помощи wxSm...

  • Мой 1-ый опыт использования MongoDB

    Большая часть программистов (не считая тех, кто вообщем не смотрит за новостями) наверное что-то слышали о MongoDB, но никогда не воспользовалось этой СУБД. Давайте же выясним, что умеет MongoDB, а что не у...

  • Тест производительности скриптов на Python

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

  • Совместное внедрение C и Haskell

    Помните, я как-то писал про разработку GUI приложений на Haskell с внедрением библиотеки wxWidgets? Мне стало любопытно, а нельзя ли сгенерировать код GUI на языке C++ в wxGlade либо Code::Bloc...

Теги: ос
Рейтинг: +8 Голосов: 37 908 просмотров
Комментарии (0)

Нет комментариев. Ваш будет первым!

Найти на сайте: параметры поиска

Windows 7

Среда Windows 7 на первых порах кажется весьма непривычной для многих.

Windows 8

Если резюмировать все выступления Microsoft на конференции Build 2013.

Windows XP

Если Windows не может корректно завершить работу, в большинстве случаев это

Windows Vista

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