Правильная ссылка на эту страницу
http://az-design.ru/Projects/AzBook/src/005/01TS050.shtml

Глава 5. ТЕСТИРОВАНИЕ (ИСПЫТАНИЕ) ПРОГРАММ

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

       Если тестирование программы проводится интуитивно, по принципу невмешательства в работу программы и без какого-либо четкого плана испытаний, то этот процесс можно назвать искусством. Если же тестированию предшествует тщательный подбор данных для контрольных примеров и заблаговременный выбор элементов программы, подлежащих проверке, а само оно выполняется последовательно и аккуратно, то тестирование становится наукой.
       Разработка алгоритмов и программирование стали ремеслом, которым можно овладеть. Теперь пришло время сделать то же самое в отношении тестирования программ, покончив с подходом к нему как к искусству. Это позволит исключить повторения испытаний, обеспечивать контроль за их эффективным проведением и четко определять момент завершения тестирования.
       Испытания любой системы всегда представляют собой один из наиболее ответственных этапов ее разработки и часто бывают связаны с наибольшими трудностями и наибольшими потерями времени. Отладка и тестирование — это два четко различимых и непохожих друг на друга этапа. В первом случае происходит устранение синтаксических ошибок и явных ошибок кодирования. Во втором случае имеют дело с программой, не содержащей отмеченных ошибок, которая выдает какие-то правильные результаты. Основная цель выделения отладки и тестирования как отдельных этапов создания программы заключается в том, чтобы обратить внимание на обязательность обеих стадий и на необходимость специального планирования временных затрат по каждой из них в отдельности.
       Никогда не делайте вывода, что программа правильна, лишь на том основании, что она не отвергнута машиной, полностью транслирована и выдала численные результаты. Ведь все, что достигнуто в данном случае,— это получение некой выходной информации, не обязательно правильной. В программе все еще может оставаться большое количество логических ошибок, а между тем задача, которая ставится при написании программы, — это не просто получение ответов, но получение правильных ответов. Поэтому обычно бывает необходима “(ручная” проверка машинных результатов. После того как отладка полностью завершена, даже в программе опытного программиста существует примерно одна ошибка на 20—30 написанных: операторов. Эти ошибки могут быть как катастрофическими по своим последствиям, так и незначительными и могут быть связаны как с неправильным алгоритмом, так и с несущественными ошибками кодирования. Таким образом, отлаженная программа — это программа, для которой просто не нашлось подходящего набора тестовых данных, чтобы привести ее к отказу.

5.1. НЕБРЕЖНОСТЬ НАЧИНАЮЩИХ ПРОГРАММИСТОВ

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

5.2. ПРОБЛЕМА ЖИВУЧЕСТИ ПРОГРАММЫ

       Цель тестирования всякой программы состоит в том, чтобы убедиться, что она решает действительно ту задачу, для которой предназначена, и выдает правильный ответ при любых условиях. Последнее требование обычно связывают с живучестью (robustness)1 {Этот термин обычно переводят как “робастность”. — Прим. перев.}) программы и говорят, что она не обладает этим свойством, если легко перестает формировать правильные результаты. Живучесть программ со временем возрастает,, так как в процессе их эксплуатации отказы обнаруживаются и устраняются.
       Иногда программа может казаться работоспособной в течение многих месяцев и даже лет, пока не становится очевидным, что в каком-то ее блоке имеется серьезная ошибка. Теоретически считается, что большие программы никогда не бывают полностью свободными от ошибок, поэтому никогда не следует жертвовать качеством тестирования во имя мнимой экономии времени и денег. Неправильная программа не имеет никакой ценности, и лучше вообще не иметь никакой программы, чем принимать на ее основе ошибочные решения, которые могут стоить очень дорого. Достаточно представить себе, во что обойдется, например, полное повторение обработки годового объема входных данных из-за недостаточно тщательно проверенной программы, чтобы ощутить необходимость ее полного тестирования.
       Живучая программа — это такая программа, которая продолжает сохранять свою работоспособность, несмотря на рассеянность операторов подготовки данных, небрежность персонала, ответственного за контроль информации, и безграмотные действия операторов ЭВМ. Тот, кто принимает во внимание закон Мэрфи, который, естественно, распространяется и на программное обеспечение, будет создавать программы, обладающие свойством живучести.
       Разумеется, невозможно дать абсолютно точные указания относительно того, как надо испытывать программные средства, однако ряд практических рекомендаций и принципов, излагаемых в последующих разделах, может служить хорошим руководством по тестированию программ.

5.3. ОБЩИЕ РЕКОМЕНДАЦИИ

       Наиболее важный принцип, относящийся к тестированию программ, состоит в том, чтобы думать об этой стадии еще на этапе написания программы. Следует постоянно задаваться вопросом: как будет тестироваться данный сегмент? Если ответ на вопрос о способе тестирования программы неясен, она должна быть либо переписана заново, либо разбита на модули. Нарушение этого принципа неизбежно приводит к тому, что программу вообще не удается проверить до конца и рано или поздно при очередном рабочем прогоне она откажет.
       К сожалению, при написании программ о тестировании не задумываются. Стараются сделать их эффективными, удобочитаемыми, мобильными и т.п., но никак не полностью тестируемыми. А между тем средства тестирования должны заблаговременно встраиваться в программу. Проектировать программу следует таким образом, чтобы процесс разработки легко контролировался; при этом особое внимание необходимо обращать на простоту и ясность программы, выбирая каждый раз такой способ ее кодирования, чтобы всегда существовала возможность проверки соответствия программы своему назначению.
       Сборка и перекомпоновка программной колоды должны производиться непосредственно перед каждым тестовым прогоном. Загрузочными модулями пользоваться не следует, поскольку исходная программа находится в состоянии непрерывного изменения. В такой ситуации применение программы-загрузчика может легко вызвать путаницу, потому что трудно установить, какой загрузочный модуль какой версии программы соответствует.
       Если для целей тестирования в исходную программу необходимо вставить дополнительные карты, они должны отличаться по цвету от основных. Кроме того, в каждой тестовой строке программы целесообразно перфорировать в колонках 73—80 признак TEST. Тогда будут существовать одновременно два напоминания о том, что тестовые строки подлежат исключению из готовой программы.
       На самых ранних этапах разработки программы необходимо сразу установить контроль за ее качеством. Для этого программа должна проверяться опытными программистами, в обязанности которых входит выявление пропущенных блоков, нерационально запрограммированных частей и отклонений от технических требований к программе. Раннее обнаружение потенциальных ошибок приносит несомненную выгоду автору программы, помогая ему избежать серьезных затруднений на стадии тестирования.
       Язык программирования должен выбираться соответственно решаемой задаче. Учет этого фактора, как и надлежащий выбор алгоритма, облегчает процесс тестирования программы.

5.4. НЕОБХОДИМАЯ ПОЛНОТА ТЕСТИРОВАНИЯ

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

5.4.1. ЧИСЛО ТЕСТОВЫХ ПРОГОНОВ

       Само по себе число тестовых прогонов программы зачастую не является определяющим фактором. Дело в том, что программам не присуще явление старения, и поэтому, если некоторая программа выполняет сложение нескольких чисел правильно, то она будет выполнять его правильно и для других чисел, сколько раз ни производилась бы эта операция. Точно так же само по себе количество проверок не может гарантировать полноты проведенных испытаний. Если, например, вы обрабатываете 100 тестовых записей, содержащих некоторые реальные входные данные, то 90 из них будут вести себя одинаково, т.е. при этом проверяется лишь основная логическая ветвь программы. Остальные 10 записей, вероятно, позволят дополнительно испытать еще какие-нибудь две ветви, и, таким образом, 100 обработанных записей в данном случае представляют собой лишь три контрольных примера.
       Еще одна причина нецелесообразности использования излишне больших объемов тестовых данных заключается в том, что никому не хочется тратить время на анализ результатов 1000 испытаний только для того, чтобы убедиться в их правильности. Поэтому никакой выгоды из большого числа испытаний не извлекается.
       Очень часто прогон большого количества тестов просто позволяет убедиться, что программа хорошо выполняет одну и ту же совокупность операций над различными числами. Однако цель тестирования состоит совсем не в этом, а в том, чтобы гарантировать живучесть программы. Мы всегда хотим, чтобы очередной тестовый прогон контролировал нечто такое, что не было проверено в предыдущих прогонах. И задача тестирования заключается в том, чтобы создать для программы предельно напряженный режим работы. А это требует от программиста хорошего воображения и недоверчивого отношения к своей программе.

Обходитесь минимальным количеством контрольных примеров.

5.5. НЕВОЗМОЖНОСТЬ ИСЧЕРПЫВАЮЩЕГО ТЕСТИРОВАНИЯ

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

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

232х232 = 264

       их различных сочетаний. При таком количестве ситуаций программу, работающую одну миллисекунду, пришлось бы тестировать в течение 50 млрд. лет1 {Подсчет автора неточен: при круглосуточной работе в автоматическом режиме понадобится всего лишь 300 млн. лет. — Прим. перев.}).


Рис.5.2

       Мы убедились, таким образом, что проверка всех возможных комбинаций входов нереализуема, но можно ли проверить все логические ветви? Если обратиться, например, к блок-схеме, показанной на рис. 5.2, то здесь прослеживаются три самостоятельных логических пути. Совокупность двух таких сегментов (рис. 5.3) образует девять путей (отыщите их), а в случае, когда программа, соответствующая блок-схеме рис. 5.3, работает дважды, их будет уже 9X9. Если же предположить, что эта программа является частью цикла, выполняемого 10 раз, то количество возможных логических путей станет равным 910. Таким образом, даже в таком простом примере исчерпывающее тестирование неосуществимо. Наконец, следует иметь в виду что если бы и удалось провести полное тестирование программы, то необходимое для этого машинное время оказалось бы, потраченным зря, поскольку неизвестно, какими способами можно было бы вручную проверить достоверность всех выходных результатов.


Рис.5.3

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

Учитывая, что исчерпывающее тестирование невозможно, испытывайте программу разумно.

5.6. ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ К ТЕСТИРОВАНИЮ

       Тестовые испытания программы представляют собой процесс, направленный на то, чтобы гарантировать ее нормальную работу во всех предполагаемых практических ситуациях. При этом должны осуществляться два вида испытаний: на соответствие созданной программы поставленной задаче и на правильность ее функционирования.
       Первый вид испытаний невозможно провести, если задача определена не полностью, нечетко и непоследовательно. Все результаты, соответствующие той или иной совокупности входов, должны быть предусмотрены. Требование четкости означает, что как постановщик задачи, так и программист должны ясно представлять себе, чего они хотят добиться от программы,
       Требование последовательности предполагает недопустимость жакой-либо неопределенности. Когда программная спецификация обладает свойствами полноты, четкости и последовательности, то человек, проводящий испытания программы, может рассматривать ее как “черный ящик” (не заботясь о ее внутренней структуре).
       Один из возможных способов контроля за соответствием спецификации программе состоит в том, чтобы поручать постановщику задачи формировать тестовые данные и указывать соответствующие им правильные результаты. Эта мера позволяет, во-первых, гарантировать, что постановщики задач хорошо знают, чего хотят, и, во-вторых, проверять, правильно ли программист понимает задачу.
       Ряд проверок можно провести еще до начала какой-либо работы по кодированию программы. Например, некоторые ошибки алгоритма могут быть выявлены и легко устранены в результате “ручного моделирования” логики работы программы.
       В подходе к тестированию программ возможны две крайности. Первая состоит в том, чтобы глубоко изучить программу и использовать ее в качестве основы для проведения испытаний. Однако в данном случае, если текст программы не соответствует ее спецификации, этот факт никогда не обнаружится, и тот, кто испытывает программу, будет вынужден для организации тестирования обращаться к ее функциональному описанию.
       Вторая крайность имеет место тогда, когда для тестирования используется только программная спецификация, а сама программа рассматривается как “черный ящик”. Предположим, перед вами стоит задача разработать тестовые данные для программы, которая считывает некоторое целое число, обозначающее радиус круга, и затем вычисляет площадь последнего. Придумайте для этой программы контрольные примеры, считая программу “черным ящиком”.
       А теперь допустим, выдвигается требование, чтобы программа выдавала результаты в следующем виде: если радиус равен единице, то площадь равна ...; если радиус равен двум, то площадь равна ...; и т.д. Здесь уже контрольные примеры, разработанные вами для предыдущего случая, не смогут обеспечить адекватной проверки программы. Дело в том, что для организации такой проверки необходимо совместное использование и программной спецификации и текста программы.

5.7. НЕОБХОДИМОСТЬ РАННЕГО ТЕСТИРОВАНИЯ

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

Начинайте тестирование как можно раньше.

       Тестовые проверки следует осуществлять еще на этапе разработки алгоритма программы, но на каком бы уровне они ни проводились, следует иметь в виду, что результатом тестирования может стать частичное или даже полное перепроектирование системы.
       Присутствие в составе бригады разработчиков системы лиц, занимающихся испытаниями программного обеспечения (назовем их испытателями), необходимо по двум главным причинам. Во-первых, испытатели способствуют более четкому формулированию требований к проектируемой системе, поскольку смотрят на эти требования с несколько иной точки зрения, чем разработчики. В результате испытатели часто обнаруживают ошибки в алгоритме и неполноту спецификаций. Тем самым они помогают улучшить программные средства и фактически ускоряют разработку проекта.
       Во-вторых, сами масштабы работы, которую предстоит совершить испытателям, делают целесообразным их подключение к разработке уже на этапе проектирования. Испытатели должны подготовить контрольные примеры с известными результатами; сформированные контрольные примеры должны быть затем записаны на каком-либо носителе информации для обеспечения возможности их обработки. Вся эта подготовительная работа может и должна выполняться в процессе разработки программы. И если тестирование осуществляется способом сверху вниз, тестовые данные потребуются очень скоро.

5.8. ПРОВЕРКА ПРАВИЛЬНОСТИ ПРОЕКТНЫХ РЕШЕНИЙ

       В случае когда мы не знаем, для чего предназначена программа и каким требованиям она должна удовлетворять, никакое, даже самое большое количество тестовых проверок не может гарантировать ее правильной работы. Наоборот, хорошо продуманные требования могут сэкономить массу времени на стадии испытаний. Эта заключительная стадия разработки системны никак не предназначена для штопания дыр в проекте, поскольку к ее началу большинство концепций бывает уже воплощено в программы, и, если первоначальный замысел неверен, все эти программы могут оказаться бракованными. Во избежание такого положения дел правильность постановки задачи должна быть тщательно проверена еще до начала программирования.
       Для выполнения указанной проверки необходимо убедиться, что группа тестирования, взяв описание задачи, может самостоятельно разработать на его основе адекватное множество контрольных примеров, рассматривая программу как “черный ящик”. Обычно с первого раза этого сделать не удается, спецификация задачи должна быть переделана” так как если она непонятна группе тестирования, то вряд ли в ней сумеет разобраться кто-либо еще. Иногда случается, что разработчики, пытаясь улучшить описание задачи, обнаруживают, что они не в состоянии это осуществить. Такая ситуация говорит о недостаточно глубоком понимании проблемы в целом и одновременно служит предупреждением о предстоящих неприятностях, которые ожидают разработчиков в случае, если постановка задачи не будет уточнена.
       Целый ряд исследований показал, что источником примерно половины всех ошибок в программном обеспечении больших систем являются проектные недоработки. Следовательно, именно на стадии проектирования следует ликвидировать пагубные последствия программных ошибок, которые могут проникать в проект либо по вине разработчиков, либо из-за расплывчатой формулировки целей заказчиками программного обеспечения. Однако, каковы бы ни были причины, важно, чтобы они устранялись в самом начале их возникновения.

5.8.1. СКВОЗНОЙ СТРУКТУРНЫЙ КОНТРОЛЬ

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

Прежде всего проводите ручную проверку.

       Проверка проектных решений должна охватывать лишь верхние уровни разработки, а именно контролировать правильность системной логики и сопряжений модулей. Когда проект утвержден, он не должен содержать ошибок, исправление которых требует корректировки сразу нескольких модулей программы. Такие ошибки должны выявляться на этапе проектирования.
       Еще один широко распространенный способ проверки правильности проектных решений — это предварительное написание программ системы на языках APL или БЕЙСИК. Хотя такой подход связан с довольно большим объемом дополнительной работы, это окупается в случае необходимости внесения изменений в проект.

Старайтесь проверять правильность принципов построения системы на ее простом варианте.

       Структурный контроль должен тщательно планироваться самим разработчиком с учетом того, что необходимо иметь от четырех до шести рецензентов в качестве которых обычно выступают испытатели, проектировщики и документалисты. Предполагаемые рецензенты должны получать материалы за 4—6 дней до проведения совещания по их рассмотрению, чтобы иметь возможность ознакомиться с ними и быть готовыми к обсуждению любых проблем. При проведении совещания всегда следует руководствоваться определенной целью и ограничиваться по времени двумя часами. Если этого времени недостаточно, то лучше запланировать дополнительную встречу. Председательствующий должен обеспечить рабочую обстановку и составление полного списка ошибок, неувязок и противоречий, которые отнюдь не должны быть устранены сразу же, в ходе совещания. Разработчик обязан заняться этим позже и сообщить о принятых мерах.
       В начале совещания по проверке структуры рецензенты характеризуют степень завершенности проекта в целом, точность выполнения выдвинутых требований и качество проектных решений. Затем разработчик делает краткий обзор всех элементов программного обеспечения, При этом возможна демонстрация работы программ на контрольных примерах, результаты которых подвергаются групповому анализу. По окончании совещания председательствующий вручает каждому члену группы список выявленных проблем, требующих решения. Разработчик обязан затем разрешить отмеченные проблемы и довести до сведения рецензентов, какие были предприняты действия по их замечаниям.
       Необходимо, однако, установить допустимое количество обнаруживаемых ошибок. Это означает, что, если выявлено 5—10 ошибок, сквозной контроль должен быть приостановлен для проведения тщательного анализа проекта и устранения основных ошибок. Только после этого имеет смысл продолжить сквозной контроль. Нецелесообразно тратить время на выявление двух-трех десятков ошибок, поскольку такое их обилие будет говорить лишь о том, что весь проект нуждается в переделке и его частичная корректировка не спасет положения. Стратегия проведения сквозного структурного контроля хорошо описана в работе [9].

5.9. МЕТОДЫ ТЕСТИРОВАНИЯ

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

5.9.1. ТЕСТИРОВАНИЕ СНИЗУ ВВЕРХ

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

5.9.2. ТЕСТИРОВАНИЕ СВЕРХУ ВНИЗ

       Этот метод тестирования является дополнительным этапом процесса проектирования сверху вниз, сквозного контроля и кодирования сверху вниз. При таком методе разработки программного обеспечения вначале пишется основная программа, а незапрограммированные модули более низкого уровня заменяются имитирующими их подыгрывающими программами. Такая “скелетная” программа тем не менее может быть испытана в отсутствие вызываемых подпрограмм и даже без каких-либо данных. Эта проверка должна выявить неточности языка управления заданиями, который часто бывает далеко не тривиальным. Следующий шаг должен состоять в добавлении модуля, генерирующего входные данные, — это может быть либо модуль ввода, либо некоторый вспомогательный модуль, заменяющий его до момента окончания программирования последнего. Вслед за этим испытания проводятся с использованием каких-либо простых входных данных.

5.9.3. ПРЕИМУЩЕСТВА ТЕСТИРОВАНИЯ СВЕРХУ ВНИЗ

       По мере того как “скелет” программы “обрастает” новыми модулями, должны добавляться и новые тестовые данные, объем которых увеличивается постепенно, одновременно с разрастанием программы. В результате появляется возможность накапливать тестовые данные вместо раздельного их формирования для каждого модуля.
       Еще одним плюсом тестирования по методу сверху вниз является то, что стержневая логика программы тестируется на раннем этапе, и эта проверка повторяется многократно с добавлением новых модулей. При тестировании же снизу вверх стержневая логика программы испытывается в последнюю очередь; в этом случае при обнаружении в ней ошибки могут быть неверными прошедшие предыдущие проверки элементы более низких уровней, и огромная работа может оказаться выполненной напрасно.
       Обычным делом в разработке систем является такая ситуация, когда две группы программистов разрабатывают две различные подсистемы, которые должны взаимодействовать друг с другом или сходиться в верхнем узле. При использовании метода снизу вверх место связи подсистем испытывается в последнюю очередь. Метод сверху вниз позволяет проверить взаимодействие подсистем еще до того, как будут готовы модули нижних уровней.
       Немаловажным преимуществом метода сверху вниз является распределенное тестирование, проводимое фактически на протяжении всей разработки проекта, когда модули тестируются по мере их добавления. Наоборот, при использовании метода снизу вверх вся работа по тестированию скапливается к моменту окончания проектирования. Очень часто в этот момент бывает настолько сильным внешнее давление на разработчиков с требованием быстрейшего завершения проекта, что тестирование делается кое-как, а это, как правило, приводит к катастрофическим последствиям, когда неиспытанная система выходит из строя.

Старайтесь применять тестирование по методу сверху вниз.

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

5.9.4. КАКОЙ МЕТОД ЛУЧШЕ?

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

5.10. ТЕСТОВЫЕ ДАННЫЕ

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

11,11+22,22+33,33,

       она, несомненно, столь же успешно будет выполнять и сложение

12,56 + 45,92 + 34,79.

       Простые тестовые данные облегчают ручной контроль результатов, а сами контрольные примеры предназначаются для проверки правильности работы программы, а совсем не для того, чтобы испытывать способность машины к выполнению арифметических действий.
       Хотя для тестирования и рекомендуется использовать простые данные, желательно также, чтобы каждый тестовый прогон давал максимально возможное количество информации, а общее число прогонов было незначительным. Однако при этом результаты испытаний отдельных элементов программы должны быть хорошо различимы, чтобы в случае ошибок не возникало путаницы. Хорошими следует считать такие тесты, которые помогают установить первопричину ошибок.
       В некоторых случаях тестирования требуется производить “миниатюризацию” программы, т.е. разумно сокращать объем данных по сравнению с реальным. Можно, например, представить себе программу, которая работает обычно с матрицей размерностью 50*50, однако проверка соответствующих вычислений вручную при такой размерности неосуществима. Поэтому в качестве тестовых данных может быть использована матрица 5*5. Однако если такая миниатюризация программы требует внесения в нее каких-либо изменений, то могут возникнуть две нежелательные ситуации: либо существующие в программе ошибки в результате упрощающих изменений могут стать неявными или временно исчезнуть, либо могут появиться новые ошибки.
       Точно так же, если некоторая подпрограмма работает в цикле 5 раз, она, безусловно, сможет работать и 105 раз (лишь бы хватало зарезервированного объема памяти). Часто бывает интересно посмотреть, какие результаты выдает программа при настройке ее на нуль, один или отрицательное число циклов; однако в последнем случае требуется особая осторожность. Обычно, если некоторый цикл предполагает выполнение N итераций, то наиболее распространенной ошибкой является организация N—1 или N+1 итераций.
       Усложнение тестовых данных должно происходить постепенно. С каждым новым тестовым прогоном прибавляется еще один элемент программы, функционирующий правильно. Такая равномерность усложнений облегчает задачу распознавания ошибки при выдаче неверных результатов. Если же с помощью одного теста контролируются сразу несколько непроверенных модулей программы, то в случае ошибки бывает трудно определить, какой из модулей привел к отказу.
       Заключительные испытания должны обеспечить проверку всей программы в целом. Они предназначены для того, чтобы не пропустить подпрограмм с ошибками, которые при конкретных тестовых данных могли и не нарушить правильности результатов.
       Цель тестирования любой программы состоит не в том, чтобы проверить, работает ли она при различных комбинациях тестовых данных, а в том, чтобы убедиться в ее Правильной работе. Поэтому тестовые данные должны подбираться таким образом, чтобы программист был в состоянии вычислить правильный результат еще до начала тестового прогона. Если этого не сделать заблаговременно, то потом очень легко поддаться соблазну считать машинный результат достоверным.

5.10.1. ТИПЫ ТЕСТОВЫХ ДАННЫХ

       Можно выделить три типа тестовых данных: создаваемые программистом, реальные модифицированные и реальные в полном объеме. Для тестирования обычно требуются данные всех трех типов.
       В первую очередь используют данные, создаваемые программистом. Они подразделяются в свою очередь на контролируемые и случайные данные. Данные первого типа применяют для проверки общей работоспособности программы; они отличаются тем, что могут использоваться в редко встречающихся ситуациях. Благодаря такой возможности контролируемые данные позволяют осуществлять максимально эффективные проверки. Кроме того, правильный подбор тестовых данных должен способствовать уменьшению объема ручной работы по проверке машинных результатов. Недостатком контролируемых тестовых данных является то, что они предусматривают проверку лишь тех ситуаций, которые признаются существенными с точки зрения пользователя.
       Поэтому не следует забывать об использовании данных другого типа (случайных тестовых данных), для формирования которых существуют специальные программы. Эти тестовые данные обладают тем преимуществом, что позволяют последовательно исключать ошибки из программы, а также обнаруживать ошибки, не выявлемые с помощью контролируемых тестовых данных. Отрицательной стороной использования случайных тестовых данных является трудность проверки правильности результатов. Хотя следует заметить, что если ошибки настолько серьезны, что приводят к авостам, то они, как правило, обнаруживаются.
       Реальные модифицированные данные сочетают в себе до некоторой степени достоинства обоих отмеченных выше типов тестовых данных, создаваемых программистом. Путем аккуратного целенаправленного видоизменения реальных данных возможно осуществить целый ряд специальных режимов тестирования программы. Использование реальных данных вместо данных, создаваемых программистом, позволяет избежать специального формирования иногда весьма значительных по объему массивов информации.
       Еще одно преимущество использования реальных модифицированных данных состоит в том, что в этом случае процесс тестирования приближается к практическим условиям работы. Это позволяет выявить множество проблем, которые не могут быть вскрыты с помощью данных, полученных искусственным путем. Одной из целей модификации реальных данных является проверка подпрограмм обработки ошибок. Умышленное ведение ошибок в тестовые данные — это единственный способ проверить правильность функционирования таких подпрограмм.
       Заключительные испытания проводятся на реальных данных в полном объеме. Если этот этап тестирования программного обеспечения может быть осуществлен параллельно с функционированием старой, не автоматизированной системы, то работа по ручной проверке машинных результатов существенно сокращается. Обычно при тестировании реальных данных программиста ожидает множество сюрпризов. Именно здесь выявляется недопонимание (“коммуникационный разрыв”) между программистом и заказчиком и часто становятся явными их обоюдные просчеты.
       Иногда параллельное функционирование новой и старой систем вскрывает те или иные дефекты старой системы. Но контроль результатов без такой параллельной работы становится чрезвычайно трудоемким, а то и вообще невозможным. Для того чтобы испытания можно было бы считать завершенными, необходимо выполнить по крайней мере три параллельных тестовых прогона.
       Параллельная работа старой и новой систем приводит к такому объему выходной информации, что визуальное сравнение результатов оказывается невозможным. В этих случаях пишут специальные программы, которые сравнивают записанные на ленту или диск результаты с требуемыми и устанавливают имеющиеся расхождения. Кроме того, эти программы позволяют сопоставлять старые и новые массивы данных результата в тех случаях, когда в программное обеспечение вносятся изменения.
       Каждый из рассмотренных типов тестовых данных имеет свои достоинства и недостатки, но при совместном использовании они обеспечивают наиболее полное и качественное тестирование.
       В ходе ручной проверки машинных результатов следует аккуратно проводить вычисления, чтобы в них не вкрались ошибки. В противном случае выходные данные, полученные с машины, будут сравниваться с неверными результатами ручного счета, что может привести к большим затратам времени на поиск программной ошибки, в то время как корнем зла являются неверные вычисления.

5.10.2. КЛАССЫ ТЕСТОВЫХ ДАННЫХ

       В связи с тем что мы не располагаем средствами, чтобы опробовать программу при всех возможных совокупностях данных, желательно подобрать наиболее характерные данные. Для этого каждый новый тест должен содержать вполне определенный класс данных. Этот аспект тестирования часто оказывается вне сферы внимания разработчиков. Между тем правильная работа программы с данными конкретного класса позволяет надеяться на то, что она будет так же хорошо обрабатывать и любые другие данные того же класса.
       Если, например, мы имеем дело с программой, которая должна считывать количество отработанных за неделю часов и начислять заработную плату, можно было бы установить несколько классов данных, характеризующих отработанное время: 0, 35, 40, 50, 100. Поскольку программа должна вычислять сверхурочную оплату, проверка ее работоспособности должна предусматривать задание времени, меньшего, большего и равного 40 ч. Кроме того, необходимо удостовериться, что программа правильно работает, когда время равно нулю или некоторому большому числу. Следует также посмотреть как ведет себя программа при некоторых неправильных значениях отработанного времени, например при отрицательных величинах.
       Для правильного определения необходимых классов тестовых данных обычно требуется некоторое знание программы. В предыдущем примере охвачены все возможные классы значений переменной “время”. Введение каких-либо дополнительных значений, превышающих число 40, ничего не добавило бы к тому результату проверки, который получается при использовании числа 50, позволяющему судить о том, правильно ли работает программа при начислении сверхурочной оплаты. Однако, если бы какие-то из отмеченных классов были упущены, мы не могли бы иметь полной уверенности в работоспособности программы. Основная задача состоит в том, чтобы определить необходимые классы данных и сформировать 1—2 контрольных примера для каждого из них. Программисты-непрофессионалы склонны к избыточной проверке программ на одних классах данных и недостаточной — на других.

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

       Использование тестовых данных определенных классов отвечает требованию экономичности тестирования.

5.10.3. АНАЛИЗ РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ

       Каждый раз, когда используются тестовые данные, необходимо иметь какой-то способ проверки правильности машинных результатов. Существует несколько таких способов:
       1) вычисление результатов вручную;
       2) получение результатов из регистрационной книги, документации или совокупности таблиц и
       3) получение результата с помощью некоторой другой машинной программы.
       Важно, чтобы проверяемые машинные результаты сопоставлялись с соответствующими данными, взятыми из другого, независимого источника. Очевидно, что наименее желательно применение первого метода вследствие большого объема ручной работы.
       Все три отмеченных метода проверки основываются на знании конкретных значений результатов, которые должны быть получены. Однако это не всегда возможно или целесообразно. Для таких сучаев можно прибегнуть к двум дополнительным методам, применение которых, однако, требует от испытателя знания алгоритма, реализованного в программе.
       Для использования первого из этих дополнительных методов необходимо знание пределов изменения выходных переменных. Обычно такие пределы можно довольно легко определить, если проанализировать программу, зная ее алгоритм. Любые значения, выходящие за ожидаемые пределы, должны подвергаться более тщательной проверке.
       Второй метод состоит в целенаправленном манипулировании тестовыми данными и предсказании на основе продуманного и осторожного их изменения направления и пределов изменения выходных переменных. Совпадение фактических и прогнозируемых изменений результатов может в какой-то мере говорить о правильности работы программы.

5.10.4. ФОРМИРОВАНИЕ ТЕСТОВЫХ ДАННЫХ

       Усовершенствование процесса тестирования связано с применением способов генерирования тестовых данных. В случае использования перфокарт такие методы очень просты и основываются на введении специальных стандартных тестовых перфокарт:
       1. Перфокарт, заполненных нулями в позициях 1-9, единицами в позициях 10-19, двойками в позициях 20-29, и т.д.
       2. Перфокарт, содержащих 1 в позиции 1, 2 в позиции 2, ... 9 в позиции 9, 0 в позиции 10, 1 в позиции 11, ... 9 в позиции 19, 0 в позиции 20, 1 в позиции 21, и т.д.
       3. Перфокарт, содержащих во всех 80 позициях нули, единицы, ... девятки.
       Перфокарт, в которых единицы чередуются с нулями.
       Перфокарт, содержащих во всех 80 колонках буквенные символы:
       А в позиции 1,
       Б в позиции 2,
       В в позиции 3,
       и т.д.
       Тестовые данные такого вида могут применяться для контроля правильности идентификации полей данных. Например, если для ввода информации используются колонки 27-32, то в качестве теста целесообразно применять карты видов 1 и 2. В этом случае результат тестирования должен при правильной работе программы выглядеть следующим образом:

222333
789012

       где каждый столбец читается как двузначный номер, идентифицирующий позиции входных данных (т.е. 27, 28,..,32). Этот тест обеспечивает проверку одного из самых важных условий — правильности считывания программой нужных позиций перфокарт. После этого могут быть использованы тесты видов 3-5 для проверки правильности обработки нулевых и буквенных данных. Еще один метод проверки правильности идентификации полей данных заключается в использовании определенной буквы или цифры для каждого поля. При таком способе легче различать границы полей данных.
       Конечно, наивно полагать, что применение тестовых перфокарт разрешает все проблемы тестирования. Однако с их помощью можно выполнить много разнобразных простых проверок. Такие карты должны находиться в ящике-стенде для хранения перфокарт и быть легкодоступными для программистов.
       Для языков КОБОЛ и ПЛ/1 характерна интенсивная работа с файлами данных, поэтому необходимо иметь тесты для проверки файлов. Для формирования таких тестовых файлов обычно можно применять программы-утилиты. Например, фирма IBM предлагает программу-утилит IEBDG (генератор данных), которая обеспечивает получение контрольных примеров, могущих использоваться для тестирования рабочих программ. В ряде вычислительных комплексов имеются специальные генераторы тестовых данных, которые формируют испытательные файлы. Основной принцип работы таких генераторов заключается в использовании описаний файлов, содержащихся в испытываемых программах для построения тестовых файлов. Тестовые генераторы могут как создаваться собственными силами пользователей, так и поставляться фирмами, разрабатывающими программное обеспечение ЭВМ.

5.10.5. ЭТАПЫ ТЕСТИРОВАНИЯ

       Процесс тестирования программы можно разделить на три этапа:
       1. Проверка в нормальных условиях.
       2. Проверка в экстремальных условиях.
       3. Проверка в исключительных ситуациях.
       Эти этапы должны быть обеспечены всеми необходимыми средствами тестирования.
       Каждый из трех этапов проверки должен гарантировать получение верных результатов при правильных входных данных и выдачу сообщений об ошибках при неправильных данных.

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

Проверка в экстремальных условиях
       Этот этап тестирования должен идти сразу за проверкой программы в нормальных условиях. Тестовые данные этого этапа включают граничные значения области изменения входных переменных, которые должны восприниматься программой как правильные данные. Для нецифровых данных необходимо использовать подобные типичные символы, охватывающие все возможные ситуации. Для цифровых данных в качестве экстремальных условий следует брать начальное и конечное значения допустимой области изменения переменной при одновременном изменении длины соответствующего поля от минимальной до максимальной. Типичными примерами таких экстремальных значений являются очень большие числа, очень малые числа и отсутствие информации. Каждая программа характеризуется своими собственными экстремальными данными, которые должны подбираться программистом.
       Процесс использования экстремальных значений переменных в качестве тестовых данных носит название граничных испытаний. Граничные испытания зачастую предоставляют наилучшие возможности для выявления ошибок. Если некоторая программа работает правильно в граничных условиях, обычно это означает, что она будет нормально работать и в любой другой области значений переменных.
       Еще один тип экстремальных условий — это граничные объемы данных, когда они состоят из слишком малого или, наоборот, слишком большого числа записей. Необходимо установить,, что происходит с программой, если ей “а обработку не поступает ни одного элемента данных или только один, и сохранит ли она в этих условиях свою работоспособность.
       Особый интерес представляют так называемые нулевые примеры. Для цифрового ввода — это обычно нулевые значения вводимых данных; для последовательностей символов — это цепочка пробелов или нулей; для указателей — нулевое значение указателя. Нулевые примеры представляют собой один из лучших тестов, поскольку они имитируют состояние данных, которое время от времени имеет место в реальных условиях эксплуатации программы. Если подобное тестирование не выполняется, то впоследствии часто приходится сталкиваться с непонятным поведением программы.
       Не всегда, однако, легко определить, какие значения данных являются экстремальными. Рассмотрим, например, программу, которая должна считывать четыре одноразрядных положительных целых числа. Поверхностный подход к построению тестов приводит в данном случае к формированию следующих экстремальных условий:

  А В С D
Тест 1 0 0 0 0
Тест 2 9 9 9 9

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

ANSWER = A+S+C+D

       Однако если предстоит вычислить

       то наиболее вероятное значение ответа получится при С=1:

       в то время как при С = 9

       Следовательно, правильный подбор численных значений переменных для создания экстремальных условий возможен только в случае знания алгоритма решения задачи. Приведенный пример иллюстрирует часто допускаемую ошибку тестирования, когда забывают, что существуют экстремальные значения как входных, так и выходных переменных. Главная цель использования экстремальных тестов состоит в установлении того факта, что поля данных промежуточных результатов имеют размеры, достаточные для проведения требуемых вычислений.
       Условия, необходимые для тестирования программы, можно создавать также путем введения в нее определенных констант. Этот способ хорош тогда, когда возникает необходимость тестирования экстремальных условий, являющихся результатом длительных вычислений, но трудно подобрать контрольные данные для проверки тех ситуаций, которые представляют интерес. Например, если вы желаете установить момент, когда результаты вычислений становятся отрицательными, а эта ситуация возникает относительно редко, то можно добавить в программу оператор, который в конце проверки присваивает интересующей вас переменной отрицательный знак. Однако впоследствии надо не забывать об исключении этой искусственной модификации из программы. Один простой способ запоминания этого требования заключается в том, чтобы иметь в программе комментарий, который указывал бы, что данный оператор является тестовым. Можно также отперфорировать в позициях 73—80 тестовой строки исходной программы слово ТЕСТ как напоминание о необходимости исключения этой строки по окончании тестирования.

Проверка в исключительных ситуациях.
       Последний этап тестирования программы проводится с использованием данных, значения которых лежат за пределами допустимой области изменения. Известно, что все программы разрабатываются в расчете на обработку какого-то ограниченного набора данных. Поэтому важно получить ответ на вопросы: что произойдет, если программе, не рассчитанной на обработку отрицательных или нулевых значений переменных, в результате какой-либо ошибки придется иметь дело как раз с такими данными? Как будет вести себя программа, работающая с массивами, если количество их элементов превысит величину, указанную в описании? Что случится, если цепочки символов окажутся длиннее или короче, чем это предусмотрено? Что произойдет, если числа будут слишком большими или, наоборот, слишком малыми?
       Наихудшая ситуация складывается тогда, когда программа воспринимает неверные данные как правильные и выдает неверный, но правдоподобный результат. В этом случае представляются неубедительными отговорки, что программа не была предназначена для обработки данных, приведенных к такому результату, или что неверные данные не должны вводиться в машину. Такие ограничения не могут решить проблему, поскольку неверные данные могут образоваться в результате опечаток или неправильного понимания оператором инструкций по вводу данных. Поэтому программа должна сама отвергать любые данные, которые она не в состоянии обрабатывать правильно.
       Если вы являетесь единственным пользователем программы, то можете не вводить в нее операторов, отвергающих недопустимые данные. Однако, если та же программа предназначена еще и для других пользователей, такие операторы крайне необходимы. Они должны проходить обязательную проверку в процессе тестирования, чтобы программист убедился в наличии всех необходимых операторов редактирования данных.
       В отношении проверки программы в исключительных и экстремальных условиях можно дать ряд рекомендаций, которые могут сэкономить время программиста. Прежде всего, если предусматриваются проверки правильности редактирования данных, берите значения, расположенные на обеих границах допустимой области; именно эти значения являются наиболее вероятными источниками затруднений при работе программы.
       Данные, содержащие пробелы, цифры и буквы, должны опробоваться в самых разнообразных сочетаниях. Пробуйте заполнять пробелами и буквами те поля, которые требуют цифровых данных. При проверке взаимных связей между различными полями данных попытайтесь перемежать верные и неверные данные.
       Иногда большие затруднения вызывают данные, содержащие сразу несколько ошибок. Еще один вид ошибок, представляющих особый интерес, — это ошибка в первом или последнем элементе обрабатываемой информации. Важно, чтобы при наличии такой ошибки программа не заканчивала работу как в нормальной ситуации. Для этого достаточно при тестировании сформировать файл, состоящий из единственного элемента, который неверен. Если такая работа не проделана на стадии тестирования программы, трудности неизбежно возникнут при ее эксплуатации. По окончании отмеченных выше проверок наступает время удовлетворения любознательности и свободного полета фантазии программиста. Попробуйте использовать в качестве входных данных программы необычную информацию: небольшую пачку карт из приемного кармана, неупорядоченную колоду перфокарт, ленту с данными, не относящимися к вашей программе. Помните, что первое правило в системах обработки данных гласит: при использовании неверных входных данных результаты будут неправильными. Если ваша программа не защищена от использования неверной информации, вы можете потратить массу времени на поиски программных ошибок, в то время как истинной причиной отказа являются сами обрабатываемые данные. Хотя оператор, допускающий из-за небрежности ошибки при подготовке данных, достоин порицания, но и программисту непростительна создавать программу, которая воспринимает неверные данные как правильные.

Испытывайте программу в нормальных, экстремальных и исключительных условиях.

5.10.6. ТЕСТИРОВАНИЕ ВЕТВЕЙ

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

Подготавливайте тестовые данные для проверки каждой ветви алгоритма.

       Проверку ветвей легче всего осуществлять при тестировании отдельных модулей. Если же ее отложить до этапа испытаний связанных модулей, тестирование ветвей становится более трудным делом. Рассмотрим модуль, в котором проверке подлежат n ветвей. Предположим далее, что существует второй модуль, который обращается к первому в m точках. Если каждый из этих модулей допускает независимую проверку, то необходимо опробовать всего m+n путей. Если же эти два модуля испытываются совместно, то число подлежащих проверке путей работы программы становится равным пгХп. Кроме того, чем больше размер испытываемой части программы, тем труднее определить местонахождение обнаруженной ошибки.
       Следует, однако, помнить, что простое прослеживание всех возможных путей работы программы не обеспечивает необходимой полноты ее тестирования. Предположим, например, что программа должна определить, равны ли между собой три числа, и в ней использован следующий алгоритм:

IF ((X+Y+Z)/3 = Y)
   THEN PRINT 'Y, Z, EQUAL' 
   ELSE PRINT 'Y, Z, NOT EQUAL'

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

5.11. ПРИМЕРЫ ТЕСТОВ

       Очень простую для тестирования задачу представляет проверка модуля, вычисляющего длину диагонали параллелепипеда, который показан на рис. 5.4. Длина диагонали в данном случае равна

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

Номер теста Стороны параллелепипеда Примечания
1. 1 1 1 Хороший начальный тест
2. 1 2 3 Проверка в нормальных условиях
3. 0 0 0 Результат должен быть равен нулю
4. 0 1 2 Не параллелепипед. Что произойдет?
5. 1 0 3 Не параллелепипед. Что произойдет?
6. 2 1 0 Не параллелепипед. Что произойдет?
7. 1 —6 3 Неверные данные

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


Рис. 5.4.

       В качестве второго примера тестирования рассмотрим задачу нахождения корней квадратного уравнения ax2+bx+c=0. Предназначенная для решения этого уравнения программа считывает коэффициенты а, b и с и для вычисления двух корней использует формулу

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

Номер теста Коэффициенты Примечания
1. 1 1 —2 Хороший начальный тест
2. 1 0 0,25 Проверка в нормальных условиях
3. 0 0 0 Что произойдет здесь?
4. 0 2 1 Должен получиться только один корень
5. 2 1 0 Все должно быть в порядке
6. 1 1 1 Комплексные корни
7. 0 0 2 Неправильное уравнение
8. 0 2 0 Должен быть один корень
9. 2 0 0 Должно быть два корня

       Если вы будете применять модульное программирование, ваши программы тоже можно будет делать простыми. Во-вторых, проведенное нами тестирование было целенаправленным и систематизированным, так как случайный выбор чисел привел бы к большим трудностям в определении ручным способом результатов, которых следует ожидать при прогоне тестов. Кроме того, при случайном выборе тестовых данных могут оказаться непроверенными многие ситуации. Лучше всего преднамеренно подбирать такие данные, которые позволили бы проверить все возможные при функционировании программы ситуации. Только доскональная проверка программы может считаться хорошим тестированием.

5.12. ТЕСТИРОВАНИЕ ПРОГРАММ МАТЕМАТИЧЕСКИХ ВЫЧИСЛЕНИЙ

       В тестировании программных средств, работающих с цифровыми данными, необходимо принимать во внимание три главных источника ошибок в численных результатах. Во-первых, это ошибки, связанные с первоначальными данными (исходные ошибки), примерами которых являются ошибки ввода, а также ошибки, связанные с ограниченной точностью измерения значений переменных (унаследованные ошибки). Последний тип ошибок связан с неопределенностью значений входных переменных и не может быть устранен никакими ухищрениями в вычислительном процессе.
       Вторым источником неверных результатов являются так называемые ошибки аналитического усечения, когда бесконечный процесс математических вычислений заменяется конечным алгоритмом. Примером может служить использование вместо некоторой бесконечной числовой последовательности только шести ее членов.
       Третий источник ошибок в результатах — это ошибки округления, вызываемые ограниченной точностью выполнения арифметических операций в машине. На величину таких ошибок влияют разрядность машины и основание системы счисления, в которой представляются числа в машине. Ошибки округления называются также генерируемыми или порождаемыми ошибками в связи с тем, что они вносятся машинной программой при правильных входных данных. Проверка программ на отсутствие ошибок округления производится путем использования данных, для которых заранее известны результаты обработки, и сопоставления этих результатов с теми, которые выдаются машиной в ходе тестирования. Эталонные результаты определяются либо на основе некоторых теоретических соображений, либо путем выполнения вычислений на другой машине, обладающей большей точностью представления чисел. Вопросы точности арифметических операций особенно важно учитывать при построении тестов, поскольку почти правильные программы ведут себя особенно коварно.
       Процесс сопоставления машинных результатов с заранее заданными (до начала тестирования) называется прямым анализом ошибок. Однако прямой анализ не всегда возможен или целесообразен. Иногда бывает удобнее показать, что полученное машиной решение задачи является точным решением исходной задачи. Такой способ действий называется обратным анализом ошибок. В этом случае измеряются не расхождения между решением математической задачи и заранее определенным результатом, а различия между поставленной и решенной задачами. В качестве примера можно привести процедуру нахождения обратных значений. Мерой близости решения к точному может в этом случае служить величина произведения исходного числа на обратное. Тем, кто интересуется проблемами программирования математических вычислений, можно рекомендовать книгу под редакцией Дж. Раиса1{Rice J.R. (ed.), Mathematical Software, Academic Press, New York, 1971.}.

5.13. МОДУЛИ

       Вероятно, некоторые из программистов склонны считать примеры тестирования, приведенные в данной главе, изящными, но неприемлемыми для тех сложных программ, которые разрабатывают они сами. Не следует, однако, забывать, что, используя модульный принцип построения, можно делать программы простыми. Только неопытные программисты пишут неделимые, громоздкие и неудобные для тестирования программы. Большую монолитную программу практически невозможно испытать достаточно полно из-за наличия в ней огромного количества логических путей. Чем квалифицированнее программист, тем короче создаваемые им модули.
       Основная цель модульного построения программы — это обеспечение легкого тестирования ее элементарных блоков. Каждый модуль должен предназначаться для выполнения одной функции, тогда в процессе его испытаний необходимо будет только убедиться в том, что он правильно решает именно эту единственную задачу. Сборка программы из модулей, прошедших тщательную индивидуальную проверку, дает большую уверенность в том, что она будет функционировать нормально.
       Испытания отдельных модулей должны включать проверку связей и взаимодействий между модулями. Несмотря на то что области значений данных должны быть проконтролированы еще на стадии отладки программы, нелишне еще раз проверить “правильность значений данных, передаваемых от одного модуля к другому, поскольку именно этот аспект функционирования программы является обычно источником затруднений. В связи с тем что взаимодействие между модулями ограничивается передачей известных параметров, тестирование большой программы, скомпонованной из модулей, легче, чем большой программы, не содержащей модулей. Однако обязательным условием должно быть отсутствие в модулях логических ошибок; если же такие ошибки выявляются на этапе тестирования всей программы, это значит, что тестирование модулей было несовершенным.
       Кроме того, при использовании небольших модулей уменьшается вероятность возникновения ошибок в программе при внесении изменений. Модули легко также включать в библиотеку программ, а использование такой библиотеки автоматически сокращает объем работ по тестированию, поскольку стандартные программы, включаемые в библиотеку, предварительно испытываются тщательным образом.

5.13.1. ИМИТАЦИЯ РАБОТЫ МОДУЛЕЙ

       Часто возникает ситуация, когда нужный модуль не готов к моменту завершения испытаний какого-либо другого модуля, и поэтому требуется имитировать работу первого. Это может быть осуществлено двумя способами: посредством фиктивного модуля и посредством замещающего модуля.
       Фиктивный модуль — это такой модуль, который состоит только из одной точки входа и одной точки возврата. Используется для тестирования модулей более высокого уровня, если нужный реальный модуль еще не создан или не требуется. Часто оказывается полезным наличие в фиктивном модуле некоторого оператора, сигнализирующего об обращении к нему.
       Замещающий модуль — это модуль, который выполняет ряд вычислений, но в очень упрощенной форме. Такие вычисления бывают необходимы в тех случаях, когда модулю более высокого уровня требуются для завершения процесса тестирования некоторые величины, определяемые в реально отсутствующем модуле нижнего уровня. Применяется в тех случаях, когда использование фиктивного модуля не может дать желаемого эффекта.
       Замещающий модуль может использоваться и тогда, когда реальный модуль требует большого количества машинного времени или особых данных на выходе вызывающего модуля, что увеличивает продолжительность испытаний программы.

5.13.2. РЕКОМЕНДАЦИИ ПО ТЕСТИРОВАНИЮ МОДУЛЕЙ

       Легче всего тестировать модули, задавая контрольные данные на вход модуля самого высокого уровня. При таком подходе все данные оказываются внешними по отношению к модулям нижних уровней, подлежащим проверке. Может, например, оказаться, что модуль ввода-вывода данных требует очень сложного программирования, которое должно занять много времени, а многие другие модули могут быть запрограммированы гораздо раньше. В этом случае нецелесообразно задерживать тестирование готовых модулей и имеет смысл использовать для формирования тестовых исходных данных замещающий модуль. В связи с тем что все данные сосредоточиваются в модуле высокого уровня, создание такого замещающего модуля является несложным делом и обеспечивает тестирование готовых модулей в отсутствие реального модуля ввода-вывода.
       Для того чтобы можно было полностью охватить программу, каждый модуль следует делать небольшим по размеру, стремиться к тому, чтобы он не содержал большого числа логических ветвей и работал с небольшими объемами данных. При соблюдении всех этих условий модуль получается сравнительно простым для тестирования, так как становится возможным учесть все экстремальные ситуации и проверить каждую ветвь процесса обработки данных.
       Один из методов тестирования модулей состоит в использовании специального “инициирующего” оператора, встраиваемого в модуль и генерирующего начальные тестовые данные. Такой метод позволяет обойтись без фактических передач данных на вход модуля и сосредоточить внимание именно на тестировании самого модуля. Однако необходимость последующего тестирования операций передач данных на вход модуля все же остается, так как эти операции могут быть связаны с ошибками, которые не обнаруживаются при применении инициирующего метода тестирования.

5.14. БИБЛИОТЕКА ПРОГРАММ

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

IF TESTING THEN...

       подобные отладочным операторам, описанным в гл.4.

5.15. ТЕСТИРОВАНИЕ ФАЙЛОВ

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

5.16. СИСТЕМНЫЕ ИСПЫТАНИЯ

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

5.17. СРЕДСТВА ТЕСТИРОВАНИЯ

       Средства тестирования представляют собой программы, которые помогают автоматизировать процесс испытаний системы; их часто называют средствами автоматического тестирования. Наличие таких средств дает возможность небольшой группе программистов провести анализ программ большого объема, что в противном случае было бы неосуществимо. Но прежде чем повести разговор об автоматических средствах тестирования, целесообразно рассмотреть ряд других вспомогательных средств, чрезвычайно облегчающих этот процесс. Прежде всего необходим хороший отладочный компилятор, способный выявлять ошибки, которые в противном случае должны были бы обнаруживаться при тестировании. Такие компиляторы рассматривались в гл.4. Необходимым условием получения правильной готовой программы является устранение синтаксических ошибок. Чем больше ошибок обнаруживается компилятором, тем меньше их остается на стадии тестирования. В настоящее время наблюдается благоприятная тенденция расширения возможностей диагностики ошибок в языках программирования. Так, язык ПЛ/1 насчитывает порядка 1000 диагностических средств, включая предупреждения об ошибках.
       Второй тип вспомогательных средств тестирования — это пакет подпрограмм, встраиваемых в рабочую программу для автоматического исправления ошибок. Современные языки программирования предоставляют возможность обнаруживать переполнение, потерю значимости, деление на нуль и недопустимые условия вызова подпрограмм. Более современные компиляторы, такие, как WATFIV для ФОРТРАНа и WATBOL для КОБОЛа, позволяют также обнаруживать нарушение правил индексирования массивов, неиспользуемые ветви программы и неправильно указанные параметры вызова подпрограммы. В связи с тем, что использование этих дополнительных средств требует дополнительных затрат машинного времени, их применение ограниченно (возможности по обнаружению 136 типов функциональных ошибок предоставляет язык ПЛ/1). Ошибки, которые способен обнаружить компилятор, неизбежно выявляются; поэтому применение компилятора позволяет не ждать, когда ошибка проявит себя в результате сочетания определенных условий, приводящих к отказу программы.
       Компилятор должен обладать также способностью слежения за ходом выполнения программы и контроля за определенными переменными. Оба эти свойства чрезвычайно полезны при тестировании, так как дают возможность отслеживать в программе логические пути и последовательные изменения значений переменных.
       Еще одно необходимое средство тестирования — это возможность хранения тестовых входных данных в целях их повторного использования. Таким средством в простейшем случае, когда программа невелика, является колода перфокарт, а для сложной системы это может быть целый набор программ. При этом необходимо иметь некоторый метод обеспечения хранения, дополнения и модифицирования тестовых данных. Все это требуется для того, чтобы иметь возможность повторного запуска тестов после внесения изменений в программу.

5.17.1. ГЕНЕРАТОР ТЕСТОВЫХ ДАННЫХ

       Хотя рассмотренные выше средства полезны и необходимы, они все же не решают полностью проблемы проверки программ. Первым действенным средством автоматического тестирования является генератор тестовых данных (ГТД), который формирует данные для использования в проверяемой программе. Такие данные часто оказываются полезными как первый тест программы, позволяющий определить, пригодны ли конкретный модуль или подпрограмма к стыковке с другими элементами. Здесь, однако, имеется целый ряд трудностей. Во-первых, необходимо уметь пользоваться ГТД. Но изучение его может потребовать стольких усилий, что целесообразнее окажется создавать тестовые данные вручную (особенно для небольших систем). Во-вторых, основное назначение ГТД — формирование больших массивов тестовых данных. Однако для тестирования нам требуется не просто произвольный набор данных, а хорошо подобранные данные для всестороннего испытания тестируемой программы. Более того, тестовые данные обычно должны формироваться в определенной последовательности; часто между ними могут существовать очень сложные зависимости (как, (например, между главным и вспомогательным файлами). В одних тестовых пакетах эта проблема решается лучше, в других — хуже. В табл. 5.1 приведены сведения о некоторых доступных для широкого применения генераторах тестовых данных. Некоторые другие ГТД обсуждаются в книге Нэфтэли1 {Naftaly S.М. Cobol Support Packages, Wiley, New York, 1972.}).

Таблица 5.1

Генератор тестовых данных Фирма-изготовитель
IEBDG (в составе вспомогательных программ операционной системы OS) IBM
PRO/TEST Synergetics (One Garfield Circle, Burlington, Mass., 01803)
МетаКОБОЛ Applied Data Research (11661 San Vicente Blvd., Los Angeles, Cal. 90049)

5.17.2. ВСПОМОГАТЕЛЬНЫЕ ПРОГРАММЫ РАСПЕЧАТЫВАНИЯ ФАЙЛОВ (ПРОГРАММЫ-УТИЛИТЫ)

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

5.17.3. КОМПАРАТОР ФАЙЛОВ

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

5.17.4. ПРОГРАММЫ-ПРОФИЛИРОВЩИКИ

       О программах-профилировщиках речь уже шла в гл.3. Профилирование программы оказывается весьма полезным для отладки и тестирования, так как дает информацию о том, какие операторы и сколько раз выполнялись. Профилирование выявляет модули или части программы, которые оказались неиспользованными, и указывает на необходимость продолжения тестирования. Средства профилирования программ предусматривают возможность накопления статистических данных о работе тестируемой системы. Такие статистические данные позволяют испытателям выявлять те блоки программ, которые недостаточно проверены. Поэтому обратите внимание на средства профилирования, приведенные в разд. “Проекты”.

5.17.5. ТЕСТОВЫЙ МОНИТОР

       Тестовый монитор — это программа, которая пересылает нужные данные на вход тестируемого модуля и накапливает выходные данные, выдаваемые на печать или записываемые в файл. Программу тестового монитора часто называют тестовым драйвером. Назначение этой программы состоит в том,, чтобы создавать нужные условия для проверки модулей. Иногда монитор представляет собой небольшую программу, предназначенную для тестирования отдельных подпрограмм, а порой он может быть очень сложной программой. Хороший тестовый монитор должен предусматривать распечатывание входных параметров и всех переменных, формируемых проверяемым модулем. Тестирование по принципу “сверху вниз” в какой-то мере исключает необходимость использования тестового монитора благодаря выполнению его функций главной программ мой.

5.18. КОНТРОЛЬ РЕЗУЛЬТАТОВ С ПОМОЩЬЮ ПРОВЕРЯЕМОЙ ПРОГРАММЫ

       Проверка результатов тестирования может осуществляться самой тестируемой программой. Этот процесс часто называют самопроверкой программного обеспечения. Так, например, если проверяемый модуль должен извлекать квадратный корень из числа, то лучший путь тестирования этого модуля состоит в том, чтобы вызвать его с помощью вспомогательной подпрограммы и затем выдать на печать вычисленное значение квадратного корня, квадрат его величины и разность между значением входного аргумента и квадратом вычисленного значения корня. Описанная процедура называется реверсивной проверкой. Другими областями ее применения могут быть обращение матриц, преобразования Фурье, шифрирование — дешифрирование и ряд других преобразований. В целях сокращения объема ручной работы по контролю результатов реверсивной проверки модуля можно применять тестовый драйвер или монитор. Это особенно удобно при больших объемах информации, когда драйвер может быть настроен на фиксацию только неблагоприятных исходов тестирования, что сокращает общее количество выдаваемых результатов.
       Иллюстрацией реверсивной проверки является контроль правильности решения системы уравнений с помощью подстановки решения в исходную систему. В некоторых случаях между выходными переменными может существовать простая зависимость, и тогда правильность результатов может устанавливаться на основе контроля этой зависимости. Примерами таких ситуаций являются упорядоченные массивы или какие-либо групповые итоговые суммы, которые должны взаимно балансироваться (перекрестные итоги).
       В большинстве случаев для проверки правильности выходных результатов тестирования требуется иметь алгоритм, который по своей сложности не уступает самой испытываемой программе. Однако даже и в такой ситуации часто существует возможность установить правдоподобность результатов. Например, начисляемые денежные суммы должны быть не меньше или не больше некоторых установленных пределов, и этот факт можно использовать для контроля результатов вычислений.
       Особенно важно проявлять аккуратность в оценке результатов и не допускать, чтобы неверные результаты принимались за правильные. Если вы имеете некоторые промежуточные результаты, выданные машиной, и затем проверяете каждый шаг расчетов, то все, что вы делаете в данном случае, — это проверку способности машины правильно умножать. Но непроверенными остаются корректность выбора математических операций и правильность порядка их выполнения. В этом отношении описанная выше процедура реверсивной проверки модуля для извлечения квадратного корня свободна от указанного недостатка, так как вычисления были проделаны в обоих направлениях и результаты сопоставлены между собой. И хотя эта процедура не дает абсолютной гарантии от ошибок, она является примером полного контроля.

5.19. ОКОНЧАТЕЛЬНОЕ УТВЕРЖДЕНИЕ ПРОГРАММЫ

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

5.20. ПЛАНИРОВАНИЕ ИСПЫТАНИЙ ПРОГРАММ

       Одно из необходимых условий надлежащего тестирования программ — это выделение на него в плановом порядке достаточных ресурсов времени, которые, как показывает практика, примерно равны затратам времени на программирование. Поскольку существует тенденция планировать только работу по программированию, нетрудно понять, отчего все проекты выполняются с нарушениями сроков. Это говорит о необходимости раздельно планировать затраты времени на разработку, кодирование, отладку и тестирование программ и отдавать себе отчет в том, что сдвиг сроков окончания разработки алгоритма на неделю будет приводить к такому же сдвигу сроков по всем остальным этапам.
       Системам, для которых крайне необходимы всесторонние испытания, требуется еще и дополнительный резерв времени. Например, по оценкам НАСА, стоимость испытаний программ в рамках проекта “Аполлон” составила 80% общей стоимости разработки программного обеспечения. Конечно, эта цифра столь велика потому, что указанный проект требовал всеобъемлющего тестирования, однако подобные оценки по операционным системам ЭВМ показывают, что на их тестирование приходится от 30 до 60% общих затрат на разработку.

5.20.1. ГРАФИКИ ПРОВЕДЕНИЯ ИСПЫТАНИИ

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

Планируйте надлежащим образом затраты времени на испытания.

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

5.20.2. ПЛАНИРОВАНИЕ ПРОГРАММНЫХ ИСПЫТАНИИ

       Очень часто бывает желательно получать информацию о ходе тестирования программ. При этом нас интересуют ответы на два вопроса: выдерживается ли график? достаточно ли полно проверена программа? Получить ответы на эти вопросы столь же важно, сколь и трудно. Однако существуют методы работы, позволяющие следить за ходом тестирования программ.
       Как известно, большие системы обычно разделяют на подсистемы или компоненты. Компоненты в свою очередь состоят из элементарных блоков, представляющих собой наименьшие различные элементы программ. Может существовать много уровней такого разбиения и множество элементов на каждом уровне. Традиционный подход к тестированию предполагает первоначальную проверку наименьших элементов с последующим объединением их в более крупные блоки и повторением проверки постепенно укрупняемых узлов до тех пор, пока не пройдет испытания вся система в целом.
       Для придания тестированию целенаправленного характера необходимо уже в процессе программирования сформировать библиотеку контрольных примеров. Эта работа должна выполняться не программистами, а группой испытателей, работающей параллельно с ними; однако необходимым условием является использование обеими группами одних и тех же исходных требований (о группах испытателей, или тестирования, речь будет идти ниже). Если требования сформулированы нечетко, то разработанные тесты не будут согласовываться с программой, что, вообще говоря, помогает своевременно уточнять проектную документацию. Параллельная разработка тестов служит не только целям подготовки программных испытаний, но и целям проверки правильности сформулированных требований к системе. Поэтому, если какая-либо программа не работает при прогонке предназначенных для нее тестов, этот акт свидетельствует скорее об ошибке в спецификации, чем ш тесте или в программе.
       Когда контрольные примеры проектируются специальной группой испытателей, может быть поставлен вопрос об отчетности этой группы по показателям, характеризующим процент опробованных тестов и процент тестов с благоприятными результатами испытаний. Располагая ежедневной информацией о результатах тестирования, руководство может контролировать ход программных испытаний.
       Если не предпринимать никаких попыток систематизации контрольных примеров, то сомнительна сама возможность отыскания каких-либо объективных способов оценки текущего состояния и успешности проведения испытаний. Немало систем программного обеспечения потерпело неудачу из-за пренебрежения руководством процедурой долгосрочного планирования и из-за недооценки напряженности и многосторонности усилий, необходимых для исчерпывающих испытаний системы.

5.21. ОЦЕНКА ПОЛНОТЫ ПРОВЕРКИ ПРОГРАММЫ

       Иногда желательно знать, насколько полно выполнено тестирование той или иной программы. Если программа велика но объему и содержит много логических блоков, то имеются основания полагать, что она будет проверена не особенно хорошо. Однако и в этом случае полезно иметь представление о степени полноты выполненной проверки программы. Элементами такой оценки могут служ:ить следующие данные:
       1) доля проверенных частей программы; здесь следует стремиться к 100%;
       2) процент условных переходов программы, которые притестировании проверялись по обоим логическим направлениям;здесь тоже следует стремиться к достижению предела 100%;
       3) степень сегментированности программы; хорошо сегментированные программы обычно легче тестировать, а следовательно, и само тестирование выполняется более тщательно;
       4) степень проверки различных ситуаций взаимодействия отдельных частей программы.
       Самым уязвимым местом тестирования является, как правило, последний элемент оценки.
       В больших программах нет возможности проверить все логические пути и опробовать все мыслимые сочетания данных, однако все главные ветви подлежат обязательной проверке. Что касается неосновных ветвей, то здесь программисту следует, полагаясь на свою интуицию, решить, какие из них могут обеспечить наибольший эффект тестирования (т.е. определить пути, наиболее подверженные ошибкам). На этом этапе процесс тестирования программы превращается из науки в искусство. В связи с тем что число сочетаний и перестановок логических путей может достигать астрономических значений, большая программа никогда не может быть испытана до конца. Однако особый интерес должны представлять два режима тестирования: минимальные и максимальные значения входных переменных. Обычно и те и другие определяются довольна легко. Основные факторы, которые испытатель программы должен принимать во внимание,— это важность конкретных логических путей, объем предварительного тестирования модулей, уязвимость программы для ошибок, затраты на испытания и возможная отдача.

5.22. ПОВТОРНОЕ ТЕСТИРОВАНИЕ

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

Повторяйте тестирование после каждого случая внесения изменений в программу.

5.23. ГРУППА ТЕСТИРОВАНИЯ

       Хорошо зарекомендовал себя такой подход к тестированию, когда создается специальная группа из опытных программистов, в задачу которой входит испытание всех программ и систем, прежде чем они будут окончательно переданы в постоянную эксплуатацию. Такой подход, однако, не освобождает программиста — автора программы — от обязанности ее первоначального тестирования. Группа тестирования обеспечивает только заключительную проверку программы уже после того, как программист гарантировал ее правильность.
       При этом группа тестирования может рассматривать программу как “черный ящик”, который при подаче на его вход конкретных входных данных должен выдавать известные результаты. Иногда людей, входящих в группу тестирования, называют профессиональными идиотами (отнюдь не в оскорбительном смысле).
       Такое название вошло в обиход потому, что люди, входящие в группу тестирования, хорошо умеют формировать неверные входные данные, т.е. такие, которые только идиоту (теперь уже в буквальном смысле этого слова) могли бы прийти в голову задать на вход программы. Иначе говоря, работа профессиональных испытателей должна носить разрушительный характер. Их цель заключается в том, чтобы попытаться привести систему к отказу. Отказ системы считается для них благоприятным исходом. В противоположность этому программист хочет, чтобы его программа в процессе испытаний работала безотказно. Таким образом, программист и испытатель преследуют разные цели.
       При создании постоянной группы тестирования у последней имеется возможность накапливать практический опыт по тестированию программ. Кроме того, свежий взгляд на программу часто может выявить такие проблемы, о которых сам программист мог и не задумываться. Следовательно, всегда целесообразно привлекать к тестированию своей программы кого-нибудь постороннего.
       Тестирование и утверждение, осуществляемые группой тестирования, иногда называют приемочным контролем. Если программисты проверяют свои программы, используя дедуктивно-аналитические методы и свое знание их внутренней логики, то профессионалы-испытатели применяют эмпирический подход к тестированию, поскольку имеют лишь общее представление о внутренней логике испытываемой программы. По своему положению испытатель находится где-то посередине между разработчиком и заказчиком программы.
       Испытатель должен полагаться на программные спецификации, и, если последние нечетки или непоследовательны, этот факт выявляется при испытаниях программы. Специалист, проводящий приемочный контроль, должен вынести свое суждение о том, соответствует ли программа целям и требованиям, сформулированным в программной спецификации. Если же группа тестирования не может уяснить этих целей и требований из спецификации, то ни о каком тестировании не может быть и речи.
       Испытатель должен также дать ответы на следующие вопросы:
       1. Достаточно ли точны полученные результаты?
       2. Обладает ли программная документация той полнотой, которая необходима для обеспечения удобства эксплуатации программы?
       3. Достаточно ли уделили внимания устранению ошибок?
       Преимущества создания специальной группы тестирования состоят в следующем:
       1. Эта группа не расформировывается по окончании каждого конкретного проекта и потому может накапливать опыт по тестированию и разрабатывать специальные методы и средства, которые обеспечивали бы более полную и эффективную проверку программ.
       2. Эта группа лояльна по отношению не только к пользователям, но и к коллегам, разрабатывающим программы. Задача группы тестирования заключается не в том, чтобы доказать правильность выполнения разработчиками своей работы, а в том, чтобы показать пригодность программы для конечного ее пользователя.
       3. В связи с тем что группа тестирования в какой-то мере выражает интересы заказчика, она имеет возможность склонить разработчика программы к ее надлежащему тестированию.

5.24. ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ

       О квалификации программиста судят по числу ошибок, обнаруживаемых в его программе после того, как она передана для широкого использования. Поэтому лучше иметь репутацию медлительного программиста, но создающего хорошие и тщательно выверенные программы, чем репутацию быстра работающего программиста, но разрабатывающего программы, испещренные ошибками. Создание программ без ошибок — трудоемкая работа.
       Традиционный вопрос программирования состоит в следующем: почему у нас никогда нет времени делать сразу хорошую программу, но зато всегда находится время на ее исправление? Торопливые и недобросовестные программисты подрывают репутацию других программистов, которые полагаются на достоверность результатов, выдаваемых чужими программами. Недаром все важные программы поручают разрабатывать программистам, которые известны как авторы правильных, тщательно выверенных программ. И если вы обладаете именно такой репутацией, то разумно всегда настаивать на выделении необходимого времени для обеспечения надлежащего качества тестирования программ. Нарушение этого условия приводит к неполному тестированию, в то время как его соблюдение дает хорошие результаты.

5.25. СОВЕТЫ ПРОГРАММИСТУ

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

5.26. УПРАЖНЕНИЯ

ПОВТОРЕНИЕ ПРОЙДЕННОГО
       1. Что означают следующие термины:
       а) живучесть программы,
       б) тестирование ветвей,
       в) тестирование элементов,
       г) приемочный контроль,
       д) промышленные испытания,
       е) избыточное тестирование,
       ж) проверка спецификаций,
       з) дымовой тест,
       и) системные испытания,
       к) полевые испытания,
       л) утверждение,
       м) профессиональный идиот.
       2. Чем отличается отладка программы от тестирования? Считаете ли вы, что тестирование и отладка программы должны быть самостоятельными стадиями? Если да, то почему?
       3. Какие обычно выдвигаются оправдания при отказе программы?
       4. Назовите ряд преимуществ планирования испытаний.
       5. Когда возникает потребность в тестировании программы профессиональным испытателем?
       6. Перечислите преимущества использования специальных подпрограмм при проведении тестирования.
       7. Назовите три способа определения результатов, которые должны соответствовать тестовым данным.
       8. Почему важно выполнять повторное тестирование?

ЗАДАНИЯ
       9. Как вы думаете, достаточно ли, чтобы контрольные примеры проверяли каждый исключительный случай отдельно или необходимо создавать еще и комбинации исключительных условий?
       10. Почему целесообразно обрабатывать большие объемы реальных тестовых данных, даже если нет возможности проверить достоверность этих результатов?
       11. Подыщите пример проекта, который был забракован на стадии тестирования. Проанализируйте, что явилось причиной такого положения: плохая подготовка тестов, неудачные проектные решения, изменение первоначальных требований или какие-либо другие факторы?
       12. Возьмите какую-нибудь простую программу и подсчитайте в ней количество различных логических путей. Возможно ли для этой программы исчерпывающее тестирование? Какие вам известны способы уменьшения количества тестируемых путей?
       13. Разработайте контрольные примеры для тестирования программы, которая
       а) считывает два целых числа и определяет их наибольший общий делитель,
       б) считывает два целых числа и определяет их наименьшее общее кратное.
       14. Разработайте контрольные примеры для проверки одной из программ, приведенных ниже. Определите нужные для каждой программы классы тестовых данных. Каково минимальное число тестов, необходимое для каждой программы? Что представляют собой их граничные точки?
       а) Программа извлечения квадратного корня.
       б) Программа, которая считывает три числа, являющиесяпо предположению длинами сторон некоторого треугольника, и затем печатает сообщение о типе треугольника (с разными сторонами, равнобедренный или равносторонний).
       в) Программа, которая считывает две даты и вычисляет количество прошедших дней.
       г) Программа, которая считывает значение числа N, затем считывает N чисел и печатает максимальное и минимальное значения чисел в группе.
       д) Программа, которая считывает некоторый элемент данных и проверяет, нет ли такого же элемента в хранимом массиве.
       15. Обратитесь к имеющимся у вас руководствам по программированию и определите допустимые области значений аргументов для следующих функций:
       логарифма с основанием 10, косинуса,
       логарифма с основанием е, гиперболического синуса,
       синуса, гиперболического косинуса.
       Напишите программу для вычисления одной из этих функций, попробуйте нарушить допустимые границы аргумента и понаблюдайте, что при этом произойдет.
       16. Любая функция, описываемая математическим выражением, вычисляется не совсем точно вследствие ошибок, свойственных вычислительному процессу. Попробуйте найти в руководстве данные о величине ошибки, возникающей при вычислении значений таких функций, как SQRT, SIN, TAN и др.
       17. Подготовьте для какой-нибудь программы
       а) тестовые данные, создаваемые программистом;
       б) реальные модифицированные данные;
       в) реальные данные в полном объеме.
       18. Дана формула

       Подготовьте контрольные примеры для проверки соответствующей программы
       а) в нормальных условиях,
       б) в экстремальных условиях,
       в) в исключительных ситуациях.
       19. Вспомните случай отказа какой-нибудь программы при рабочем прогоне. Определите, могли ли хорошо спланированные испытания выявить ошибку, вызвавшую отказ.
       20. Какие подпрограммы автоматического тестирования доступны в используемом вами языке программирования?
       21. Какие у вас имеются программы-утилиты, пригодные для использования при тестировании рабочих программ?
       22. Напишите программу для системы резервирования местна авиалиниях. Известно, что ежедневно выполняются пять рейсов под номерами 142, 148, 153, 181 и 191. Заказы принимаются только за неделю вперед. Ваша программа должнаудовлетворять заявки, аннулировать заказы и отказывать врезервировании мест, если самолет уже полностью укомплектован пассажирами. Для упрощения считайте, что самолет вмещает шесть пассажиров. Имеются три класса мест: первый, туристский и студенческий. Если хотите, программу можно усложнить, введя правило, по которому пассажиры первого класса имеют более высокий приоритет по сравнению с туристами, туристы — более высокий приоритет по сравнению состудентами и т.д. Разработайте несколько контрольных примеров для тестирования этой программы.
       23. Исходя из собственного опыта программирования, определите, какой процент вашего времени уходит на написание, отладку и тестирование программы.
       24. Возьмите какую-нибудь хорошо знакомую вам программу и разработайте тесты для проверки ее
       а) в нормальных условиях,
       б) в экстремальных условиях,
       в) в исключительных случаях.
       25. Напишите программу нахождения кубического корня из заданного числа и используйте ее же для проверки точности вычислений.
       26. Напишите программу нахождения корней квадратного уравнения

ах2+bх+с = 0.

       Корни определяйте по формуле

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

a b с а b с
1 2 1 6 5 -4
1 -1 -6 6*1030 5*1030 -4*1030
1 -10 1 10-30 -1030 1030
1 -1000 1 1.0 -4.0 3.9999999
1 -10000 1 1.0 -4.0 4.0
1 2 3 1.0 100.0001 0.01

       В заключение перепрограммируйте задачу заново с использованием данных повышенной точности и отметьте, улучшилось ли качество результатов.
       27. Разработайте тестовые данные для программы “естественного отбора”, работающей по следующему алгоритму: вычисляется среднее значение и и стандартное отклонение s видового признака t по формулам

       и затем объекту присваивается
       оценка А, если видовой признак принимает значения, большие или равные u+2s;
       оценка В, если видовой признак больше или равен u+s, но меньше u+2s;
       оценка С, если видовой признак больше или равен и—s, но меньше u+s;
       оценка D, если видовой признак больше или равен и2s, но меньше u—s;
       оценка F, если видовой признак принимает значения меньше и2s.
       28. Предположим, что ваша ЭВМ может работать с числами, содержащими не более шести значащих разрядов, новам необходимо выполнить сложение с сохранением восьмизначащих разрядов. Вы решили эту проблему путем разбиения задаваемых чисел на две тетрады и последующего раздельного сложения соответствующих тетрад. Так, например,число 00123456 было бы преобразовано в две тетрады 0012 и 3456 и сложение чисел 00123456 и 65432100 приняло бы вид

0012       3456
6543       2100
-----       -----
6555       5556

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

ПРОЕКТЫ
       29. В этой главе было рассмотрено несколько генераторов тестовых данных. Исследуйте, какие еще аналогичные пакеты программ доступны для использования, и оцените их полезность. Разработайте набор требований, которым должен удовлетворять хороший генератор тестовых данных.
       30. Выше были описаны некоторые дополнительные средства тестирования. Оцените степень их доступности и полезности. Ряд пакетов тестирования рассматривается также вкниге под редакцией Гетцеля [7], посвященной методам тестирования программ.
       31. Компаратор файлов. Напишите или возьмите готовую программу, которая считывала бы два файла, сравнивала ихмежду собой и выводила на печать лишь различающиеся элементы. Оформите необходимую программную документацию исделайте вашу программу общедоступной для пользователей вашего вычислительного комплекса. Подобная программа (FILCOM) на языке БЕЙСИК-ПЛЮС имеется в составе программного обеспечения машин PDP.
       32. Соберите статистические данные, характеризующие
       а) время, затрачиваемое на разработку, кодирование, отладку и тестирование программ,
       б) объем программ,
       в) используемый язык программирования,
       г) квалификацию программиста.
       Попытайтесь установить, существуют ли между этими характеристиками корреляционные связи; например, оказывает ли влияние используемый язык или время, затраченное на разработку программы, на продолжительность стадий тестирования или отладки. Тем, кто интересуется подобными вопросами, рекомендуется ознакомиться со статьей Боэма и книгой Вейнберга1 {Boehm В., Software and Its Impact: A Quantitative Assessment, Datamation (May 1973); Weinberg G., Psychology of Computer Programming.}).
       33. Для оценки надежности программного обеспечения могут использоваться следующие характеристики:
       а) среднее время наработки между ошибками, характеризующее средний интервал времени между прерываниями по программным ошибкам;
       б) среднее время восстановления, показывающее как быстро выполняется необходимая корректировка программы приобнаружении в ней ошибки;
       в) коэффициент готовности, определяющий долю времени, в течение которого программное обеспечение находится в работоспособном состоянии;
       г) график распределения числа ошибок по календарным месяцам, позволяющий по крутизне кривой приближенно судить о достигнутом прогрессе.
       Проанализируйте указанные характеристики и примените какие-нибудь из них для оценки надежности используемых вами программных средств.
       34. Напишите программу, которая генерировала бы достаточно полный набор тестовых данных для проверки способности используемого вами компилятора выявлять синтаксические ошибки в арифметических выражениях, операторах вызова подпрограмм и циклах оператора DO.
       35. Разработайте тестовые данные для детальной проверки одной из программ вычисления значений стандартных функций,таких, как абсолютная величина переменной, квадратныйкорень, синус, косинус и т.д. Какими должны быть критические значения тестовых данных?
       36. Многие пытаются доказывать правильность программы, используя те же приемы, что и математики при доказательстве правильности математических построений. Изучите современное состояние проблемы доказательства правильности машинных программ и подумайте, какое влияние на программирование может оказать применение подобных доказательств?
       37. Представляется полезным иметь на вооружении какой-либо метод определения степени полноты тестирования системпрограммного обеспечения. Ряд принципов, относящихся к этому вопросу, изложен в данной главе. Попытайтесь на основеизложенных представлений разработать свой способ оценки полноты проверки программ. Для выполнения этой работы рекомендуется также обратиться к материалу гл.4, касающейся проблем отладки.
       38. Журнал программных испытаний. Подобно обычному дневнику, этот документ фиксирует ход тестирования программ.Он может использоваться на заключительной стадии осуществления проекта для оценки успеха реализации плана испытаний. Кроме того, при наличии такого журнала никому не захочется оставлять в нем запись, аналогичную следующей: “Тест номер 2 должен быть повторен, так как был составлевмною неправильно”. Попробуйте вести такой журнал в процес-те испытаний какой-либо программы.
       39. Программы-профилировщики. Существует целый ряд,пакетов программ, предназначенных для анализа последовательности действий, выполняемых рабочей программой. Эти, служебные программы подсчитывают, какое количество раз выполнялся каждый оператор, а иногда еще и проверяют программу на наличие ошибок и не предусмотренных свойств. Вот некоторые из упомянутых пакетов:
       а) Пакеты COTUNE — профилировщик КОБОЛ а и FOTUNE — профилировщик ФОРТРАНа (САРЕХ Corp., 2613, North Third Street, Phoenix, Ariz. 85004).
       б) Пакет PROFILE (CACI, Inc., 12011 San Vincente Blvd Los Angeles, CA. 90049).
       в) Пакет метаКОБОЛ (Applied Data Research, Route 20&Center, Princeton, N.J. 08540).
       г) Пакет RXVP (General Research Corp., 5383 Hollister Ave.Santa Barbara, Ca).
       д) Пакет PET — Program Evaluator and Tester (McDonnell: Douglas Astronautics Company). Последние два пакета описываются Гилбом в статье, вошедшей в Труды симпозиума IEEE1973 г. по вопросам надежности программного обеспечения:ЭВМ и в книге Software Metrics. Ознакомьтесь с описаниямиперечисленных выше пакетов и отыщите информацию о другихподобных пакетах. Определите их сходство и различия и укажите полезные свойства этих программных средств применительно к их использованию для тестирования программ.
       40. Двойное программирование. Если определение результатов контрольных примеров вызывает серьезные затруднения, то можно применить подход, при котором две различные группы программируют одну и ту же задачу на разных языках. В связи с тем, что тестирование — процедура довольно дорогостоящая, а процессы разработки алгоритма и анализа при рассматриваемом подходе являются общими для обеих программ, введение дополнительного программирования обычно не очень сильно влияет на общие затраты. Попробуйте для какой-нибудь задачи применить двойное программирование; более детально с этим методом можно ознакомиться в гл.4 вышеупомянутой книги Гилба.
       41. Соберите данные о различных типах ошибок программирования. Предварительно изучите две полезные статьи наэту тему1 {Endres A., An Analysis of Errors and Their Causes in System Programs, IEEE Transactions on Software Engineering (June 1975): Boehm В., Software and Its Impact: A Quantitative Assessment, Datamation (May 1973).}).
       42. Степень сложности. Степенью сложности программы-могут служить следующие ее характеристики:
       а) число различных логических путей в программе,
       б) число операторов IF и их процент относительно использования всех операторов,
       в) средний размер модуля.
       Эти характеристики можно определять с помощью специальной программы. Напишите такую программу. Используя ее на практике, вы можете прийти к выводу, что некоторые программы излишне сложны и неудобны для тестирования или эксплуатации и поэтому они должны быть забракованы и переписаны заново. Предложите принцип подобного отбора.
       43. В книге Гилба Software Metrics в качестве меры надежности программного обеспечения предлагается величина,равная

      Количество прогонов, во время которых выявились ошибки
I=1 - ------------------------------------------------------
                    Общее количество прогонов

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

ЛИТЕРАТУРА
       1. Boehm В. W., Some Information Processing Implications of Air Force Space Missions in the 1970's, Astronautics and Aeronautics (January 1971).
       2. Boehm В.W., McClean R.К., Urfrig D.В., Some Experience with Automated Aids to the Design of Large-Scale Software, IEEE Transactions on SoftwareEngineering, March 1975.
       3. Conway R., Gries D., Program Testing, Primer on Structured Programmingusing PL/1, Cambridge, Mass., Winthrop Publishers, 1976.
       4. Elmendorf W. R., Controlling the Functional Testing of an Operating System, IEEE Transactions on Systems Science and Cybernetics, October 1969.
       5. Gruenberger F., Program Testing and Validating, Datamation (July 1968).
       6. A Guide to Testing in a Complex System Environment, IBM Corporation,GH20-1628.
       7. Hetzel W.C. (ed.), Program Test Methods, Englewood Cliffs, N. J., Prentice-Hall, 1973.
       8. Hice G.F., Turner W.S., Cashwell L.F., System Development Methodology, New York, American Elsevier, 1974.
       9. Hughes J.K., Michtom J.I., A Structured Approach to Programming, Englewood Cliffs, N.J., Prentice-Hall, 1977. (Имеется перевод: Хьюз Дж., Мюстом Дж. Структурный подход к программированию. М.: Мир, 1980)
       10. Management Planning Guide for a Manual of Data Processing Standards,IBM Corporation, C20-1670.
       11. Maynard J., Modular Programming, Philadelphia, Pa., Auerbach Publishers,1972.
       12. Rustin R. (ed.), Debugging Techniques in Large Systems, Englewood Cliffs,N. J., Prentice-Hall, 1971.




<<< Пред. Оглавление
Начало раздела
След. >>>

Дата последнего изменения:
Thursday, 21-Aug-2014 09:11:10 MSK


Постоянный адрес статьи:
http://az-design.ru/Projects/AzBook/src/005/01TS050.shtml