PHP

Пишем тест кода PHP с помощью Testify.php

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

Очень опасное заблуждение!

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

Введение в Testify.php

Testify - маленькая библиотека PHP для проверки кода, которая распространяется под лицензией GPL. Ее проектировали для простого и элегантного использования. Testify использует преимущества синтаксиса анонимных функций PHP 5.3 для облегчения определения тестов. Тесты логически группируются в кейсы. Наборы кейсов тестов объединяются в комплекты. Рассмотрим, как маленькая библиотека может облегчить жизнь разработчика.

Проверка кода

Что мы будем проверять?

В коротком примере мы создадим класс PHP для конвертации времени из формата, который обычно используется в базе данных (“15-10-2011 22:32″) в строку с относительным временем (“1 month ago”). Подобные классы нуждаются в тщательной объемной проверке, которая послужит отличной иллюстрацией использования Testify.


Класс RelativeTime

Существует несколько способов для вычисления относительного времени. Можно создать набор выражений if/else, который будет работать. Но такой код очень трудно поддерживать и для него очень высока вероятность появления ошибок. Мы будем использовать более элегантный способ. В его основе лежит факт, что каждый период времени состоит из четко определенного количества меньших периодов. То есть, один день состоит из 24 часов, один час - из 60 минут, а каждая минута содержит 60 секунд.

Здесь представлен скелет нашего класса:
 	class RelativeTime{
 	     
 	    // Названия периодов
 	    private $names = array('second','minute','hour','day','week','month','year');
 	     
 	    // Как много периодов содержится один в другом
 	    private $divisions = array(1,60,60,24,7,4.34,12);
 	     
 	    private $time = NULL;
 	     
 	    public function __construct($timestr = NULL){
 	        // Можно передавать время при конструировании объекта
 	    }
 	     
 	    public function getOffsetFrom($timestr = NULL){
 	 
 	        // Данный метод вычисляет строку относительного времени
 	 
 	    }
 	     
 	    public function __toString(){
 	         
 	        // Подходящий метод магии PHP
 	         
 	    }
 	     
 	    private function timestampFromString($time){
 	         
 	        // Данный метод преобразует строку в корректное время в секундах
 	    }
 	}

А теперь перейдем к тестированию кода.

Использование Testify

Существует метод ведения процесса разработки кода, который называется "Разработка, ведомая тестированием" (TDD). В соответствии с данным процессом нужно сначала писать тест для получения лучшего результата. Рассмотрим, как использовать библиотеку в данном процессе.

Первый шаг - загрузить Testify. Затем извлечь из архива папку testify и включить файл в код PHP вместе с классом RelativeTime:
 	include 'RelativeTime.class.php';
 	include 'testify/testify.class.php';

Теперь создадим новый комплект тестов. Первым делом создаем экземпляр класса Testify.
	$tf = new Testify("Testing RelativeTime with Testify");

Строка, которая передается в конструктор, используется в заголовке наверху страницы. Теперь можно перейти к определению кейсов тестов (логических групп тестов). Первый тест является более общим и ориентирован на весь функционал класса.
 	$tf->test("Общая проверка класса", function($tf){
 	    $relative = new RelativeTime();
 	     
 	    // Еще не задано время.
 	    $tf->assert($relative == "Время не задано!");
 	     
 	    try{
 	        // Должно генерировать исключение
 	        $relative->getOffsetFrom();
 	         
 	        // Если вы оказались здесь, то тест провален:
 	        $tf->fail();
 	    }
 	    catch (Exception $e){
 	        $tf->assert($e->getMessage() == "Время не задано!");
 	    }
 	     
 	    try{
 	        // Должно работать
 	        $relative->getOffsetFrom("22-10-2011");
 	        $tf->pass();
 	    }
 	    catch (Exception $e){
 	        $tf->fail();
 	    }
 	     
 	});

Класс RelativeTime генерирует исключение, если мы запрашиваем строку времени, но еще не передали значение (либо в конструкторе, либо вызовом метода getOffsetFrom()). Но метод __toString не может генерировать исключение. Он только возвращает строку с текстом.

Здесь демонстрируется несколько методов для поддержки тестов – assert(), pass() и fail().

Теперь добавим второй кейс тестов, который проверяет правильность возвращаемых строк.
 	$tf->test("Проверка функционала относительного времени", function($tf){
 	     
 	    // Проверяем класс с установленным временем
 	    $relative = new RelativeTime(timestamp(time()-130));
 	     
 	    // Проверяем метод getOffsetFrom
 	    $tf->assert($relative->getOffsetFrom() == "2 minutes ago");
 	 
 	    // Проверяем конверсию __toString
 	    $tf->assert($relative == "2 minutes ago");
 	     
 	    // Быстрая проверка
 	    $tf->assert( new RelativeTime( time())           == "just now");
	    $tf->assert( new RelativeTime( time()-11)        == "11 seconds ago");
 	    $tf->assert( new RelativeTime( time()-59)        == "59 seconds ago");
 	    $tf->assert( new RelativeTime( time()-60)        == "1 minute ago");
 	    $tf->assert( new RelativeTime( time()-89)        == "1 minute ago");
 	    $tf->assert( new RelativeTime( time()-90)        == "2 minutes ago");
 	    $tf->assert( new RelativeTime( time()-30*60)     == "30 minutes ago");
 	    $tf->assert( new RelativeTime( time()-59*60)     == "59 minutes ago");
 	    $tf->assert( new RelativeTime( time()-60*60)     == "1 hour ago");
 	    $tf->assert( new RelativeTime( time()-90*60)     == "2 hours ago");
 	    $tf->assert( new RelativeTime( time()-86400)         == "1 day ago");
 	    $tf->assert( new RelativeTime( time()-3*86400)       == "3 days ago");
 	    $tf->assert( new RelativeTime( time()-9*86400)       == "1 week ago");
 	    $tf->assert( new RelativeTime( time()-29*86400)      == "4 weeks ago");
 	    $tf->assert( new RelativeTime( time()-31*86400)      == "1 month ago");
 	    $tf->assert( new RelativeTime( time()-100*86400) == "3 months ago");
 	    $tf->assert( new RelativeTime( time()-350*86400) == "12 months ago");
 	    $tf->assert( new RelativeTime( time()-365*86400) == "1 year ago");
 	    $tf->assert( new RelativeTime( time()-20*365*86400)  == "20 years ago");
 	     
 	});
 	 
	// Вспомогательная функция для конструирования строки времени
 	function timestamp($unixTime){
 	    return date('r',$unixTime);
 	}

Отлично! Теперь вызываем метод run() и получаем отчет об ошибке теста.
 	$tf->run();

Отчет об ошибке теста

Теперь напишем действительный код методов класса:
 	class RelativeTime{
 	     
 	    // Названия периодов
 	    private $names = array('second','minute','hour','day','week','month','year');
 	     
 	    // Как много периодов содержится один в другом
 	    private $divisions = array(1,60,60,24,7,4.34,12);
 	     
 	    private $time = NULL;
 	     
 	    public function __construct($timestr = NULL){
 	        // Можно передавать время при конструировании объекта
 	        $this->timestampFromString($timestr);
 	    }
 	     
 	    public function getOffsetFrom($timestr = NULL){
 	 
 	        // Данный метод вычисляет строку относительного времени
 	 
 	        $this->timestampFromString($timestr);
 	         
 	        if(is_null($this->time)){
 	            throw new Exception("Timestamp not specified!");
 	        }
 	         
 	        $time = $this->time;
 	        $name = "";
 	         
 	        if($time < 10){
 	            return "just now";
 	        }
 	         
 	        for($i=0; $i<count($this->divisions); $i++){
 	            if($time < $this->divisions[$i]) break;
 	             
 	            $time = $time/$this->divisions[$i];
 	            $name = $this->names[$i];
 	        }
 	         
 	        $time = round($time);
 	         
 	        if($time != 1){
 	            $name.= 's';
 	        }
 	     
 	        return "$time $name ago";
 	    }
 	     
 	    public function __toString(){
 	         
 	        // __toString не может генерировать исключение
 	         
 	        try{
 	            return $this->getOffsetFrom();
 	        }
 	        catch(Exception $e){
 	            return $e->getMessage();
 	        }
 	    }
 	     
 	    private function timestampFromString($time){
 	         
 	        if(is_numeric($time)){
 	            // Формат времени unix (количество секунд, прошедших с 1 января 1970)
 	            $this->time = time() - $time;
 	        }
 	        else if(is_string($time)){
 	            // Время строкой
 	            $this->time = time() - strtotime($time);
 	        }
 	    }
 	}

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

Заключение

Тестирование кода является важным процессом в разработке приложения. Оно помогает сохранить время и нервы разработчика, особенно при внесении изменений в проект.
Добавлено: 19 Августа 2013 08:53:49 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...