BAGATO-REFERATIV.COM.UA – ПЕРЕКЛАД З РОСІЙСЬКОЇ КУРСОВА “ПРОГРАМА складної структури з використанням меню"
ЗМІСТ 1. ВИДИ КОНТРОЛЮ ПРОГРАМ 2. ЦІЛІ, ПРИНЦИПИ І ЕТАПИ ТЕСТУВАННЯ 3. СТРУКТУРНЕ ТЕСТУВАННЯ 4. СПІЛЬНЕ ТЕСТУВАННЯ МОДУЛІВ 5. ФУНКЦІОНАЛЬНЕ ТЕСТУВАННЯ 6. ТЕСТУВАННЯ ПРОГРАМНОГО КОМПЛЕКУ ВЦІЛОМУ 7. ВІДЛАДКА ПРОГРАМ
ВИДИ КОНТРОЛЮ ПРОГРАМ Програмний комплекс - це сукупність програмних модулів, призначених для рішення однієї задачі і складових одне ціле. Основними різновидами контролю програмного забезпечення є візуальний, статичний і динамічний. Візуальний контроль - це перевірка програм “ за столом “, без використання комп'ютера. На першому етапі візуального контролю здійснюється читання програми, причому особлива увага приділяється наступним її елементам: коментарям і їх відповідності тексту програми; умовам в операторах умовного вибору ( IF, CASE ) і циклу; складним логічним виразам; можливості незавершення ітераціонних циклів ( WHILE, REPEAT, LOOP ). Другий етап візуального контролю - крізний контроль програми ( її ручна прокрутка на декількох зазделегідь підібраних простих тестах). Поширена думка, що більш вигідним є перекладання більшої частини роботи по контролю програмних засобів на комп'ютері, помилкова. Основний довід на користь цього такий: при роботі на комп'ютері головним чином удосконалюються навички у використанні клавіатури, в той час як програмістсьа кваліфікація присвоюється передусім за столом. Статичний контроль- це перевірка програми по її тексту (без виконання) за допомогою інструментальних засобів. Найбільш відомою формою статичного контролю є синтаксичний контроль програми за допомогою компілятора, при якому перевіряється відповідність тексту програми синтаксичним правилам мови програмування. Повідомлення компілятора звичайно діляться на декілька груп в залежності від рівня тягаря порушення синтаксису мови програмування: - інформаційні повідомлення і попередження, при виявленні яких компілятор, як правило, будує коректний об'єктний код і подальшу роботу з програмою (компонування, виконання) можлива (проте повідомлення цієї групи також повинні ретельно аналізуватися, оскільки їх поява також може свідчити про помилку в програмі - наприклад, через невірне розуміння синтаксису мови); - повідомлення про помилки, при виявленні яких компілятор намагається їх виправити і будує об'єктний код, але його коректність малоймовірна і подальша робота з ним швидше усього не можлива;
3 - повідомлення про серйозні помилки, при наявності яких побудований компілятором об'єктний код явно некоректний і його подальше використання неможливо; - повідомлення про помилки, виявлення яких привело до припинення синтаксичного контролю і побудови об'єктного коду. Однак, практично будь-який компілятор пропускає деякі види синтаксичних помилок. Місце виявлення помилки може знаходитися далеко по тексту програми від місця істинної помилки, а текст повідомлення компілятора може не вказувати на істинну причину помилки. Одна синтаксична помилка може спричинити генерацію компілятором декількох повідомлень про помилки (наприклад, помилка в описі змінної приводить до появи повідомлення про помилку в кожному операторі програми, що використовує цю змінну). Другою формою синтаксичного контролю може бути контроль структурованості програм, тобто перевірка виконання угод і обмежень структурного програмування. Прикладом подібної перевірки може бути виявлення в тексті програми ситуацій, коли цикл утвориться за допомогою оператора безумовного переходу (використання оператора GOTO для переходу вгору по тексту програми ). Для проведення контролю структурованості можуть бути створені спеціальні інструментальні засоби, а при їх відсутності ця форма статичного контролю може поєднуватися з візуальним контролем. Третя форма статичного контролю - контроль правдоподібності програми, тобто виявлення в її тексті конструкцій, які хоч і синтаксично коректні, але швидше усього містять помилку або свідчать про неї. Основні неправдоподібні ситуації: - використання в програмі неініціалізувати змінних (тобто змінних, що не набули початкового значення); - наявність в програмі описів елементів, змінних, процедур, міток, файлів, надалі що не використовуються в її тексті; - наявність в тексті програми фрагментів, що ніколи не виконуються; - наявність в тексті програми змінних, ні разу що не використовуються для читання після привласнюючи їм значень; - наявність в тексті програми явно нескінченних циклів; Навіть якщо присутність в тексті програми неправдоподібних конструкцій не приводить до її неправильної роботи, виправлення цього фрагмента підвищить ясність і ефективність програми, т. е. благотворно позначиться на її якості. Для можливості проведення контролю правдоподібності в повному об'ємі також повинні бути створені спеціальні інструментальні засоби, хоч ряд можливостей по контролю правдоподібності є в існуючих відлагоджувальний і звичайних компіляторах. 4 Потрібно зазначити, що створення інструментальних засобів контролю структурованості і правдоподібності програм може бути істотне спрощено при застосуванні наступних принципів: 1) проведення цих додаткових форм статичного контролю після завершення компіляції і тільки для синтаксично коректних програм; 2) максимальне використання результатів компіляції програми і, зокрема, інформації, що включається в лістинг компілятора;
3) замість повного синтаксичного розбору тексту програми, що перевіряється побудова для неї списку ідентифікаторів і списку операторів з вказівкою всіх їх необхідних ознак. При відсутності інструментальних засобів контролю правдоподібності ця фаза статичного контролю також може об'єднуватися з візуальним контролем. Четвертою формою статичного контролю програм є їх верифікація, тобто аналітичний доказ їх коректності. У інтуїтивному значенні під коректністю розуміють властивості програми, що свідчать про відсутність в ній помилок, допущених розробником на різних етапах проектування ( специфікації, проектування алгоритму і структур даних, кодування ). Коректність самої програми по відношенню до цілей, поставлених перед її розробкою ( тобто ця відносна властивість ). Відмінність поняття коректності і надійності програм в наступному: надійність характеризує як програму, так і її “оточення" ( якість апаратури, кваліфікацію користувача і т.п. ); кажучи про надійність програми, звичайно допускають певну, хоч і малу, частку помилок в ній і оцінюють імовірність їх появи. Надійність можна представити сукупністю наступних характеристик: 1) цілісність програмного засобу (здатність його до захисту від відмов); 2) живучість (здібність до вхідного контролю даних і їх перевірки в ході роботи); 3) завершеність (бездефектність готового програмного засобу, характеристика якості його тестування); 4) працездатність (здатність програмного засобу до відновлення своїх можливостей поле збоїв). Очевидно, що не всяка синтаксично правильна програма є коректною у вказаному вище значенні, т. е. коректність характеризує семантичні властивості програм. 5 З урахуванням специфіки появи помилок в програмах можна виділити дві сторони поняття коректності: 1) коректність як точна відповідність цілям розробки програми (які відображені в специфікації) при умові її завершення або часткова коректність; 2) завершення програми, тобто досягнення програмою в процесі її виконання своєї кінцевої точки. У залежності від виконання або невиконання кожного з двох названих властивостей програми розрізнюють шість задач аналізу коректності: 1) доказ часткової коректності; 2) доказ часткової некоректність; 3) доказ завершення програми; 4) доказ незавершення програми; 5) доказ тотальної (повної ) коректності (тобто одночасне рішення першої і третьої задач); 6) доказ некоректність (рішення другої або четвертої задачі). Методи доказу часткової коректності програм як правило спираються на аксіоматичний підхід до формалізації семантики мов програмування. У цей час відомі аксіоматичні семантики Паськаля, підмножини ПЛ/1 і деяких інших мов. Аксіоматична семантика мови програмування являє собою сукупність аксіом і правил висновку. За допомогою аксіом задається семантика простих операторів мови (привласнення, введення - виведення, процедур). За допомогою правил висновку описується семантика складових операторів або керуючих структур (послідовності, умовного вибору, циклів). Серед цих правил висновку треба відмітити правило висновку для операторів циклу оскільки воно вимагає знання інваріанта циклу (формули, істинності якій не змінюється при будь-якому проходженні циклу). Побудова інваріанта для оператора циклу по його тексту є алгоритмічно не вирішуваної задачі, тому для опису семантики циклів потрібно свого роду "підказка" від розробника програми. Найбільш відомим з методів доказу часткової коректності програм є метод індуктивних тверджень запропонований Флойдом і вдосконалений Хоаром. Метод складається з трьох етапів. Перший етап - отримання анотованої програми. На цьому етапі для синтаксично правильної програми повинні бути задані твердження на мові логіки предикатів першого порядку: 6 вхідний предикат; вихідний предикат; за одному твердженням для кожного циклу (ці твердження задаються для вхідної точки циклу і повинні характеризувати семантику обчислень в циклі). Доказ неістинності умов коректності свідчить про неправильність програми, або її специфікації, або програми і специфікацій. Незважаючи на достатню складність процесу верифікації програми і на те, що навіть успішно завершена верифікація не дає гарантій якості програми ( так як помилка може міститися і у верифікації ), застосування методів аналітичного доказу правильності дуже корисне для уточнення значення програми, що розробляється, а знання цих методів благотворно позначається на кваліфікації програміста. Нарешті, динамічний контроль програми - це перевірка правильності програми при її виконанні на комп'ютері, тобто тестування. ЦЕЛІ, ПРІНЦИПИ І ЕТАПИ ТЕСТУВАННЯ
Кожному програмісту відомо, скільки часу і сил йде на відладку і тестування програм. На цей етап доводиться біля 50% загальної вартості розробки програмного забезпечення. Але не кожний з розробників програмних засобів може вірно визначити мету тестування. Нерідко можна почути, що тестування - це процес виконання програми з метою виявлення в ній помилок. Але ця мета недосяжна: ні яке саме ретельне тестування не дає гарантії, що програма не містить помилок. Інше визначення тестування ( у м. Майерса ) тестування - це процес виконання програми з метою виявлення в ній помилок. Таке визначення мети стимулює пошук помилок в програмах. Звідси також ясно, що “вдалим" тестом є такою, на якому виконання програми завершилося з помилкою. Навпаки, “невдалим можна назвати тест, що не дозволив виявити помилку в програмі. Визначення м. Маєрса вказує на об'єктивну трудність тестування: це деструктивний ( тобто зворотний творчому ) процес. Оскільки програмування - процес конструктивний, ясно, що більшості розробників програмних засобів складно “перемкнутися" при тестуванні створеної ними продукції.
7 У Майерса сформульовані також основні принципи організації тестування: 1) необхідною частиною кожного тесту повинно бути опис очікуваних результатів роботи програми, щоб можна було швидко з'ясувати наявність або відсутність помилки в ній; 2) слідує по можливості уникати тестування програми її автором, так як крім вже вказаної об'єктивної складності тестування для програмістів тут присутній і той чинник, що виявлення недоліків в своїй діяльності суперечить людській психології ( однак відладка програми ефективніше усього виконується саме автором програми ); 3) по тих же міркуваннях організація - розробник програмного забезпечення не повинна “одноосібно " його тестувати ( повинні існувати організації, що спеціалізуються на тестуванні програмних засобів ); 4) повинні бути правилом доскональне вивчення результатів кожного тесту, щоб не пропустити малопомітну на поверхневий погляд помилку в програмі; 5) необхідно ретельно підбирати тест не тільки для правильних ( передбачених ) вхідних даних, але і для неправильних (непередбачених); 6) при аналізі результатів кожного тесту необхідно перевіряти, чи не робить програма того, що вона не повинна робити; 7) потрібно зберігати використані тести (для підвищення ефективності повторного тестування програми після її модифікації або установки у замовника); 8) тестування не повинне плануватися виходячи з припущення, що в програмі не будуть виявлені помилки (зокрема, потрібно виділяти для тестування достатні тимчасові і матеріальні ресурси); 9) потрібно враховувати так званий “принцип скупчення помилок": імовірність наявності не виявлених помилок в деякій частині програми прямо пропорційна числу помилок, вже виявлених в цій частині; 10) потрібно завжди пам'ятати, що тестування - творчий процес, а не відноситися до нього як до рутинного заняття. Існує два основних вигляду тестування: функціональне і структурне. При функціональному тестуванні програма розглядається як “чорний ящик" (тобто її текст не використовується). Відбувається перевірка відповідності поведінки програми її зовнішньої специфікації. Чи Можливо при цьому повне тестування програми? Очевидно, що критерієм повноти тестування в цьому випадку був би перебір всіх можливих значень вхідних даних, що нездійсненно. 8 Оскільки вичерпне функціональне тестування неможливе, мова може йти об розробки методів, що дозволяють підбирати тести не “наосліп", а з великою імовірністю виявлення помилок в програмі. При структурному тестуванні програма розглядається як “білий ящик" (тобто її текст відкритий для користування ). Відбувається перевірка логіки програми. Повним тестуванням в цьому випадку буде таке, яке приведе до перебору всіх можливих шляхів на графі передач управління програми (її керуючому графові). Навіть для середніх по складності програм числом таких шляхів може досягати десятків тисяч. Якщо обмежитися перебором тільки лінійних не залежних шляхів, то і в цьому випадку вичерпне структурне тестування практично неможливе, т. к. неясне, як підбирати тести, щоб забезпечити “покриття" всіх таких шляхів. Тому при структурному тестуванні необхідно використати інші критерії його повноти, що дозволяють досить просто контролювати їх виконання, але не даючі гарантії повної перевірки логіки програми. Але навіть якщо передбачити, що вдалося досягнути повного структурного тестування деякої програми, в ній проте можуть міститися помилки, так як 1) програма може не відповідати своїй зовнішній специфікації, що зокрема, може привести до того, що в її керуючому графові виявляться пропущеними деякі необхідні шляхи; 2) не будуть виявлені помилки, поява яких залежить від даних (, що обробляються тобто на одних початкових даних програма працює правильно, а на інших - з помилкою). Таким чином, ні структурне, ні функціональне тестування не може бути вичерпним. Розглянемо детальніше основні етапи тестування програмних комплексів. У тестування багатомодульных програмних комплексів можна виділити чотири етапи: 1) тестування окремих модулів; 2) спільне тестування модулів; 3) тестування функцій програмного комплексу (тобто пошук відмінностей між розробленою програмою і її зовнішньою специфікацією ); 4) тестування всього комплексу загалом (тобто пошук невідповідності створеного програмного продукту сформульованим раніше цілям проектування, відображеним звичайно в технічному завданні). На перших двох етапах використовуються передусім методи структурного тестування, так як на подальших етапах тестування ці методи використати складніше через великі розміри програмного забезпечення, що перевіряється; подальші етапи тестування орієнтовані на виявлення помилок різного типу, які не обов'язково пов'язані з логікою програми. При тестуванні як окремих модулів, так і їх комплексів повинні бути вирішені дві задачі: 1) побудова ефективної безлічі тестів; 2) вибір способу комбінування (збирання) модулів при створенні трестируємого варіанту програми. СТРУКТУРНЕ ТЕСТУВАННЯ Оскільки вичерпне структурне тестування неможливе, необхідно вибрати такі критерії його повноти, які допускали б їх просту перевірку і полегшували б цілеспрямований підбір тестів. Найбільш слабою з критеріїв повноти структурного тестування є вимога хоч би однократного виконання (покриття) кожного оператора програми. Більш сильним критерієм є так званий критерій С1: кожна гілка алгоритму (кожний перехід) повинна бути пройдена (виконана) хоч би один раз. Для більшості мов програмування покриття переходів забезпечує і покриття операторів, але, наприклад, для програм на мові ПЛ/1 додатково до покриття всіх гілок потрібно всіх додаткових точок входу в процедуру (що задаються операторами ENTRY) і всіх ON - одиниць. Використання критерію покриття умов може викликати підбір тестів, що забезпечують перехід в програмі, який пропускається при використанні критерію С1 (наприклад, в програмі на мові Паськаль, що використовує конструкцію циклу WHILE х AND у DO..., застосування критерію покриття умов вимагає перевірки обох варіантів виходу з циклу: NOT х і NOT у ). З іншого боку покриття умов може не забезпечувати покриття всіх переходів. Наприклад, конструкція IF А AND В THEN... вимагає по критерію покриття умов двох тестів (наприклад, А=true, В=false і А=false, В=true ), при яких може не виконуватися оператор, розташований в then - гілки оператора if. Практично єдиним засобом, що надається сучасними системами програмування, є можливість визначення частоти виконання різних операторів програми (її профілізації). Але за допомогою цього інструмента підтримки тестування можна перевірити виконання тільки найслабішого з критеріїв повноти - покриття всіх операторів. Правда, за допомогою цього ж інструмента можна перевірити і виконання критерію С1. Але для цього заздалегідь текст програми повинен бути перетворений таким чином, щоб всі конструкції умовного вибору (IF і CASE 10 або SWITCH ) містили гілки ELSE або DEFAULT, хоч би і пусті. У цьому випадку всі гілки алгоритму, ті, що не виконувалися на даному тесті будуть “видимі" з таблиці частоти виконання операторів перетвореної програми. Актуальною залишається задача створення інструментальних засобів, що дозволяють: 1) нагромаджувати інформації про покриті і непокриті гілки для всіх використаних тестів; 2) виділяти розробнику ще не покриті при тестуванні дільниці програми, полегшуючи вибір наступних тестів; 3) підтримувати більш могутні критерії повноти структурного тестування.
СПІЛЬНЕ ТЕСТУВАННЯ МОДУЛІВ.
Відомі два підходи до спільного тестування модулів: покрокове і монолітне тестування. При монолітному тестуванні спочатку по окремості тестуються всі модулі програмного комплексу, а потім всі вони об'єднуються в робочу програму для комплексного тестування. При покроковому тестуванні кожний модуль для свого тестування підключається до набору вже перевірених модулів. У першому випадку для автономного тестування кожного модуля потрібний модуль - драйвер ( тобто допоміжний модуль, що імітує виклик модуля, що тестується ) і один або декілька модулів - заглушок ( тобто допоміжних модулів, що імітують роботу модулів, що викликаються з того, що тестується). При покроковому тестуванні модулі перевіряються не ізольовано один від одного, тому потрібні або тільки драйвери, або тільки заглушки.
А B C D E F мал. 1 При порівнянні покрокового і монолітного тестування можна відмітити наступні переваги першого підходу: 1) менша трудомісткість (для прикладу на мал. 1 при монолітному тестуванні потрібно 5 драйверів і 5 заглушок; при покроковому тестуванні потрібні або тільки 5 драйверів - якщо модулі підключаються “знизу вгору ", - або тільки 5 заглушок - якщо модулі підключаються “зверху вниз"); 2) більш раннє виявлення помилок в інтерфейсах між модулями (їх збирання починається раніше, ніж при монолітному тестуванні ); 3) легше відладка, тобто локалізація помилок (вони в основному пов'язані з останнім з підключених модулів ); 4) більш досконалі результати тестування (більш ретельна перевірка спільного використання модулів). Є переваги і у монолітного тестування: 1) менше витрата машинного часу (хоч через більшу складність відладки може бути потрібна додаткова перевірка програми і ця перевага буде зведена на немає); 2) надається більше можливостей для організації паралельної роботи на початковому етапі тестування. Загалом більш доцільним є вибір покрокового тестування. При його використанні можливі дві стратегії підключення модулів: низхідна і висхідна. Низхідне тестування починається з головного (або верхнього) модуля програми, а вибір наступного модуля, що підключається відбувається з числа модулів, що викликаються з вже протестованих. Одна з основних проблем, виникаючих при низхідному тестуванні, - створення заглушок. Це тривіальна задача, т. к. як правило недостатньо, щоб в заглушці виконувався висновок відповідного інформаційного повідомлення і повернення завжди одних і тих же значень вихідних даних. Інша проблема, яку необхідно вирішувати при низхідному тестуванні, - форма представлення тестів в програмі, оскільки, як правило, головний модуль отримує вхідні дані не безпосередньо, а через спеціальні модулі введення, які при тестуванні на початку замінюються заглушками. Для передачі в головний модуль різних тестів треба або мати декілька різних заглушок, або записати ці тести в файл у зовнішній пам'яті і за допомогою заглушки прочитувати їх. Оскільки після тестування головного модуля процес перевірки може продовжуватися по-різному, потрібно дотримуватися наступних правил: а) модулі, що містять операції введення-висновку, повинні підключатися до тестування як можна раніше; б) критичні (тобто найбільш важливі ) для програми загалом модулі також повинні тестуватися насамперед. 12 Основні переваги низхідного тестування: вже на ранній стадії тестування є можливість отримати працюючий варіант програми, що розробляється; швидко можуть бути виявлені помилки, пов'язані з організацією взаємодія з користувачем. Недоліки низхідної стратегії продемонструються з допомогою мал. 2. Допустимо, що на наступному кроці тестування заглушка модуля Н замінюється його реальним текстом. Тоді 1) може виявитися важким або навіть неможливим побудувати такий тест на вході модуля J, якому відповідав би будь-яка задана наперед послідовность значень даних на вході модуля Н; 2) не завжди виявиться можливим легко оцінити відповідність значень даних на вході модуля J необхідним тестам для перевірки модуля Н; 3) т. я. результати виконання програми на побудованому для перевірки модуля Н тесті виводяться не їм, а модулем I, може виявитися важким відновлення дійсних результатів роботи модуля Н. Інші проблеми, які можуть виникати при низхідному тестуванні: з'являється знада поєднання низхідного проектування з тестуванням, що, як правило, безрозсудно, так як проектування - процес інтерактивний і в ньому неминучий повернення на верхні рівні і виправлення прийнятих раніше рішень, що знецінює результати вже проведеного тестування; може виникнути бажання перейти до тестування модуля наступного рівня до завершення тестування попереднього по об'єктивних причинах (необхідності створення декількох версій заглушок, використання модулями верхнього рівня ресурсів модулів нижніх рівнів). При висхідному тестуванні перевірка програми починається з термінальних модулів (тобто тих, які не викликають не яких інших модулів програми). Ця стратегія багато в чому протилежна низхідному тестуванню (зокрема, переваги стають недоліками і навпаки). Немає проблеми вибору наступного модуля, що підключається - враховується лише те, щоб він викликав тільки вже протестовані модулі. На відміну від заглушок драйвери не повинні мати декілька версій, тому їх розробка в більшості випадків простіше (крім того, використання коштів автоматизації і відладки полегшує створення якраз драйверів, а не заглушок). Інші достоїнства висхідного тестування: оскільки немає проміжних модулів (модуль, що тестується є для робочого варіанту програми модулем самого верхнього рівня), немає проблем, пов'язаних з можливістю або тружністю завдання тестів; немає можливості поєднання проектування з тестуванням; немає труднощів, що спричиняють бажання перейти до тестування наступного модуля, не завершивши перевірки попереднього. Основними недоліком висхідного тестування є те, що перевірка всієї структури програмного комплексу, що розробляється можлива тільки на завершальній стадії тестування. Хоч однозначного висновку про переваги тієї або іншої стратегії пошагового тестування зробити не можна (треба враховувати конкретні характеристики програми, що тестується ), в більшості випадків більш переважним є висхідне тестування. На третьому етапі тестування програмних комплексів (тестуванні функцій) використовуються передусім методи функціонального тестування. ФУНКЦІОНАЛЬНЕ ТЕСТУВАННЯ Огляд методів проектування тестів при функціоналному тестуванні почнемо з методу зквівалентного роздріблення. Так як нашою метою є побудови безлічі тестів, що характеризується найвищою імовірністю виявлення більшості помилок в програмі, що тестується, то тест з цієї безлічі повинен: 1) зменшувати (більш ніж на одиницю) число інших тестів, які повинні бути разроблені для досягнення зазделегідь поставленої мети “задовільного" тестування; 2) покривати собою значну частину інших можливих тестів. Іншими словами: 1) кожний тест повинен містити в собі перевірку найбільшого числа вхідних, що задаються зовнішньою специфікацією умові (обмежень на вхідні дані) для того, щоб мінімізувати загальне число необхідних тестів; 2) необхідно розбити область значень вхідних даних на кінцеве число підобластей (які будуть називатися класами еквівалентності), щоб можна було вважати кожний тест, що є представником деякого класу, еквівалентним будь-якому другому тесту цього класу (тобто що виявляє одні і ті ж помилки). У загальному випадку використання терміну “клас еквівалентності" є тут не цілком точним, так як виділені підобласті можуть перетинатися. Проектування тестів по методу еквівалентного роздроблення проводиться в два етапи: виділення по зовнішній специфікації класів еквівалентності; побудова безлічі тестів. На першому етапі відбувається вибір з специфікації кожної вхідної умови і роздроблення його на дві або більше за групу, відповідні так званим “правильним" (ПКЕ) і “неправильним" класом еквівалентності (НКЕ), тобто областям допустимих для програми і недопустимих значень вхідних даних. Цей процес залежить від вигляду вхідної умови. Якщо вхідна умова описує область (наприклад, х <=0.5) або кількістю (наприклад, розмір масиву А рівний 50) допустимих значень вхідних даних, то визначаються один ПКЕ (х <=0.5 або розмір А рівний 50) і два НКЕ (х< -0.5; х>0.5 або розмір А менше 50; розмір А більше 50). Якщо вхідна умова описує дискретну безліч допустимих значень вхідних даних (наприклад, В може дорівнює -1, 0 або 1), то визначаються ПКЕ для кожного значення з безлічі (в даному прикладі 3) і одного НКЕ (В<>-1 & В<>0 & В<>1). Якщо вхідна умова описує ситуацію “помилково бути " (наприклад, N>0), то визначаються один ПКЕ (N>0) і один НКЕ (N<=0). На другому етапі методу еквівалентного роздроблення виділені класи еквівалентності використовуються для побудови тестів: кожному класу привласнюється свій номер; проектуються тести для ПКЕ таким чином, що кожний тест покриває як можна більше ще не покритих ПКЕ, доти, поки все ПКЕ не будуть покриті; проектуються тести для НКЕ таким чином, що кожний тест покриває один і тільки один НКЕ, доти, поки все НКЕ не будуть покриті. Порушення третьої умови приводить до того, що деякі тести з недопустимими значеннями вхідних даних перевіряють тільки одну помилку і приховують реакцію програми на інші помилки. Метод еквівалентного роздроблення значно краще випадкового підбору тестів, але має свої недоліки. Основної з них - пропуск певних типів високоефективних тестів (тобто тестів, що характеризуються великою імовірністю виявлення помилок). Від цього недоліку багато в чому вільний метод аналізу граничних умов. Під граничними умовами розуміють ситуації, виникаючі безпосередньо на кордоні певної в специфікації вхідної або вихідної умови, вище або нижче її. Метод аналізу граничних умов відрізняється від методу еквівалентного роздроблення наступним: вибір будь-якого представника класу еквівалентності здійснюється таким чином, щоб перевірити тестом кожний кордон цього класу; при побудові тестів розглядаються не тільки вхідні умови, але і вихідні (тобто певні у зовнішній специфікації обмеження на значення вхідних даних). Загальні правила методу аналізу граничних умов: 1) побудувати тести для кордонів області допустимих значень вхідних даних і тести з недопустимими значеннями, відповідними незначному виходу за межі цієї області (наприклад, для області [-1.0; 1.0] будуємо тести -1.0; 1.0; -1.001; 1.001); 2) побудувати тести для мінімального і максимального значень вхідних умов, що визначають дискретну безліч допустимих значень вхідних даних, і тести для значень, великих або менших цих величин (наприклад, якщо вхідний файл може містити від 1 до 225 записів, то вибираються тести для порожнього файла, що містить 1, 255 і 256 записів); 3) використати правило 1 для кожної вихідної умови (наприклад, програма обчислює щомісячну витрату приватної особи або невеликого підприємства, мінімум якого 0.00 $, а максимум 1165.50 $; тоді необхідне построїти тести, що викликають негативну витрату, витрати, рівну 0.00 $ і 1165.50 $, і витрату, більшу 1165.50 $); 4) використати правило 2 для кожної вихідної умови (наприклад, програма шукає і відображає на екрані дисплея найбільш відповідні, в залежності від вхідної умови, реферати статей, але не більш чотирьох; тоді необхідно побудувати тести, що приводять до відображення 0, 1, 4 рефератів і спроб помилкового відображення 5 рефератів); 5) якщо вхідні і вихідні дані програмиявляють собою впорядковану безліч (послідовний файл, лінійний список, таблицю), то при тестуванні зосередити увагу на першому і останньому елементі безлічі; 6) спробувати знайти і перевірити тестами інші граничні умови. Важливість перевірки кордонів вихідних умов пояснюється тим, що не завжди граничним значенням вхідних даних відповідають граничні значення результатів роботи програм. Для ілюстрації необхідності аналізу граничних умов приведемо тривіальний приклад. Нехай є програма, що здійснює введення трьох чисел що інтерпретує їх як довжину сторін трикутника і що виводить повідомлення про тип трикутника (“різносторонній", “рівнобедрений" або “рівносторонній "). Допустимо також, що в програмі міститься помилка: при перевірці умови побудови трикутника (сума довжин будь-яких двох сторін повинна бути більше третьою) використовується операція відношення >= замість >. При проектуванні тестів по методу еквівалентного роздроблення будуть побудовані тести для випадків можливості побудови трикутника (наприклад, 3, 4, 5) і неможливості його побудови (наприклад, 1, 2, 4), тобто помилка в програмі не буде виявлена (на вхідні дані 1, 2, 3 буде виведене повідомлення “різносторонній трикутник"). Але подібний тест буде отриманий при використанні методу аналізу граничних умов. Аналіз граничних умов - один з найбільш корисних методів проектування тестів. Але він часто виявляється неефективним через те, що граничні умови іноді ледве вловимі, а їх виявлення вельми важке. Загальним недоліком двох розглянутих вище методів функціонального тестування є те, що при їх застосування досліджуються досліджуються можливі комбінації вхідних умов. Слідує, правда, помітити, що через вельми велике число таких комбінацій, їх аналіз викликає істотні ускладнення. Але існує метод (метод функціональних діаграм), що дозволяє в цьому випадку систематичним образом вибрати високо ефективні тести. Корисним побічним ефектом цього методу є виявлення неповноти і суперечності у зовнішніх специфікаціях. Функціональна діаграма - це текст на деякій формальній мові, на яку транслюється специфікація, складена на природному або напівформальному мовах. Далі буде називатися причиною окрема вхідна умова і слідством - вихідна умова або перетворення системи (тобто залишкова дія програми, викликана певною вхідною умовою або їх комбінацією). Наприклад, для програми оновлення файла зміна в ньому є перетворенням системи, а підтверджуюча ця зміна повідомлення - вихідною умовою. Метод функціональних діаграм складається з шести основних етапів. На першому з них (необов'язковому) зовнішня специфікація великого розміру розбивається на окремі дільниці (наприклад, специфікація компілятора мови програмування розбивається на дільниці, що визначають синтаксичний контроль окремих операторів мови). На другому етапі в специфікації виділяються причини і слідства, а на третьому - аналізується семантичний зміст специфікації і вона перетворюється в булевський граф, зв'язуючий причини і слідства і що називається функціональною діаграмою. На мал. 3 приведені базові символи для запису функціональних діаграм (кожний вузол функціональної діаграми може знаходитися в стані 1 - “існує" - або 0 - “не існує"). а) Тотожність: (а(1(>b(1) & (а(0(>b(0)))))) а b би) Заперечення: (а(1(>b(0) & (a(0(>b(1)))))) ~ а b в) Диз'юнкція: (a(1(b(1(>з(1) & (a(0&b(>0 >з(0)))))))) а ( з b г) Кон'юнкція: (a(1&b(1(>з(1) & (a(0(b(0(>з(0))))))))) а & з b мал. 3 На четвертому етапі функціональна діаграма забезпечується коментарями, які задають обмеження на комбінації причин і слідств. На мал. 4 приведені знаки коментарів, задаючих ці обмеження. а) Виключення однієї з причин:
а Е (a(1(b(1)^~(a(1&b(1)) ( (a(0&b(0)))))))) b
б) Включення хоч би однієї причини: а I (a(1(b(1)&~(a(0&b(0) ))))) b в) Існує одна і тільки одна причина: а ПРО (a(1(b(1)&~(a(1&b(1)&~(a(0&b(0) ))))))) b г) Одна причина спричиняє за собою іншу: а R ~(a(1&b(0) ))
b
д) Одне слідство приховує в собі інше: а M (a(1&b(0)&(a(1&b(1) ))))
b мал. 4 П'ятий етап - функціональна діаграма перетворюється в таблицю рішень: вибирається слідство, яке встановлюється в 1; знаходяться всі комбінації причин (з урахуванням обмежень), які встановлюють вибране слідство в 1