Главная страница
Навигация по странице:

  • Предметно-ориентированный язык тестирования

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница18 из 49
    1   ...   14   15   16   17   18   19   20   21   ...   49
    149
    cationsController
    . Кроме того, сразу же после появления
    TransmitterAPI
    можно создать граничные тесты для проверки правильности использования API .
    Рис . 8 .2 . Прогнозирование интерфейса передатчика
    Чистые границы
    На границах происходит много интересного . В частности, стоит уделить особое внимание изменениям . В хорошей программной архитектуре внесение изменений обходится без значительных затрат и усилий по переработке . Если в продукте используется код, находящийся вне нашего контроля, примите особые меры по защите капиталовложений и позаботьтесь о том, чтобы будущие изменения обходились не слишком дорого .
    Для граничного кода необходимо четкое разделение сторон и тесты, опреде- ляющие ожидания пользователя . Постарайтесь, чтобы ваш код поменьше знал о специфических подробностях реализации стороннего кода . Лучше зависеть от того, что находится под вашим контролем, чем от тех факторов, которые вы не контролируете (а то, чего доброго, они начнут контролировать вас) .
    Чтобы границы со сторонним кодом не создавали проблем в наших проектах, мы сводим к минимуму количество обращений к ним . Для этого можно восполь- зоваться обертками, как в примере с
    Map
    , или реализовать паттерн АДАПТЕР для согласования нашего идеального интерфейса с реальным, полученным от разработчиков . В обоих вариантах код становится более выразительным, обе- спечивается внутренняя согласованность обращений через границы, а изменение стороннего кода требует меньших затрат на сопровождение .
    литература
    [BeckTDD]: Test Driven Development, Kent Beck, Addison-Wesley, 2003 .
    [GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al ., Addison-Wesley, 1996 .
    [WELC]: Working Effectively with Legacy Code, Addison-Wesley, 2004 .
    149
    Fake

    Модульные тесты
    За последние десять лет наша профессия прошла долгий путь . В 1997 году никто не слыхал о методологии TDD (Test Driven Development, то есть «разработка че- рез тестирование») . Для подавляющего большинства разработчиков модульные тесты представляли собой короткие фрагменты временного кода, при помощи которого мы убеждались в том, что наши программы «работают» . Мы тщательно выписывали свои классы и методы, а потом подмешивали специализированный код для их тестирования . Как правило, при этом использовалась какая-нибудь несложная управляющая программа, которая позволяла вручную взаимодейство- вать с тестируемым кодом .
    Помню, в середине 90-х я написал программу на C++ для встроенной системы реального времени . Программа представляла собой простой таймер со следующей сигнатурой:
    void Timer::ScheduleCommand(Command* theCommand, int milliseconds)
    9
    150

    Три закона TTD
    151
    Идея была проста; метод
    Execute класса
    Command выполнялся в новом программ- ном потоке с заданной задержкой в миллисекундах . Оставалось понять, как его тестировать .
    Я соорудил простую управляющую программу, которая прослушивала события клавиатуры . Каждый раз, когда на клавиатуре вводился символ, программа планировала выполнение команды, повторяющей этот же символ пять секунд спустя . Затем я настучал на клавиатуре ритмичную мелодию и подождал, пока эта мелодия «появится» на экране спустя пять секунд .
    «Мне… нужна такая девушка… как та… которую нашел мой старый добрый папа…»
    Я напевал эту мелодию, нажимая клавишу « .», а потом пропел ее снова, когда точки начали появляться на экране .
    И это был весь тест! Я убедился в том, что программа работает, показал ее своим коллегам и выкинул тестовый код .
    Как я уже говорил, наша профессия прошла долгий путь . Сейчас я бы написал комплексный тест, проверяющий, что все углы и закоулки моего кода работают именно так, как положено . Я бы изолировал свой код от операционной системы, не полагаясь на стандартное выполнение по таймеру . Я бы самостоятельно реа- лизовал хронометраж, чтобы тестирование проходило под моим полным контро- лем . Запланированные команды устанавливали бы логические флаги, а потом тестовый код выполнял бы мою программу в пошаговом режиме, наблюдая за состоянием флагов и их переходами из ложного состояния в истинное по про- хождении нужного времени .
    Когда у меня накопился бы пакет тестов, я бы позаботился о том, чтобы эти тесты были удобными для любого другого программиста, которому потребуется работать с моим кодом . Я бы проследил за тем, чтобы тесты и код поставлялись вместе, в одном исходном пакете .
    Да, мы прошли долгий путь; но дорога еще не пройдена до конца . Движения гибких методологий и TDD поощряют многих программистов писать автома- тизированные модульные тесты, а их ряды ежедневно пополняются новыми сторонниками . Однако в лихорадочном стремлении интегрировать тестирование в свою работу многие программисты упускают более тонкие и важные аспекты написания хороших тестов .
    три закона TTD
    В наши дни каждому известно, что по требованиям методологии TDD модуль- ные тесты должны писаться заранее, еще до написания кода продукта . Но это правило — всего лишь верхушка айсберга . Рассмотрим следующие три закона
    1
    :
    1
    Professionalism and Test-Driven Development, Robert C . Martin, Object Mentor, IEEE Software,
    May/June 2007 (Vol . 24, No . 3), pp . 32–36; http://doi .ieeecomputersociety .org/10 .1109/MS .2007 .85 151

    152
    Глава 9 . Модульные тесты
    Первый закон. Не пишите код продукта, пока не напишете отказной модульный тест .
    Второй закон. Не пишите модульный тест в объеме большем, чем необходимо для отказа . Невозможность компиляции является отказом .
    Третий закон. Не пишите код продукта в объем большем, чем необходимо для прохождения текущего отказного теста .
    Эти три закона устанавливают рамки рабочего цикла, длительность которого составляет, вероятно, около 30 секунд . Тесты и код продукта пишутся вместе, а тесты на несколько секунд опережают код продукта .
    При такой организации работы мы пишем десятки тестов ежедневно, сотни те- стов ежемесячно, тысячи тестов ежегодно . При такой организации работы тесты охватывают практически все аспекты кода продукта . Громадный объем тестов, сравнимый с объемом самого кода продукта, может создать немало организаци- онных проблем .
    О чистоте тестов
    Несколько лет назад мне предложили заняться обучением группы, которая реши- ла, что тестовый код не должен соответствовать тем же стандартам качества, что и код продукта . Участники группы сознательно разрешили друг другу нарушать правила в модульных тестах . «На скорую руку» — вот каким девизом они руко- водствовались . Разумно выбирать имена переменных не обязательно, короткие и содержательные тестовые функции не обязательны . Качественно проектировать тестовый код, организовать его продуманное логическое деление не обязательно .
    Тестовый код работает, охватывает код продукта — и этого вполне достаточно .
    Пожалуй, некоторые читатели сочувственно отнесутся к этому решению . Воз- можно, кто-то в прошлом писал тесты наподобие тех, которые я написал для своего класса
    Timer
    . Примитивные «временные» тесты отделены огромным рас- стоянием от пакетов автоматизированного модульного тестирования . Многие программисты (как и та группа, в которой я преподавал) полагают, что тесты «на скорую руку» — лучше, чем полное отсутствие тестов .
    Но на самом деле тесты «на скорую руку» равносильны полному отсутствию тестов, если не хуже . Дело в том, что тесты должны изменяться по мере развития кода продукта . Чем примитивнее тесты, тем труднее их изменять . Если тестовый код сильно запутан, то может оказаться, что написание нового кода продукта займет меньше времени, чем попытки втиснуть новые тесты в обновленный пакет .
    При изменении кода продукта старые тесты перестают проходить, а неразбериха в тестовом коде не позволяет быстро разобраться с возникшими проблемами . Та- ким образом, тесты начинают рассматриваться как постоянно растущий балласт .
    От версии к версии затраты на сопровождение тестового пакета непрерывно росли . В конечном итоге тесты стали главной причиной для жалоб разработчи-
    152

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

    154
    Глава 9 . Модульные тесты
    А причина заключается в том, что тесты обеспечивают возможность внесения изменения .
    Таким образом, если ваши тесты недостаточно чисты и проработаны, ваши воз- можности по изменению кода сокращаются и вы лишаетесь возможности улуч- шения структуры кода . Некачественные тесты приводит к некачественному коду продукта . В конечном итоге тестирование вообще становятся невозможным, и код продукта начинает загнивать .
    Чистые тесты
    Какими отличительными признаками характеризуется чистый тест? Тремя: удо- бочитаемостью, удобочитаемостью и удобочитаемостью . Вероятно, удобочитае- мость в модульных тестах играет еще более важную роль, чем в коде продукта .
    Что делает тестовый код удобочитаемым? То же, что делает удобочитаемым любой другой код: ясность, простота и выразительность . В тестовом коде не- обходимо передать максимум информации минимумом выразительных средств .
    В листинге 9 .1 приведен фрагмент кода из проекта FitNesse . Эти три теста труд- ны для понимания; несомненно, их можно усовершенствовать . Прежде всего, повторные вызовы addPage и assertSubString содержат огромное количество по- вторяющегося кода [G5] . Что еще важнее, код просто забит второстепенными подробностями, снижающими выразительность теста .
    листинг 9 .1 . SerializedPageResponderTest.java public void testGetPageHieratchyAsXml() throws Exception
    {
    crawler.addPage(root, PathParser.parse("PageOne"));
    crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
    crawler.addPage(root, PathParser.parse("PageTwo"));
    request.setResource("root");
    request.addInput("type", "pages");
    Responder responder = new SerializedPageResponder();
    SimpleResponse response =
    (SimpleResponse) responder.makeResponse(
    new FitNesseContext(root), request);
    String xml = response.getContent();
    assertEquals("text/xml", response.getContentType());
    assertSubString("PageOne", xml);
    assertSubString("PageTwo", xml);
    assertSubString("ChildOne", xml);
    }
    public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() throws Exception
    154

    Чистые тесты
    155
    {
    WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne"));
    crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
    crawler.addPage(root, PathParser.parse("PageTwo"));
    PageData data = pageOne.getData();
    WikiPageProperties properties = data.getProperties();
    WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME);
    symLinks.set("SymPage", "PageTwo");
    pageOne.commit(data);
    request.setResource("root");
    request.addInput("type", "pages");
    Responder responder = new SerializedPageResponder();
    SimpleResponse response =
    (SimpleResponse) responder.makeResponse(
    new FitNesseContext(root), request);
    String xml = response.getContent();
    assertEquals("text/xml", response.getContentType());
    assertSubString("PageOne", xml);
    assertSubString("PageTwo", xml);
    assertSubString("ChildOne", xml);
    assertNotSubString("SymPage", xml);
    }
    public void testGetDataAsHtml() throws Exception
    {
    crawler.addPage(root, PathParser.parse("TestPageOne"), "test page");
    request.setResource("TestPageOne");
    request.addInput("type", "data");
    Responder responder = new SerializedPageResponder();
    SimpleResponse response =
    (SimpleResponse) responder.makeResponse(
    new FitNesseContext(root), request);
    String xml = response.getContent();
    assertEquals("text/xml", response.getContentType());
    assertSubString("test page", xml);
    assertSubString("}
    Например, присмотритесь к вызовам
    PathParser
    , преобразующим строки в экземпля- ры
    PagePath
    , используемые обходчиками (crawlers) . Это преобразование абсолютно несущественно для целей тестирования и только затемняет намерения автора .
    Второстепенные подробности, окружающие создание ответчика, а также сбор и пре- образование ответа тоже представляют собой обычный шум . Также обратите внима- ние на неуклюжий способ построения URL-адреса запроса из ресурса и аргумента .
    (Я участвовал в написании этого кода, поэтому считаю, что вправе критиковать его .)
    155

    156
    Глава 9 . Модульные тесты
    В общем, этот код не предназначался для чтения . На несчастного читателя об- рушивается целый водопад мелочей, в которых необходимо разобраться, чтобы уловить в тестах хоть какой-то смысл .
    Теперь рассмотрим усовершенствованные тесты в листинге 9 .2 . Они делают абсолютно то же самое, но код был переработан в более ясную и выразительную форму .
    листинг 9 .2 . SerializedPageResponderTest.java (переработанная версия)
    public void testGetPageHierarchyAsXml() throws Exception {
    makePages("PageOne", "PageOne.ChildOne", "PageTwo");
    submitRequest("root", "type:pages");
    assertResponseIsXML();
    assertResponseContains(
    "PageOne", "PageTwo", "ChildOne"
    );
    }
    public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception {
    WikiPage page = makePage("PageOne");
    makePages("PageOne.ChildOne", "PageTwo");
    addLinkTo(page, "PageTwo", "SymPage");
    submitRequest("root", "type:pages");
    assertResponseIsXML();
    assertResponseContains(
    "PageOne", "PageTwo", "ChildOne"
    );
    assertResponseDoesNotContain("SymPage");
    }
    public void testGetDataAsXml() throws Exception {
    makePageWithContent("TestPageOne", "test page");
    submitRequest("TestPageOne", "type:data");
    assertResponseIsXML();
    assertResponseContains("test page", "}
    В структуре тестов очевидно воплощен паттерн ПОСТРОЕНИЕ-ОПЕРАЦИИ-
    ПРОВЕРКА
    1
    . Каждый тест четко делится на три части . Первая часть строит те- стовые данные, вторая часть выполняет операции с тестовыми данными, а третья часть проверяет, что операция привела к ожидаемым результатам .
    1
    http://tnesse .org/FitNesse .AcceptanceTestPatterns .
    156

    Чистые тесты
    157
    Обратите внимание: большая часть раздражающих мелочей исчезла . Тесты не делают ничего лишнего, и в них используются только действительно необходи- мые типы данных и функции .
    Любой программист, читающий эти тесты, очень быстро разберется в том, что они делают, не сбиваясь с пути и не увязнув в лишних подробностях .
    Предметно-ориентированный
    язык тестирования
    Тесты в листинге 9 .2 демонстрируют методику построения предметно-ориен- тированного языка для программирования тестов . Вместо вызова функций API, используемых программистами для манипуляций с системой, мы строим набор функций и служебных программ, использующих API; это упрощает написание и чтение тестов . Наши функции и служебные программы образуют специали- зированный API, то есть по сути — язык тестирования, который программисты используют для упрощения работы над тестами, а также чтобы помочь другим программистам, которые будут читать эти тесты позднее .
    Тестовый API не проектируется заранее; он развивается на базе многократной переработки тестового кода, перегруженного ненужными подробностями . По аналогии с тем, как я переработал листинг 9 .1 в листинг 9 .2, дисциплинированные разработчики перерабатывают свой тестовый код в более лаконичные и вырази- тельные формы .
    1   ...   14   15   16   17   18   19   20   21   ...   49


    написать администратору сайта