За последние 24 часа нас посетили 17609 программистов и 1602 робота. Сейчас ищут 1112 программистов ...

PHP Tests Helper - помощник при проведении тестов

Тема в разделе "Решения, алгоритмы", создана пользователем TheShock, 9 июл 2009.

  1. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Посмотрел я тему кружка любопытных извращенцев, вспомнил свою практику и подумал, что часто приходится делать такую рутинную работу, как генерация тестовых данных - строк и массивов. И если генераторы строк уже давным давно написаны, то генераторы массивов пишутся прям во время теста, что, во-первых, загромождает код теста, во-вторых часто пишется достаточно лениво и потому массивы получаются похожи на array("блабла", "йцукен", "11111"), в-третьих это совершенно неправильно.

    Уверен, что генерация массивов - это один из немногих примеров, которые впадлу завернуть в какую-то обертку, потому что "только для тестов".

    Предлагаю вам библиотеку - PHP Tests Helper. Её цель - помочь разработчику в проведении теста части своего продукта, или либы. Лицензия - LGPL.

    [ Скачать с Google Code ] [ Краткий ман ]

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

    Ну и для затравки примеры:
    PHP:
    1. <?php
    2. $th = new TestsHelper;
    3. $arr = $th->createArray(array(
    4.     'length' => array(2, 5), // Массивы длинной от 2 от 5 элементов
    5.     'depth'  => array(0, 1), // Вложеностью до 1
    6.     'keys'   => array(
    7.         'length' => array(5, 10), // Длина ключей - 5-10 символов
    8.         'chars'  => StringGenerator::CYR | StringGenerator::NUM // Кириллица и цифры
    9.     ),
    10.     'values' => 'v*' // В качестве значений элементов подставить 'v' + порядковый номер элемента
    11. ));
    12.  
    Код (Text):
    1. Array
    2. (
    3.     [ійЧЩкип5] => v0
    4.     [жЛ9ШТ] => Array
    5.         (
    6.             [кфІ6РчжЩР] => v0
    7.             [Ё2М1м] => v1
    8.             [смшЗА6] => v2
    9.             [Ю4МьРЫкЩлО] => v3
    10.         )
    11.  
    12.     [ЩЩМ2Уб] => v2
    13.     [фв7є0] => v3
    14.     [єПяцГз7Т4] => Array
    15.         (
    16.             [Р1ыхлТЯ] => v0
    17.             [ЬЮфЦЩСЦ] => v1
    18.         )
    19. )
    PHP:
    1. <?php
    2. $th->createQuery(array(
    3.         'table'   => 'messages', // В таблицу messages
    4.         'fields'  => array (
    5.                 'ID'     => null, // ID у нас autoincrement
    6.                 'Text'   => array (
    7.                         'length' => array (8, 64), // Длина текста - от 8 до 64 символов
    8.                         'chars'  => StringGenerator::LAT | StringGenerator::SPACES // Использовать латинские символы и пробелы
    9.                 )
    10.         ),
    11.         'length'  => 100, // 100 записей
    12.         'execute' => true // Выполнять запросы по ходу их создания
    13. ));
    Подробнее - в инструкции и исходном коде

    Принимаю предложения и пожелания

    И да, предлагаю рекомендовать к использованию в кружке любопытных извращенцев
     
  2. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    На счет предложений:
    Добавь метод создания sql кода для заполнения таблиц.
     
  3. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Выложил обновленную версию. Код в кодохранилище. Более подробная информация - в вики на гуглокоде. И легкий пример:

    PHP:
    1. <?
    2. $th->createQuery(array(
    3.         'table'   => 'messages', // В таблицу messages
    4.         'fields'  => array (
    5.                 'ID'     => null,
    6.                 'Text'   => array (
    7.                         'length' => array (8, 64),
    8.                         'chars'  => StringGenerator::LAT | StringGenerator::SPACES
    9.                 )
    10.         ),
    11.         'length'  => 100,
    12.         'execute' => true
    13. ));
    И еще расширил StringGenerator
     
  4. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    И опять про sql. Было бы полезно иметь возможность генерить связанные данные для таблиц. Для проверки join.
     
  5. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    дааамс.. это будет потяжелее, конечно. я подумаю над алгоритмом... если у кого есть предложения по интерфейсу и реализации - прошу.
     
  6. kostyl

    kostyl Guest

    а вот по поводу проверки скорости работы sql я опять отсылюсь
     
  7. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    TheShock
    Одно замечание по стилистике написания кода:
    В интерфейс не следует вносить объявление конструктора и/или деструктора.
    Интерфейс описывает логическую абстракцию методов объекта, конструктор же является служебным и, по сути, не является равноправным членом класса.
    У объекта, подобно человеку, есть две стадии: рождение и смерть.
    Для этих стадий мы можем определить какие-то действия, но, в любом случае, без этих действий невозможна суть существования, т.е определим мы конструктор или нет, существовать он всё равно будет.

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

    Это только поправка.
     
  8. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Apple, спасибо.. Но ведь это не просто объявление конструктора, а указание, что каждый ITestsHelperGenerator должен обязательно иметь в конструкторе аргумент $config. Или все-равно не нужно?
     
  9. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    Интерфейс лишь создаёт регламент методов, которые обязаны быть реализованы в классе.
    Конструктор так и так существует, опишем мы его или нет.
    Поэтому необходимости описывать ни сам конструктор, ни его аргументы — нет.

    Во всяком случае на одном иностранном форуме — DevNetworks — мы этот вопрос обсуждали.
    Большинство стояло на мнении, что конструктор не должен быть объявлен в интерфейсе, сколько бы аргументов он не принимал.

    Лично я придерживаюсь этого, поскольку человек волен распоряжаться конструктором всё-таки так, как он хочет.
    А интерфейс задаёт регламент обязательных методов класса, без которых объекта быть не может.
     
  10. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    По идее описание конструктора в интерфейсе возможно. В тех случаях, когда мне необходимо создавать объект по конкретным данным или вообще без данных. Например, есть интерфейс "Поверхность" и тогда любой объект, реализующий этот интерфейс, должен иметь конструктор с параметрами длина и ширина. Или объект реализующий интерфейс "ПервоеЧтоПопалосьПодРукуДляБроска" должен иметь конструктор без аргументов.

    PS. Могу описать алгоритм для join'ов на словах. :)
     
  11. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    расскажи свое мнение и, как ты считаешь, должен выглядеть интерфейс? Например связывание двух таблиц по полю. Как узнать какие АйДи были вставлены? Делать по одному запросу и получать mysql_last_id(), или предположить, что в таблице нету еще данных и принудительно указывать ID начиная с 1(или с числа, которое пользовать ввел в конфиге при конструировании)? Как реагировать, если пользователь связал две таблицы? Например
    Код (Text):
    1. $table1->link($table2, 'table2ID');
    2. $table2->link($table1, 'table1ID');
     
  12. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    Я не перестану повторять, что интерфейс задаёт методы, которые обязаны быть реализованы в объекте.
    Приведу пример даже: есть интерфейс, описывающий некую модель, возмем ручку.
    Какие функции должна реализовывать ручка?
    Ею можно писать, для этого внутри должен содержаться ещё один объект - стержень, который находится в оболочке и является low-level объектом по отношению к колпачку.
    Так вот интерфейс описания ручки мог вы выглядеть следующим образом:

    PHP:
    1. <?
    2.  
    3. interface IPenCommon
    4. {
    5.     public function Write($text);
    6.     public function Flush();
    7. }
    8.  
    9. interface IBar implements IPenCommon
    10. {
    11.     protected function FillInk($level);
    12.     protected function SetInkColor($color);
    13.     public function FlushInk($level);
    14. }
    15.  
    16. interface IPen implements IBar
    17. {
    18.     public function SetCat();
    19.     public function DropCap();
    20. }
    21.  
    22. ?>
    В данном примере мы может видеть методы FillInk и SetInkColor, которые определяет производитель ручки.
    Интерфейс описывает то, без чего не может существовать ручки, производитель может описать следующий класс реализации:

    PHP:
    1. <?
    2.  
    3. class GelPen implements IPen
    4. {
    5.     public function __construct($color, $level)
    6.     {
    7.         $this->SetInkColor($color);
    8.         $this->FillInk($level);
    9.     }
    10.  
    11.     /* Тут идут методы интерфейсов ... */
    12. }
    13.  
    14. ?>
    В конструктор передаются аргументы для производства ручки.
    Но что если производитель захочет сделать так:

    PHP:
    1. <?
    2.  
    3. class GelPen implements IPen
    4. {
    5.     public static $ob = null;
    6.  
    7.     public static function CreatePen($color, $level)
    8.     {
    9.           // Обращаемся к статическому свойству и пишем туда другой класс
    10.     }
    11.  
    12.     /* Тут идут методы интерфейсов ... */
    13. }
    14.  
    15. ?>
    Т.е описывая конструктор в интерфейсе вы завязываете руки тому, кто реализовывает этот интерфейс.
    Интерфейс обязан описывать только те методы (и свойства), которые класс должен иметь для полноценной работы.
    Интерфейс — это не каша из методов, которые из-за прихоти туда запихнули, а очень кратко-структурированное объединение методов, описывающих свойства объекта, которые он будет иметь при себе.
    При этом не имеет значения, будет у класса конструктор, или как-то по-другому будут передаваться данные, интерфейсу срать на это, поскольку его цель в описании СТРУКТУРЫ ОБЪЕКТА, а конструктор таковым не является.
     
  13. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    В отличии от многих других языков, в С++ нет чёткого понятия интерфейса или соотв. типа.
    Для этого мы создаёт абстрактный класс (кстати, что мне не нравится в С++, так это реализация класса возможна за его пределами), поэтому он может содержать как свойства, так и методы.
    Хотя в С++ — это общий шаблон проектирования, и интерфейсом называется лишь условно.
    Поскольку происходит разделение только на логическом уровне там, тут я описал некоторые члены интерфейса как СВОЙСТВА, а не просто методы.
     
  14. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Совершенно согласен.
    Конструктор в интерфейсе это редкое исключение из правила.

    его цель описание поведения объекта.

    А вообще я не сразу переключился на PHP. Т.к. в PHP для объекта возможен только один конструктор, то действительно:
     
  15. kostyl

    kostyl Guest

    Apple
    Да, явное объявление конструктора в интерфейсе - это завязывание рук. Вот в том случае его и объявляют, чтобы их завязать. Например, у меня есть объекты, которые получают данные только конструктором( почему так это отдельный вопрос), вот я в интерфейсе иго и объявил.
     
  16. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Работа с одной таблицей.

    Далее идут мысли по реализации, которые не проверены практикой. Думаю при столкновении с реальным миром многое может измениться. :)

    Работа с одной таблицей.

    Работа с одной таблицей имеет смысл, только если уже созданы таблицы с которыми мы хотим связать новую.
    1. Вызов.
    1.1 Вариант 1: добавляем поля в массив.
    PHP:
    1. <?php
    2. $th->createQuery(array(
    3.     'table' => 'messages',
    4.     'fields' => array (
    5.         'ID' => null,
    6.         'Text' => array (
    7.             'length' => array (8, 64),
    8.             'chars'  => StringGenerator::LAT | StringGenerator::SPACES
    9.             ),
    10.         'newTableId' => array (
    11.             'chars' => StringGenerator::NUM,
    12.             'range' => array (0,30000),
    13.             ),
    14.         'oldTableId' => array (
    15.             'chars' => StringGenerator::NUM,
    16.             'range' => array (0,30000),
    17.             ),
    18.         ),
    19.     'length'  => 100,
    20.     'execute' => true,
    21.     'links'=> array(
    22.         'param' => array(
    23.             'DNS' => '...',
    24.             'DB' => 'MyDB',
    25.             'takeFromDb' => true
    26.             ),
    27.         0 => array(
    28.             0 => array(
    29.                 'thisField' => 'newTabId',
    30.                 'thatField' => 'id',
    31.                 'type' => 'eq',
    32.                 'linkTable' => 'newTable',
    33.                 'column' => ...
    34.                 'numLinks' => array(0, 2)
    35.                 ),
    36.             1 => array(
    37.                 'thisField' => 'oldTabId',
    38.                 'thatField' => 'id',
    39.                 'type' => 'eq',
    40.                 'linkTable' => 'oldTable',
    41.                 'column' => ...
    42.                 'numLinks' => array(0, 2)
    43.                 ),
    44.             'length' => 5
    45.         1 => array(
    46.             'thisField' => 'oldTabId',
    47.             'thatField' => 'id',
    48.             'type' => 'eq',
    49.             'linkTable' => 'oldTable',
    50.             'column' => ...,
    51.             'numLinks' => array(0, 2)
    52.             'length' => 10
    53.             )
    54.         )
    55.     )
    56. );
    57. ?>
    Как видно добавляется элемент links. Он содержит в себе массивы описывающие с какой таблицей связывать, по каким полям, каким образом, массив параметров (возможно имеет смысл перенести все это на объектную основу или выделить нумерованные элементы массива в отдельный массив).
    link[0] – описывает запись связанную с двумя таблицами и говорит что надо создать пять таких записей.
    link[1] – описывает запись связанную с одной таблицей.

    Параметры:
    DNS - строка подключения к БД
    DB - база данных из которой брать информацию
    takeFromDb - нужно ли брать данные из БД.
    При takeFromDb=false DNS и DB необязательны и наоборот.
    Я не знаю как ты организушь доступ к БД, возможно DNS и DB передавать не нужно.
    type определяет как связывать поля. Надо определить как его задавать. Мне приходят в голову следующие варианты:
    а) eq - равно, lt - меньше, gt - больше, le - меньше или равно, ge - больше или равно.
    б) =, <, >, <=, >=
    в) тупо константами 1, 2. 3 и т.д.

    linkTable – необходимо при takeFromDb=true, чтобы знать из какой таблицы брать данные.
    column – необходимо при takeFromDb=false. Данные из 'thatField'. Скорее всего опять массив.
    length – количесто связанных записей. Надо решить либо это количество добавляется к общему числу записей в таблице (fullLength= length+links[0].length+ links[1].length +…), или уже включено в него (length – links[0].length – links[1].length – … = число несвязанных записей). Думаю первый вариант проще в реализации, но второй более удобен для использования.
    numLinks – количество связей с одним полем в конечной таблице (от и до). Если array(0), то случайно. Если первая цифра 0, то параметр length обязателен. В противном случае параметр length игнорируется.
    1.2 Вариант 2: добавляем аргументы в функцию.
    Массив links становится вторым аргументом функции.
    2.Проверки
    а) Проверяем соответствие длин (если решим включать длины друг в друга).
    б) Проверяем соответствие полей для связи полям описанным в массиве fields.
    в) Проверяем типы полей для связи на соответствие друг другу.
    г) Проверяем подключение к БД. Наличие нужной таблицы и нужных полей в таблице если takeFromDb=true
    д) Проверить не конфликтность numLinks для записей, которые одновременно связаны более чем с одной таблицей.

    PS. Приду домой продолжу.