Эволюция юнит-теста

    Много слов сказано о том, как правильно писать юнит-тесты, и вообще о пользе TDD. Потом ещё и какое-то BDD замаячило на горизонте. Приходится разбираться, что из них лучше и между ними какая разница. Может, это и есть причина, почему большинство разработчиков решили не заморачиваться и до сих пор не используют ни того, ни другого?

    Коротко: BDD — это дальнейшее развитие идей TDD, стало быть, его и надо использовать. А разницу между TDD и BDD я попробую объяснить на простом примере.

    Рассмотрим 3 ревизии одного юнит-теста, который я нашёл в одном реальном проекте.

    Попытка номер №1


    Первая версия этого юнит-теста была такой:

    public class ReferenceNumberTest {  
      @Test  
      public void testValidate() {  
        assertFalse( ReferenceNumber.validate("1234567890123") );  
        assertFalse( ReferenceNumber.validate("1234567") );  
        assertTrue( ReferenceNumber.validate("12345678") );  
      }  
    }
    

    Мы называем это типичным юнит-тестом. Он тестирует код, но и только. Больше никаких преимуществ у него нет. Именно после такого кода скептики делают вывод, что от юнит-тестов нет особой пользы.

    Попытка номер №2


    В какой-то момент пришёл разработчик и решил применить к этому коду некоторые "best practices" из TDD: разбить тест-метод на несколько маленьких, так чтобы каждый из них тестировал только одну вещь, и дать им соответствующие имена.

    Вот что у него получилось:
    public class ReferenceNumberTest {  
      @Test  
      public void testTooLong() {  
        String len13 = "1234567891111";  
        assertEquals(len13.length(), 13);  
        assertEquals(ReferenceNumber.validate(len13), false);  
      }  
      
      @Test  
      public void testTooShort() {  
        String len7 = "1234567";  
        assertEquals(len7.length(), 7);  
        assertEquals(ReferenceNumber.validate(len7), false);  
      }  
      
      @Test  
      public void testOk() {  
        String len8 = "12345678";  
        assertEquals(len8.length(), 8);  
        assertEquals(ReferenceNumber.validate(len8), true);  
      
        String len12 = "123456789111";  
        assertEquals(len12.length(), 12);  
        assertEquals(ReferenceNumber.validate(len12), true);  
      }  
    }
    


    Мы называем это хорошим юнит-тестом. Он гораздо легче читается: по названиям переменных легко догадаться, что 13 символов — это слишком много, 7 — слишком мало, а 8 символов — это нормально.

    Попытка номер №3


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

    Разработчик продолжает процесс разбивки и переименования:

    public class ReferenceNumberTest {  
      @Test  
      public void nullIsNotValidReferenceNumber() {  
        assertFalse(ReferenceNumber.validate(null));  
      }  
      
      @Test  
      public void referenceNumberShouldBeShorterThan13() {  
        assertFalse(ReferenceNumber.validate("1234567890123"));  
      }  
      
      @Test  
      public void referenceNumberShouldBeLongerThan7() {  
        assertFalse(ReferenceNumber.validate("1234567"));  
      }  
      
      @Test  
      public void referenceNumberShouldContainOnlyNumbers() {  
        assertFalse(ReferenceNumber.validate("1234567ab"));  
        assertFalse(ReferenceNumber.validate("abcdefghi"));  
        assertFalse(ReferenceNumber.validate("---------"));  
        assertFalse(ReferenceNumber.validate("         "));  
      }  
      
      @Test  
      public void validReferenceNumberExamples() {  
        assertTrue(ReferenceNumber.validate("12345678"));  
        assertTrue(ReferenceNumber.validate("123456789"));  
        assertTrue(ReferenceNumber.validate("1234567890"));  
        assertTrue(ReferenceNumber.validate("12345678901"));  
        assertTrue(ReferenceNumber.validate("123456789012"));  
      }  
    }
    


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

    Между прочим, этот пример эволюции юнит-теста я собирался показать на семинаре devclub.eu по BDD в Таллине. И вот, за день до семинара я обнаружил, что я забыл скопировать исходный код самого класса ReferenceNumber, который мы тут всю дорогу тестируем. Что делать? Паника! До семинара остался один день! Мне нужно было срочно самому написать его заново.

    А теперь посмотрите на эти три тест-класса и подумайте, какой из них помог мне восстановить логику класса ReferenceNumber.


    И наконец, BDD


    Можно сказать, третья версия отличается от предыдущих тем, что она описывает поведение класса. Это достигается за счёт использования таких слов как «should» и «contain»: «мой класс должен вести себя так-то и так-то», «мой метод должен делать то-то и то-то».

    Так вот, идея BDD как раз и заключается в том, чтобы вместо слов «test» и «assert» использовать слова «spec» и «should». Да-да, разница всего лишь в словах, но именно это, по замыслу авторов BDD, и делает спецификации удобочитаемыми, а написание тестов спецификаций до кода — естественным для человеческого мозга.

    Убедиться в этом вы можете, взглянув на тот же пример, переведённый с языка JUnit на язык Easyb:
    description "ReferenceNumber"
    
    it "should not be null", {
      ReferenceNumber.validate(null).shouldBe false
    }
    
    it "should be shorter than 13", {
      ReferenceNumber.validate("1234567890123").shouldBe false
    }
    
    it "should be longer than 7", {
      ReferenceNumber.validate("1234567").shouldBe false
    }
    
    it "should contain only numbers", {
      ReferenceNumber.validate("1234567ab").shouldBe false
      ReferenceNumber.validate("abcdefghi").shouldBe false
      ReferenceNumber.validate("---------").shouldBe false
      ReferenceNumber.validate("         ").shouldBe false
    }
    
    it "valid reference number examples", {
      ReferenceNumber.validate("12345678").shouldBe true
      ReferenceNumber.validate("123456789").shouldBe true
      ReferenceNumber.validate("1234567890").shouldBe true
      ReferenceNumber.validate("12345678901").shouldBe true
      ReferenceNumber.validate("123456789012").shouldBe true
    }
    


    Отчёт о запуске этих тестов спецификаций фактически может служить документацией:
    5.31 КБ

    Кроме it и should, в BDD есть и другие важные слова, такие как given, when и then, а также before и after, ну и вдобавок ensure, narrative и «should behave as». Также BDD подходит не только для юнит-тестов, но и для функциональных/интеграционных тестов, но это уже выходит за рамки данной статьи. Сейчас нас интересует уровень юнит-тестов. Цель данной статьи — показать, что их можно писать по-разному.


    Осталось добавить, что библиотеки для написания BDD спецификаций есть и для других языков: Java (JDave, JBehave), Ruby (RSpec, RBehave, Cucumber), Groovy (Easyb), Scala (Scala-test), PHP (Behat), CPP (CppSpec), .Net (SpecFlow, Shouldly), Python (Lettuce, Cucumber).
    А если по независящим от вас причинам вы не можете пересесть с JUnit на что-то другое — тоже ничего, только помните о третьем примере. Кстати, в этом случае вам пригодится библиотека Harmcrest.

    Как завещал Козьма Прутков: товарищ, BDDи!

    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну, и что?
    Реклама
    Комментарии 68
    • +6
      Как Тим Лид настоял, что версия 2.0 одной высоконагруженной распределенной отказоустойчивой системы будем делать в виде TDD. В итоге, сами того не зная, пришли к именованию и грануляции BDD.
      Версия 1.0 системы имела всего 300 тестов, исключительно интеграционных.

      Теперь же у нас более 3000 юнит тестов и около 50 интеграционных и полная увереность, что система не развалится при малейшем изменении.

      TDD/BDD просто единственный путь для систем, малейший сбой которых просто непозволителен.
      • +2
        Это высказывание, хоть и абсолютно верное, но вредное, ибо большинство программистов начинает думать, что им TDD/BDD не нужно, так как их система не такая критичная.

        На самом деле TDD/BDD позволяет быстрее и комфортнее писать любые системы, а не только отказоустойчивые.
        • +1
          На чем основано суждение, что «TDD/BDD позволяет быстрее писать любые системы» (про комфорт выкинул, т.к. совсем уж субъективно)? Сколько читал по теме, кто-то из авторов явно, кто-то неявно, но говорят, что исследования по эффективности не проводились?
          • 0
            На моём личном опыте. Мне посчастливилось работать в компании, где все коллеги понимают и используют TDD неукоснительно каждый день — и у нас всё очень хорошо. Быстро и комфортно.

            Да, это субъективно. Но мне кажется, если даже вам дадут ссылку на исследование по эффективности TDD, она вас не заставит поверить и начать использовать. А такое вот субъективное мнению — может.
            • +1
              Здесь вопрос в области применимости. Исследование покажет когда это эффективно.

              То, что у вас вся организация построена под TDD не означает, что вы бы не добились такой же скорости и комфорта без TDD/BDD (есть и другие подходы, которые решают те же проблемы; например, использование аналитиков, архитекторов, тестировщиков). А так же не означает, что любой проект любой команде быстрее писать с использованием TDD/BDD (для упрощения, можно считать, что команда делает не первый, а 3ий проект на TDD).
          • +9
            Хочу поспорить по поводу «быстрее» — это абсолютно не так. Когда нужен быстрый time to market следование TDD замедляет в 3 раза. Имеется опыт клиенсткого приложения, который начали делать при помощи TDD и все сильно затянулось, а приложение надо было выпускать очень и очень скоро (специфика рынка) — в итоге пришлось отойти от TDD и делать все по старинке. Потом, конечно, пришлось долго долго дописывать тесты после релиза.
            Я бы сказал, что TDD позволяет писать приложения, которые очень просто сопровождать ибо будет 100% увереность, что все работает правильно. Но это нефига не ускоряет разработку.
            • 0
              Это вопрос навыков. Не надо по оному опыту судить.
              На эту темы есть очень простой довод: большая часть времени уходит не на написание (кода), а на продумывание. Это время вы тратите по-любому, ведь продумывать свои действия надо, так? Ну а если вы оформляете промежуточные результаты своего мыслительного процесса в виде тест-кейсов, то идти он должен проще и стабильнее. Это всё равно что каждую следующую мысль записать на бумажку.
              • +1
                У меня на одном из проектов, который пробовали делать по TDD, значительная часть времени уходила на изменение тестов. Т.е. фактически старые тесты не поймали ни одной ошибки, а поверх писались уже новые тесты. Никакого особого продумывания для классического mvc-веб-приложения не требуется (время на продумывание относительно времени на написание тестов). Естественно, до этого уже есть некая проектная документация (описание базы, архитектуры, верстка).
                • +2
                  Видимо ключевое слово «пробовали». Имхо, ТДД/БДД дает ускорение только когда люди уже привыкли и пишут тесты быстро, а не пробуют.
                  • 0
                    Не спорю, что при определенных навыках тесты будут писаться быстрее. Но это не снимает необходимость их написания, рефакторинга и обновления под новый функционал. Собственно, интересует когда это оправдано, а когда нет. Ответ «Всегда», который по сути есть в статье представляется несколько оптимистичным.
                    • 0
                      Многие считают, что универсального критерия тут нет.
                      Может быть, стоит хорошенько попробовать и самому решить, какой критерий подходит для вашей команды и ваших разработчиков.

                      Я вот попробовал и пришёл к выводу: всегда. :)
                  • +2
                    Никакого особого продумывания для классического mvc-веб-приложения не требуется

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

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

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

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

                    — эй, вася, что ты такое пишешь?
                    — еще не знаю, щаз скомпилирую, узнаю.

                    самое сложное при внедрении TDD это ломка программистского стереотипа: сначала быстро «накодить», ведь и так все ясно, а потом думать зачем, для чего, и исправлять ошибки. :)

                    как-то так. прошу прощения за дайджест.
                  • 0
                    Я думаю, неправильно относиться к юнит-тестам как к «тестам».
                    Смотрите на них как на «спецификации». Ничего, что старые тесты не поймали ни одной ошибки — зато они описывают старые требования к коду. А если появились новые требования к коду, то их тоже надо описать в виде новых тестов. Никакого противоречия тут не вижу.

                    Если времени на продумывание КОДА особо не требуется, то уж тем более ещё меньше времени потребуется на написание требований к нему.
                  • 0
                    Нет, это не вопрос навыков — а вопрос сроков. Банально надо сроки разработки умножать на 1.5 при использовании TDD ибо объем кода в тестах будет сопоставим, если не больше, с объемом кода самого приложения.

                    Но это увеличение на 1.5 того стоит, если приложение потом будет иметь долгую жизнь.

                    А продумывание оформлять в виде тестов — не кажется ли вам, что это только все усложняет? Прототипирование и продумывание я вообще делаю в виде спарков — когда делают хомячковый проект и там обкатываю идею вообще без тестов, а потом переношу ее на проект уже следуя TDD
                    • 0
                      > продумывание оформлять в виде тестов — не кажется ли вам, что это только все усложняет?
                      Нет, не кажется. Опять же, вопрос навыков :)
                      Или даже привычки.
                  • 0
                    Кстати, уверенность часто ложная, т.к. шаблоны тяжело действительно хорошо покрыть тестами. Если их много и они достаточно часто меняются, то без носителя знаний проекта (как же оно должно работать и почему пришли к этому) и тестировщика будет сложно.
                    • 0
                      Но ускоряет тестирование? У меня неоднократно бывали случаи, когда тест приходилось прогонять раз по 30 пока не добьешься нужного поведения. Может быть, вы пытались добиться избыточного покрытия тестами?
                      • 0
                        На начальном этапе, когда функциональность невелика, почти все держится в голове TDD действительно может замедлит разработку. Выигрыш будет получен через некоторое время, более-менее долгоживущим системам TDD банально позволяет держаться на плаву, сохраняя устойчивость при рефакторинге и оптимизации самой системы, и, что не менее важно — команды, которая делает систему.
                        • 0
                          ибо будет 100% увереность

                          <=99% же.
                      • +1
                        Система не развалится. Развалятся ваши 50 интеграционных тестов и некоторые из 3000 юнитов. Не потому что плохо запрограммировано, а наоборот, потому что функционал изменился, и тесты нужно пересматривать.
                        Чем больше тестов в эволюционирующей системе, тем бОльшая часть времени тратится на их поддержку.
                        • 0
                          Такое случается, если вносить архитектурные изменения где-нибудь через год после начала проекта. В этом случае нужно сходить, сказать ответственному за изменение всё что ты о нём думаешь и садиться рефакторить и код и тесты.
                          • +2
                            Вот и хорошо, что они разваливаются.

                            Так же это смотря как писать юнит тесты — у нас почти 99% тестов это изолированные тесты, где тестируется класс, а все зависимости ему передаются в виде моков интерфейсов. И если надо поменять поведение этого конкретного класса — мы правим тесты этого конкретного класса.

                            Взаимодействие тестируется на уровне интеграционного теста, когда приложение запускается как черный ящик и проверяются результаты работы.
                        • 0
                          Почитал про Behat и увидел «Behat is testing applications outside in. It means, that Behat works only with your application's input/output. If you want to test your models — use unit testing framework instead, Behat created for behavior testing (but can be used for anything +) ).» И несмотря на уточнение в скобках, похоже BDD не является заменой надмножеством TDD, а дополняет его.

                          P.S. Так до конца TDD и не осилил. Часто пишу тесты, которые, имхо, бессмыслены, но как бы нужны, например проверяю, что new MyClass возвращает экземпляр MyClass. Или часто приходится писать кучу «обвязок», только чтобы проверить записывается ли объект в БД, хотя по коду в 2 строки видно, что не записаться он может только по внешним причинам.
                          • +4
                            Мне тоже интересно найти ответ на вопрос почему BDD может быть заменой TDD.
                            Вся идея BDD это использовать слова вроде should и ensure. Всё. В чем эволюция?
                            Почему нужно было создавать столько новых BDD фреймворков, а не оформить BDD в некоторый набор рекомендаций по написанию TDD и использовать существующие фреймворки?
                            И по-моему, пример №3 лучше чем пример с BDD %)
                            • 0
                              Моя изначальная цель была показать, что пример №3 лучше, чем №1 и №2, так что я своей цели добился, видимо. :)

                              Да есть и набор рекомендаций, есть и целые книжки.
                              Можно было бы и про JUnit сказать: зачем было городить эту библиотеку вместо того, чтобы просто написать рекомендации, как правильно тестировать код?

                              Можно было бы и про C++ сказать: зачем было городить целый язык вместо того, чтобы просто написать рекомендации, как правильно программировать на ассемблере?

                              Эволюция — она такая и есть. Каждая следующая версия обезьяны тоже несильно отличалась от предыдущей, а вот глядишь, в конце концов человечек получился.
                              • 0
                                Разговор не о том, что BDD не намного лучше чем классические юниты, а о том, что он в некоторых ситуациях хуже. И стоило бы сказать, что BDD не против TDD, он его расширяет.
                                • 0
                                  А я и сказал в самом начале: «BDD — это дальнейшее развитие идей TDD».
                                  • 0
                                    Очень интересно узнать, в каких случаях BDD хуже? Можете привести какие-то примеры?
                                    А еще лучше написать статью — я думаю, это многим было бы интересно.
                                • +4
                                  Потому что автор топика показывает не Behavior, а Unit-тесты с синтаксисом Behavior-тестов.
                                  На самом деле, основная идея BDD именно в обратном подходе к разработке, а не в синтаксисе. TDD является моделью разработки inside-out (от частного к общему), когда мы гарантируем работу отдельно взятых атомарных модулей и предполагаем, что система в общем, при этом, будет работать нормально. Через некоторое время, было замечено, что это не так и что верная работа отдельных модулей вовсе не гарантирует верную работу системы в целом.

                                  Появилась потребность в тестировании системы в общем. Такую модель разработки назвали outside-in (от общего к частному) или BDD. Смысл сводится к тому, что мы также сокрываем реализацию в черный ящик и работаем только с выходами/входами, только не отдельных модулей, а всей системы в целом (веб-приложение — Request/Response, консольное приложение — Arguments/Output). Оказалось, что подобную логику легче описывать на специфических терминах типа should/given/when и т.п. Это язык бизнес-логики, на котором разговаривают наши заказчики и UT-фрэймворки оказались к нему неприспособленными. Это как крепить на запорожец обвесы от BMW и считать, что у тебя M3.

                                  https://github.com/sebastianbergmann/phpunit/commit/6aa9183496f9bb2131d17ba08195a93a07937762#commitcomment-164598. Тут Sebastian говорит про то, что он депрекэйтит BDD функциональность в phpUnit 3.5 из-за ее неспособности решать поставленные задачи оптимально и появления на рынке достойного средства для этого — Behat.

                                  Задачи выполняются какбы одинаковые, но цели разные (тестирование атомарных модулей и тестирование всей системы в целом) — отсюда специфичные требования и новый функционал, который не нужен в TDD, но необходим в BDD.

                                  Так что для сравнения BDD с BDD на UT фрэймворке следует приводить вот этот пример: https://gist.github.com/478885.
                                  • 0
                                    Тут ниже дали ссылку на ваш доклад. Посмотрел, несколько вопросов есть по BDD вообще, и Behat в частности:
                                    — использование BDD — это, грубо говоря, переход от разработки основанной на юнит-тестах к разработке, основанной на функциональном/интеграционном тестировании?
                                    — использование юнит-тестов при BDD необходимо, желательно или избыточно?
                                    — а Behat для symfony разработчика — это, грубо говоря, обёртка над функциональными тестами symfony, делающая удобным/стандартизированным то, что и так есть (спеки/user srory можно написать в произвольном виде, и на их основе писать функциональные тесты)?
                                    • 0
                                      — использование BDD — это не переход, а добавление к юнит-тестам behavior тестов
                                      — верная работа отдельных модулей не гарантирует верную работу системы в целом, но верная работа системы в целом не гарантирует верную работу отдельных модулей ;-)
                                      — sfBehatPlugin — это набор шагов-оберток поверх sfTestFunctional (https://github.com/everzet/sfBehatPlugin/tree/master/features/steps/). EverzetBehatBundle — это набор шагов, использующих стек HTTP компонентов Symfony2 для эмуляции браузинга и тестирования аутпута (http://dl.dropbox.com/u/282797/BehatBundle_results.html). У EverzetBehatBundle более тесная интеграция с фрэймворком за счет того, что Symfony2 как и Behat используют DIC.
                                      • 0
                                        > BDD — это, грубо говоря, переход от разработки основанной на юнит-тестах к разработке, основанной на функциональном/интеграционном тестировании?
                                        Однозначно НЕТ!
                                        Юнит-тестирование и функциональное/интеграционное тестирование — это два разных вида тестирования, они оба необходимы и не могут заменить друг друга.

                                        BDD всего лишь пытается улучшить и то, и другое.
                                        • +1
                                          у меня TDD ассоциируется с en.wikipedia.org/wiki/Design_by_contract, только «вывернутым наизнанку». в обоих случаях спецификации описываются в виде пред- и пост- условий, с использованием предикатных выражений:

                                          «assert value == false» или «value should be false», — не суть.

                                          но в TDD система описывается «снаружи» (ака черный ящик), а в DbC — «изнутри».

                                          BDD же отражает другой аспект — императив — т.е. требования к поведению — «в динамике». какие действия и в какой последовательности нужно делать. часто это тоже важно:

                                          Given…

                                          When I go google.com
                                          And I see search field
                                          And I type «wtf BDD»


                                          Then…

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

                                          термины юнит/функциональные/интеграционные тесты отражают ортогональный — системный — аспект. те же юнит тесты можно записывать и в стиле bdd и в стиле tdd — всё зависит от типа требований.
                                          • 0
                                            Я не говорю про тестирование как таковое, я говорю о * driven development. При использовании TDD я должен начинать разработку очередного «юнита» с написания тестов, к нему (ну, плюс «каркас» юнита, чтобы не было fatal/compile/… error), специфицирующих, по сути, его поведение. Рассуждая по аналоги, при использовании BDDя должен начинать разработку с написания behavior приложения в целом.
                                            • 0
                                              > В BDD нигде не говорится, что он предназначен именно для описания приложения в целом. Ну, и нигде не говорится, что спецификации BDD надо писать ДО приложения. Так что правильного ответа на ваш вопрос не знаю.
                                              В нашем фирме мы пишем функциональные тесты всё-таки ПОСЛЕ того, как приложение написано.
                                        • 0
                                          > Через некоторое время, было замечено…
                                          Да что вы, необходимость интеграционного тестирования была очевидна задолго до того, как слово «юнит-тесты» появилось на свет.

                                          > TDD является моделью… предполагаем, что система в общем, при этом, будет работать нормально.
                                          Да что вы, никто не предполагает, что благодаря одному только TDD система будет работать нормально. В любом описании TDD всегда говорится, что одних юнит-тестов недостаточно, и другие виды тестирования тоже необходимы.

                                          > основная идея BDD именно в обратном подходе к разработке, а не в синтаксисе.
                                          Да что вы, откуда вы это взяли?
                                          Вот цитата с википедии:
                                          «BDD is… technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. <...> It extends TDD by writing test cases in a natural language that non-programmers can read.»

                                          То есть: «BDD — это техника… которая улучшает взаимодействие между программистами, тестеровщиками и не-техническими людьми в проекте. <...> BDD расширяет TDD путём написания тест-кейсов на естественном языке, который могут читать и не-программисты.»

                                          en.wikipedia.org/wiki/Behavior_Driven_Development
                                          • +1
                                            По-моему не хватает в статье нормального введения. Потому что щас в качестве введения «разницу между TDD и BDD», а какая может быть разница если одно лежит в основе другого?
                                            Изучив историю появления BDD, можно сделать следующие выводы:
                                            1. Многие разработчики не знали как и не умели писать хорошие тесты используя TDD
                                            2. Придумывают заменить слово test на should / ensure that, называют это BDD
                                            3. BDD можно использовать с любым unit test framework-ом
                                            4. Идея развивается, придумываются спецификации, ориентированость на user cases, а не на техническую сторону
                                            5. Появляются BDD framework-и, которые больше подходят для написания функциональных тестов.
                                            5. TDD не исчез и его нужно применять (стараясь использовать стиль BDD)
                                            6. BDD — acceptance tests

                                            Так?
                                          • 0
                                            >> Через некоторое время, было замечено…
                                            >Да что вы, необходимость интеграционного тестирования была очевидна задолго до того, как слово «юнит-тесты» появилось на свет.
                                            Я говорил не про интеграционное тестирование вообщем, а про интеграционное тестирование на основе behavior тестов. И оно уж никак не могло появиться раньше TDD ;-)

                                            > То есть: «BDD — это техника… которая улучшает взаимодействие между программистами, тестеровщиками и не-техническими людьми в проекте. <...> BDD расширяет TDD путём написания тест-кейсов на естественном языке, который могут читать и не-программисты.»

                                            Да, тут Вы правы. Я смешал понятия Outside-In Development с BDD, т.к. BDD сейчас чаще всего применяется именно при OIN разработке и я до сих пор не увидел смысла применять синтаксис BDD для написания юнит-тестов. В TDD практически не возникает коммуникативных проблем, которые существуют в Outside-IN ориентированной разработке, которые и породили необходимость в новом DSL.

                                            Вообщем, извините за укол в Вашу сторону, виноват я.
                                            • 0
                                              И спасибо, что поправили. А то укоренился бы еще больше в заблуждении. ;-)
                                        • +1
                                          похоже BDD не является заменой надмножеством TDD, а дополняет его
                                          Почему, можно и заменить. А Behat/Cucumber ещё и позволяет писать спеки человечьим языком. То есть, по сути, этим может заниматься заказчик/лицо, приближённое к. Собссно, вот доклад про Behat и BDD вообще (докладчик — everzet).
                                        • +3
                                          для .Net используют specflow.org/
                                        • 0
                                          Спустя какое-то время приходит ещё один разработчик и замечает, что даже этот хороший юнит-тест на является вполне читабельным


                                          на -> не.
                                        • +1
                                          Что-то я так и не понял в чем слоь. TDD — это методология, она управляет разработкой, я не могу добавлять функциональность не придумав как ее можно протестировать т.е. не придумав ограничения (спецификации). Различия в «попытке №3» и примере с BDD только в синтаксисе (который, надо признать, намного удобнее читать) и названии тестов? Ну так все гуру тестирования говорят что у тестов должны быть осмысленные имена, и что тесты — лучшая документация для программы.

                                          Ну а вообще, спасибо за статью, обязательно поковыряюсь в библиотеках для .NET.
                                          • +1
                                            вот и я тоже не понял при чем тут TDD равно как и BDD. Возможно пример #2 относится как-то к TDD, но это осталось за пределами заметки. А вообще, из текста возникает (во всяком случае у мена) ложное впечатление, что вся разница между «типичными юнит тестами», TDD и BDD в наименовании методов и степени грануляции тестов. Также, я не смог понять обещанной «покажу разницу между TDD и BDD». Вот если бы не знал то подумал, что там вся соль в именовании тестов и их размере, что на самом деле является просто характеристикой осмысленных и читабельных тестов.
                                            • 0
                                              Почему же ложное? Вот вам определение с википедии: en.wikipedia.org/wiki/Behavior_Driven_Development

                                              «It extends TDD by writing test cases in a natural language that non-programmers can read.»

                                              Просто я не весь language показал, ну так это и не было моей целью.
                                              • 0
                                                Для России это все-таки означает русский язык, иначе представителю заказчика это вряд ли можно будет показать. Так же ему не интересна реализация тестов. Такое есть в rspec и cucumber для ruby. Пример фичи( github.com/aslakhellesoy/cucumber/blob/master/examples/i18n/ru/features/division.feature ):

                                                # language: ru
                                                Фича: Деление чисел
                                                  Поскольку деление сложный процесс и люди часто допускают ошибки
                                                  Нужно дать им возможность делить на калькуляторе
                                                
                                                  Структура сценария: Целочисленное деление
                                                    Допустим я ввожу число <делимое>
                                                    И затем ввожу число <делитель>
                                                    Если я нажимаю "/"
                                                    То результатом должно быть число <частное>
                                                
                                                  Значения:
                                                    | делимое | делитель | частное |
                                                    | 100     | 2        | 50      |
                                                    | 28      | 7        | 4       |
                                                    | 0       | 5        | 0       |
                                                • 0
                                                  А для Питона есть что нибудь похожее?
                                              • 0
                                                Прочитал статью по ссылке. Получается, что это попытка скрестить «пользовательские сценарии» c unit-тестами?

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

                                                Unit-тесты, с другой стороны, пишутся на языке программирования и могут заботиться о таких деталях о которых бизнес-пользователь ни сном, ни духом… Например, транзакции.

                                                PS: А пример приведённый в данном хабратопике кодируется с помощью регулярного выражения. И зачем его так усиленно тестировать, ума не приложу.
                                                • 0
                                                  Отлично!
                                                  Здорово, что вы пришли к такому выводу.
                                                  Посмотрев на юнит-тесты, я тоже пришел к такому выводу.
                                                  Но класс ReferenceNumber писали ДО юнит-тестов, и он получился гораздо длиннее. В этом-то и соль!
                                              • 0
                                                Я тут ответил и на Ваш вопрос habrahabr.ru/blogs/tdd/107262/#comment_3390155 ;-)
                                            • +1
                                              для .NET есть NUnut, и метод Assert.That, позволяющий писать тест, который сам объясняет что он делает. Как пример, можно взять след.:

                                              string[] s = new string[] { «30520», «30530», «C0430», «C0441», «C0440», «C0421», «70511», «70521» };
                                              Assert.That(this.helper.GetNodesAtVoltage(2f, 5f), Is.EquivalentTo(s));

                                              или вот еще один пример

                                              Assert.That(mpsh.List_Nodes, Has.Some.Property(«uz»).EqualTo(«1»));

                                              В первом случае говорится, что все содержимое полученного списка должно быть эквивалентно массиву s
                                              Во втором, что в списке есть один элемент, у которого свойство 'uz' равно '1'
                                              • +7
                                                Это совсем не BDD. Оычное, красиво оформленное TDD. Тут тестируется не поведение объекта, а сравнение результатов с эталоном и не более. А вот тестирование поведения выглядит, например, так:

                                                @article.should.receive(:save).and_return(true)
                                                put /article/1

                                                Вот тут, во время выполнения экшена на пут запрос, проверяется вызов метода save и результат этого вызова.
                                                • 0
                                                  Если хорошенькое вдуматься, то это то же самое, только чуть по-другому названное.
                                                  так ведь я как раз об этом и говорю.
                                                  • +1
                                                    Совсем не то же самое. Но это нужно осознать, вдумавшись.
                                                    • 0
                                                      Точек между словами больше… они что-то значат и вызывают? Т.е. можно просто article.should написать? Или это чисто для группировки тестов используется… мол выбрать все тесты с article, и все с receive..?

                                                      Странный синтаксис. Напишите статью тогда уж — тема же интересная
                                                      • 0
                                                        Это синтаксис Rspec. Статью писать не буду — их в интернете итак полно )

                                                        @article.should.receive(:save).and_return(true)

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

                                                        во время выполнения экшена у статьи должен вызываться метод save, который возвращает true.

                                                        Это буквальный перевод.

                                                        Мы проверяем не результат работы, а поведение экшена или любого метода вообще то есть у нас во время выполнения экшена статья должна сохраняться.
                                                        • 0
                                                          Ну так и в юнит-тесте то же самое можно проверять, используя моки.
                                                          Примерно так это выглядит на Java:

                                                          article = mock(Article.class);
                                                          put(article, 1);
                                                          verify(article).save().shouldBeEqual(1);
                                                          • 0
                                                            verify(article).save().shouldBeEqual(1);

                                                            В это сточке происходит вызов метода save и его сверка с 1? Или save произошёл раньше, внутри метода put?
                                                            • 0
                                                              save должен был произойти раньше, внутри метода put.
                                                              А verify(article).save() убеждается, что он действительно произошёл.

                                                              Здесь есть больше примеров:
                                                              mockito.org/
                                                              habrahabr.ru/blogs/java/72617/

                                                              В RSpec просто есть встроенная аналогичная функциональность, которая, похоже, выглядит получше и покороче благодаря тому, что язык Ruby сам по себе лаконичнее.
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                  • +1
                                                    ScalaTest не только для Scala, можно и для проектов на Java юзать.

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

                                                    Самое читаемое