Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Дипломний проект.doc
Скачиваний:
27
Добавлен:
27.10.2018
Размер:
4.11 Mб
Скачать

4 Особливості реалізації

Усі існуючі соціальні мережі не стоять на місці – вони розвиваються. Саме тому, при розробці веб-застосувань, потрібно враховувати можливі майбутні зміни коду будь-якої частини системи, а особливо дизайну та логіки програми. Таким чином було вирішено розробити таке програмне забезпечення, яке забезпечило б можливість одночасної розробки для дизайнера та програміста. Таким програмним забезпеченням являється шаблонізатор. Існують вже реалізовані шаблонізатори, але було вирішено розробити власний, так як він матиме ряд переваг. Наприклад, більшість готових шаблонізаторів, той самий Smarty, не підтримують повноцінну вставку коду PHP. З власноруч розробленим шаблонізатором отримуєш повний контроль над процесом відображення веб-сторінки.

Принцип роботи шаблонізатора заснований на тому, щоб використовувати html-шаблони для генерації кінцевих html-сторінок. Таким чином, можна, фактично відділити бізнес-логіку від представленя, адже реальна html-сторінка міститиме фактично тільки посилання на PHP код, а при компіляції через шаблонізатор, на вихід матимемо чистий HTML.

Створений шаблонізатор працює на основі регулярних виразів. Фактично, за допомогую їх, шаблонізатор і шукає збіг шаблону з кодом html-сторінки. Наведу приклади використованих регулярних виразів:

  1. define('PREG_FUNCTION', '/\{\d*\s*[\w\s]*[\w\:\-\>\$]*\([\S]*\)\s*\}/');

  2. define('PREG_FUNCTION_CALL', '/[^\d\s][\w*\:\-\>*]*/');

  3. define('PREG_FUNCTION_QUEUE', '/^\s*[\d]*\s/');

  4. define('PREG_FUNCTION_PARAMS', '/\([\S*\s*]*\)/');

  5. define('PREG_FUNCTION_ADDITION', '/\s[\w]*\s/');

  6. define('PREG_VAR', '/\{\s*\w*[\:*\-\>\$*\w*]*\s*\}/');

  7. define('PREG_VAR_PARTS', '/\$*[\w]*[\:]*[\-\>]*/');

  8. define('PREG_LOCALE', '/\{\d\d\d\d\d\d\}+/');

  9. define('PREG_FILE', '/\{\|\w+.*/');

  10. define('PREG_INSERT', '/\{\s*\w*\/\w*\s*\}/');

  11. define('PREG_CODECHUNKS', '/\{\&\&\&\*\&\&\d*\&\&\*\&\&\&\}/').

Перший регулярний вираз визначає функцію у шаблоні, тега виду «{phpinfo()}» або «{11 echo time()}» загального вигляду "{NNN AA CCCCC(PPPP)}". Наступні вирази детально розбирають знайдений тег:

  1. «'PREG_FUNCTION_CALL'» - виклик функції (ССССС);

  2. «'PREG_FUNCTION_QUEUE'» - порядковий номер виклику функції (NNN);

  3. «'PREG_FUNCTION_PARAMS'» - параметри функції (PPPP);

  4. «'PREG_FUNCTION_ADDITION'» - вставка перед викликом, наприклад «echo» (AA);

  5. «'PREG_VAR'» - змінна у шаблоні; «'PREG_VAR_PARTS'» - розбиття змінної на частини;

  6. «'PREG_LOCALE'» - вставка локалізованої змінної, тег виду «{123456}»;

  7. «'PREG_FILE'», «'PREG_INSERT'» та «'PREG_CODECHUNKS'» - підключають файл темплейта.

Таким чином, ми отримуємо вхідні дані, для підстановки шаблону. Знаходимо потрібний шаблон, записуємо його у змінну:

$str = file_get_contents(DIR_VIEWS.$filename).

Далі підставляємо у шаблон змінні, функції, саме ті, що зчитали регулярними виразами описаними вище:

$str = preg_replace_callback(PREG_FILE, 'Compiler::insert_file', $str);

$str = preg_replace_callback(PREG_LOCALE, 'Compiler::insert_localized', $str);

$str = preg_replace_callback(PREG_VAR, 'Compiler::insert_var', $str);

$str = preg_replace_callback(PREG_FUNCTION, 'Compiler::insert_function', $str);

$str = preg_replace_callback(PREG_INSERT, 'Compiler::insert_insert', $str);

$str = Compiler::get_execute_code().$str;

$str = preg_replace_callback('/[\w\)][^;!?]*[\s]*\?\>/', create_function('$ce', 'return str_replace("?>", "; ?>", $ce[0]);'), $str);

$str = preg_replace('/\?\>[\s\z\Z\W]*\<\?php/', "\n", $str);

$str = preg_replace_callback(PREG_CODECHUNKS, 'Compiler::insert_codechunks', $str);

Compiler::$codechunks = array();

Compiler::$output = $str;

file_put_contents($file, Compiler::$output);

У останній строчці шаблон записується вже в файл, а далі бере участь у генерації кінцевої сторінки.

ТЕСТУВАННЯ

Тестування програмного забезпечення — це процес, що використовується для виміру якості розроблюваного програмного забезпечення. Зазвичай, поняття якості обмежується такими поняттями, як коректність, повнота, безпечність, але може містити більше технічних вимог, які описані в стандарті ISO 9126. Тестування - це процес технічного дослідження, який виконується на вимогу замовників, і призначений для вияву інформації про якість продукту відносно контексту, в якому він має використовуватись. До цього процесу входить виконання програми з метою знайдення помилок.

Якість не є абсолютною, це суб'єктивне поняття. Тому тестування не може повністю забезпечити коректність програмного забезпечення. Воно тільки порівнює стан і поведінку продукту зі специфікацією. При цьому треба розрізняти тестування програмного забезпечення і забезпечення якості програмного забезпечення, до якого належать усі складові ділового процесу, а не тільки тестування.

Існує багато підходів до тестування програмного забезпечення, але ефективне тестування складних продуктів - це по суті дослідницький процес, а не тільки створення і виконання рутинної процедури. Рівні тестування:

  1. модульне тестування тестує мінімальний компонент програми, або модуля. Кожний модуль тестується для перевірки правильності його реалізації.

  2. інтеграційне тестування виявляє дефекти в інтерфейсах та у взаємодії між компонентами (модулями).

  3. системне тестування тестує інтегровану систему для перевірки відповідності всім вимогам.

  4. системне інтеграційне тестування перевіряє, чи система інтегрується в будь-яку зовнішню систему (або системи) відповідно до системних вимог.

  5. приймальне тестування може проводитись кінцевим користувачем, замовником, або клієнтом для перевірки, чи може продукт бути прийнятий до використання.

  6. альфа-тестування — це симульоване або реальне операційне тестування потенційними користувачами/замовником або командою тестувальників на боці розробника.

  7. бета-тестування йде після альфа-тестування. Версії програмного забезпечення, відомі як бета-версії, надаються у користування обмеженій кількості людей поза компанією для того, щоб упевнитись, що програма не містить великої кількості помилок.

Тестування чорної і білої скриньки. У термінології професіоналів тестування (програмного й деякого апаратного забезпечення), фрази "тестування білого ящика" і "тестування чорного ящика" відносяться до того, чи має розробник тестів доступ до вихідного коду ПЗ, що тестується, або ж тестування виконується через інтерфейс користувача або прикладний програмний інтерфейс, наданий модулем, що тестується. При тестуванні білого ящика (англ. white-box testing, також говорять - прозорого ящика), розробник тесту має доступ до вихідного коду й може писати код, що пов'язаний з бібліотеками ПЗ, що тестується. Це типово для юніт-тестування (англ. unit testing), при якому тестуються тільки окремі частини системи. Воно забезпечує те, що компоненти конструкції - працездатні й стійкі до певного ступеня.

При тестуванні чорного ящика (англ. black-box testing), тестер має доступ до ПЗ тільки через ті ж інтерфейси, що й замовник або користувач, або через зовнішні інтерфейси, що дозволяють іншому комп'ютеру або іншому процесу підключитися до системи для тестування. Наприклад, модуль, що тестується, може віртуально натискати клавіші або кнопки миші в програмі, що тестується, за допомогою механізму взаємодії процесів, із упевненістю в тім, що ці події викликають той же відгук, що й реальні натискання клавіш і кнопок миші.

Якщо "альфа-" і "бета-тестування" відносяться до стадій до випуску продукту (а також, неявно, до обсягу співтовариства, що тестує, й обмеженням на методи тестування), тестування "білого ящика" і "чорного ящика" має відношення до способів, якими тестер досягає мети.

Бета-тестування в цілому обмежено технікою чорного ящика (хоча постійна частина тестерів звичайно продовжує тестування білого ящика паралельно бета-тестуванню). Таким чином, термін "бета-тестування" може вказувати на стан програми (ближче до випуску чим "альфа"), або може вказувати на деяку групу тестерів і процес, що виконується цією групою. Отже, тестер може продовжувати роботу з тестування білого ящика, хоча ПЗ вже "у беті" (стадія), але в цьому випадку він не є частиною "бета-тестування" (групи/процесу).

Тестування методів, пов'язаних з операціями з базами даних. Методи, які мають SQL код - необхідно тестувати з реальним підключенням до бази даних. Метод, в якому є SQL-код повинен бути по можливості як можна більш коротким, для того, щоб тест був коротким і швидким. В ідеалі тест на клас, який містить SQL-код містить тільки 1 тестовий метод.

Не можна писати код, який поєднує доступ до бази даних і логіку з обробки даних з бази даних. Це робить тести громіздкими, складними і повільними. Тому бізнес-логіку і доступ до бази даних потрібно розділяти на окремі класи і тестувати окремо. Код, який використовує класи абстракції від бази даних, і який не містить SQL-коду, можна при тестуванні ізолювати від бази даних.

Для тестування підключення до бази даних необхідна окрема база даних, відмінна від робочої.

Використання транзакцій не підходить, тому що нам потрібно буде чітко контролювати вміст для отримання очікуваних відповідей, тому доводиться використовувати ще одну тестову базу даних. Вона повинна містити ті ж таблиці, що будуть використовуватися в продукційної базі даних за умови, що у вас є комплект приймальних тестів або тільки ті таблиці, які необхідні для тестування. Ще одним варіантів може бути єдина база даних, але для тестів передбачається використання таблиць із спеціальними префіксами, наприклад '_test_some_table'. Перевагою такого підходу є те, що тести в такому випадку можна проводити на хостингу. У нас такий підхід не прижився. Справа в тому, що деякі методи містять SQL-код, в яких назви таблиць вказані явно, плюс класи DBAL також часто явно містять імена таблиць. Таблиці тестової бази даних не повинні бути порожніми перед початком і після виконання тесту. Всі дані, які необхідні в тому чи іншому тестової випадку повинні будуть записані в таблиці в setUp () і вилучені в tearDown () методах. Так як в php немає окремої фази компіляції, а php4 немає обробки виняткових ситуацій, тобто скрипт може закінчити свою роботу в будь-який момент, у нас склалася практика викликати метод для очищення тестової бази даних також і в setUp () тесту. Метод для очищення бази даних можна, правда, включати тільки в перший тестовий метод замість setUp (). Ось приклад типового тесту на код, пов'язаний з роботою в базою даних:

<?php

class DBTableTest extends UnitTestCase

{

var $db = null;

function setUp()

{

$toolkit =& Limb :: toolkit();

$this->db =& new SimpleDB($toolkit->getDbConnection);  

$this->_cleanUp();

}

function tearDown()

{

$this->_cleanUp();

}

 

function _cleanUp()

{

$this->db->delete('test1');

}

}

?>

Питання чи робити окремі тестові методи повністю незалежними один від одного є дуже важливим. Деякі розробники воліють додавати дані до бази тільки один раз, а видаляти в самому кінці. Це нібито позитивно позначається на швидкості виконання тестів. Наш досвід підказує, що в разі розподілу бізнес логіки і робота з базою даних в тестованому коді проведені грамотно, тести, не ізольовані від бази даних, мають досить невеликі розміри і містять мінімальну кількість окремих методів. До того ж ми намагаємося виділяти тести DAO-класів в окремі групи, які можна не виконувати кожен раз. Тому ми вважаємо за краще повністю ізолювати тестові методи один від одного.

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

Поділ бізнес-логіки й отримання даних. Розглянемо якийсь клас FeaturedOrdersDAO, який вибирає тільки ті замовлення, які задовольняють деякому критерію. Причому цей критерій чомусь не може бути застосований на етапі вибірки з бази даних, так як є елементом складної бізнес-логіки. Сам критерій залежить ще від будь-яких зовнішніх параметрів, наприклад, пори року. Для отримання об'єктів класу Order використовується OrderMapper:

<?php

class FeaturedOrdersDAO

{

function & fetch()

{

$toolkit =& Limb :: toolkit();

$db =& new SimpleDB($toolkit->getDbConnection());

$sql = 'SELECT * FROM orders';

$stmt =& $db->createStatement($sql);

$orders_rs =& stmt->getRecordSet();

$order_mapper = new OrderMapper();

$result = array();

for($orders_rs->rewind(); $orders_rs->valid(); $orders_rs->next())

{

$order_record =& orders_rs->current();

$order =& $order_mapper->map($order_record);

if($this->_passCriteria($order))

$result[] =& $order_record;

}

return new PagedArrayDataset($result);

function _passCriteria($order){ //some complex logic }

}

?>

Уявімо, яким буде виглядати тест на даний клас. Необхідно буде занести кілька записів у базу даних, перевірити, як працює метод _passCriteria () для різних замовлень, при цьому OrderMapper повинен працювати як годинник. Тестування бізнес-логіки, як правило, пов'язано з різними умовами (пам'ятаємо про пору року), тому тестових методів може бути багато. Схематично, тест буде виглядати так:

<?php

class FeaturedOrdersDAOTest extends UnitTestCase

{

function setUp()

{

$this->_cleanUp();

$this->_loadOrders();

function tearDown()

{

$this->_cleanUp();

}

function testManyDifferentOrderForWinter(){}

function testManyDifferentOrderForSummer(){}

function testManyDifferentOrderForSpring(){}

}

?>

Його явні недоліки - він повільний, так як кожен тестовий метод вимагає запису замовлень в базу даних. Кожен тестовий метод містить перевірку різних типів замовлень, що робить тест великим і малозрозумілим. Отже тест показує, що класу просто необхідний рефакторінг.

Виносимо SQL-код в окремий клас. Насамперед ми інкапсулюємо роботу з DB в окремий клас OrdersDAO, щоб цю частину коду можна було б використовувати повторно. Введемо клас OrdersDAO і будемо отримувати об'єкти цього класу через Registry:

<?php

class FeaturedOrdersDAO

{

function & fetch()

{

$toolkit =& Limb :: toolkit();

$orders_dao =& $toolkit->createDAO('OrdersDAO');

$orders_rs =& $orders_dao->fetch();

$order_mapper = new OrderMapper();

$result = array();

for($orders_rs->rewind(); $orders_rs->valid(); $orders_rs->next())

{

$order_record =& orders_rs->current();

$order =& $order_mapper->map($order_record);

if($this->_passCriteria($order))

$result[] =& $order;

}

return new PagedArrayDataset($result);

}

}

?>

Тепер вибірка замовлень инкапсульований у клас OrdersDAO (тіло класу ми показувати не будемо). OrdersDAO можна тестувати окремо. Так як ми легко можемо змінювати поведінку об'єкта $ toolkit, щоб він вирощує мок на OrdersDAO це дозволить ізолювати тест на FeaturedOrderDAO від бази даних:

<?php

class FeaturedOrdersDAOTest extends UnitTestCase

{

function setUp()

{

$mock_orders_dao = new MockDAO($this);

$mock_orders_dao->expectOnce('fetch');

$mock_orders_dao->setReturnReference('fetch', $orders_rs);

$mock_toolkit = MockToolkit($this);

$mock_toolkit->setReturnReference('OrdersDAO', $this->_mock_orders_dao);

Limb :: registerToolkit($mock_toolkit);

}

function tearDown()

{

$this->mock_orders_dao->tally();

Limb :: restoreToolkit($mock_toolkit)

function testManyDifferentOrderForWinter(){}

function testManyDifferentOrderForSummer(){}

function testManyDifferentOrderForSpring(){}

}

?>

Тепер тест буде набагато швидше. Однак він все також громіздкий і складний.. Виносимо бізнес логіку у окремий клас. Наступним кроком може стати винос методу _passCriteria () в окремий клас, наприклад, FeaturedOrdersCriteria, у якої буде метод pass (& $ order). По суті ми застосовуємо тут патерн Specification, в короткій формі:

<?php

class FeaturedOrderCriteria

{

function pass(&$order){ // some complex logic that return true or false}

}

?>

Цей клас набагато легше тестувати в поодинці, більше того, тепер цей елемент бізнес-логіки можна легко використовувати повторно з іншими подібними класами.

<?php

class FeaturedOrderCriteriaTest extends UnitTestCase

{

function testOrder1ForWinter(){}

function testOrder2ForWinter(){}

function testOrder3ForWinter(){}

...

}

?>

Кінцевий стан. FeaturedOrdersDAO тепер можна модифікувати таким чином, щоб він міг працювати з будь-яким критерієм:

<?php

class FeaturedOrdersDAO

{

var $criteria;

  function FeaturedOrdersDAO(&$criteria)

{

$this->criteria =& $criteria;

}

function & fetch()

{

$toolkit =& Limb :: toolkit();

$orders_dao =& $toolkit->createDAO('OrdersDAO');

$orders_rs =& $orders_dao->fetch();

$order_mapper = new OrderMapper();

$result = array();

for($orders_rs->rewind(); $orders_rs->valid(); $orders_rs->next())

{

$order_record =& orders_rs->current();

$order =& $order_mapper->map($order_record);

if($this->criteria->pass($order))

$result[] =& $order;

}

return new PagedArrayDataset($result);

}

...

}

?>

Тепер в тест можна буде також ізолювати від деталей бізнес-логіки, що дозволить зробити його дуже коротким. По суті ми тепер має клас-диспетчер, який лише делегує обов'язки інших класах:

<?php

class FeaturedOrdersDAOTest extends UnitTestCase

{

function setUp()

{

$mock_orders_dao = new MockDAO($this);

$mock_toolkit = MockToolkit($this);

$mock_toolkit->setReturnReference('OrdersDAO', $this->_mock_orders_dao);

Limb :: registerToolkit($mock_toolkit);

}

function tearDown()

{

$this->mock_orders_dao->tally();

Limb :: restoreToolkit($mock_toolkit)

}

function testFetch()

{

$orders_rs = new .... (2 orders in rs initially);

$expected_rs = new .... (1 order in rs - only 1 should pass the criteria);

$mock_orders_dao->expectOnce('fetch');

$mock_orders_dao->setReturnReference('fetch', $orders_rs);

$dao = new FeaturedOrdersDAO($mock_criteria);

$this->assertEqual($dao->fetch(), $expected_rs);

$mock_criteria->tally();

}

?>

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]