0

Magento: испольузем phpUnit для тестирования

Posted мая 4, 2010 in Magento and tagged , by SpiRi7

Сегодня я хотел бы затронуть тему очень важную для каждого разработчика. Автоматизированное тестирование разработанного приложения. Использование юнит-тестирование позволяет автоматически распознавать ошибки в разработанных и отлаженных частях исходного кода, при внесении новых правок, изменении архитектуры приложения.
Подробнее о юнит-тестировании можно почитать на викапедии, а мы сегодня поговорим о применимости phpUnit к тестированию модулей написанных для Magento.

Источники информации

Предварительные условия

Для создания юнит-тестов Вам необходимо произвести установку PhpUnit и xDebug. Использование xDebug позволяет производить генерацию отчетов по тестированию, с отображением информации об покрытие исходного кода тестами. Я не буду подробно останаливаться на процессе установки. В интернете достаточно много примеров установки и настройки. Например вот этот краткий мануал

Настройка phpUnit для Magento

Мы подошли к основной части статьи. Для тестирования приложений мы будем использовать комбинацию phpunit.xml (конфигурационный файл), bootstrap.php (стартовый файл для инициализации) и ControllerTestCase.php (базовый класс для всех юнит тестов).
Для тестирования будем использовать слейдующую структуру каталогов.

Папка с тестами находится в корне установленной Magento, на одном уровне с папками app, skin, media и т.д.
Каталог unit – содержит файлы юнит-тестов, каталог report – сгенерированный отчет выполнения тестирования.

Переходим к содержимому файлов.

phpunit.xml – файл конфигурации
<phpunit bootstrap="bootstrap.php"
		colors="true"
		convertErrorsToExceptions="true"
		convertNoticesToExceptions="true"
		convertWarningsToExceptions="true"
		stopOnFailure="true"
                syntaxCheck="true">
    <testsuite name="nameofsuite">
        <directory>./</directory>
    </testsuite>
    <filter>
        <blacklist>
            <directory suffix=".php">../tests</directory>
            <directory suffix=".php">../lib</directory>
            <directory suffix=".php">../app/code/core</directory>
            <directory suffix=".php">../app/code/community</directory>
        </blacklist>
    </filter>    
    <logging>
        <log type="coverage-html" target="./report" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/>
    </logging>    
</phpunit>

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

bootstrap.php – стартовый файл
require_once 'PHPUnit/Framework.php';
require_once './../app/Mage.php';
Mage::app('default');

Достаточно небольшой файл производящий подключение классов для юнит-тестирования и инициализирующий magento приложение (подобный подход для иницализации так же используется в magento cron.php)

ControllerTestCase.php – базовый класс наследник для всех юнит тестов
abstract class ControllerTestCase extends PHPUnit_Framework_TestCase {
 
    public $connection;
 
    public function setUp() {
 
        parent::setUp();
 
        $class  = $this->_colorize(get_class($this), 'blue');
        $method = $this->_colorize($this->getName());
 
        echo "\n" . $class . ' -> ' . $method;
 
        $this->_time = microtime(1);
 
        $this->connection = Mage::getModel('Core/Mysql4_Config')->getReadConnection();
        $this->connection->beginTransaction();
 
    }
 
    public function tearDown() {
        $time = sprintf('%0.3f sec', microtime(1) - $this->_time);
 
        echo "\t" . $this->getCount() . '(Assertions)';
 
        echo $this->_colorize("\t" . $time, 'green');
 
        $this->connection->rollBack();
 
        parent::tearDown();
 
 
    }
 
 
 
    private function _colorize($text, $color = 'yellow')
    {
        switch ($color) {
            case 'red':
                $color = "1;31m";
                break;
            case 'green':
                $color = "1;32m";
                break;
            case 'blue':
                $color = "1;34m";
                break;
            default:
                $color = "1;33m";
                break;
        }
        return "\033[" . $color . $text . "\033[m";
    }
}

Содержимое файла частично было взято из юнит тестов CMF разработанной на базе Zend Framework – ZFCore.
Функция setUp выполняется перед каждым юнит-тестом. Она получает ссылку на коннект к базе данных и стартует транзацию (нет смысла каждый раз производить чистку magento приложения от тестовых данных). Производит запись времени начала теста и вывод дополнительной полезной информации.
Функция tearDown выполняется после каждого юнит-теста. Производит откат транзации и выводит тестовую информацию о времени выполнения теста и количестве выполненных утверждений.

unit/SimpleFileOfTest.php – базовый каркас для каждого созданного юнит теста
class SimpleFileOfTest extends ControllerTestCase {
 
    public function setUp() {
        parent::setUp();
    }
 
    public function testMagentoModel() {
      // Some test case
    }
 
    public function tearDown() {
        parent::tearDown();
    }
}

В данном файле мы производим вызов методов перед началом теста и после окончания, не забывая вызвать методы родительского класса. Метод testMagentoModel будет выполняться как тестовый кейс.

Такой структуру приложения будет достаточно для выполнения тестирования Ваших magento модулей.

Пример юнит-тестирования

Ранее я описывал возможность создания order в Magento программными средствами. Давайте создим юнит тест для этого функционала.

Создаем файл CreateOrderTest.php с слейдующим содержанием.

class CreateOrdersTest extends ControllerTestCase {
 
    protected $createOrderModel;
 
    public function setUp() {
        parent::setUp();
 
        $this->createOrderModel = new Spiri7_Test_Model_CreateOrders;
 
    }
 
    /**
     * Test order creation for some predeffined Values
     *
     * @dataProvider orderProvider
     */
    public function testCreateOrder($productInfo, $billingInfo, $shippingInfo) {
 
        $result = $this->createOrderModel->createOrder($productInfo, $billingInfo, $shippingInfo);
        $orderId = $result['orderId'];
 
        // Test for right Create Order. Order Id Must me not zero
        if ($orderId == 0) {
            $this->fail($result['message']);
        }
 
        // Checking create order for right product
        $order = Mage::getModel('sales/order')->load($orderId);
        $items = $order->getAllItems();
        $itemcount = count($items);
        // We adding only single product
        $this->assertTrue($itemcount == 1);
 
        $name=array();
        $unitPrice=array();
        $sku=array();
        $ids=array();
        $qty=array();
        foreach ($items as $itemId => $item)
        {
                $unitPrice[]=$item->getPrice();
                $ids[]=$item->getProductId();
                $qtyOrdered[]=$item->getQtyOrdered();//ToInvoice();
        }
 
 
        // Order created for right product
        $this->assertEquals($ids[0], $productInfo[0]['id']);
 
        // Price for product same as we set
        $this->assertEquals($unitPrice[0], $productInfo[0]['price']);
 
        // Qty of ordered product same as we adding
        $this->assertEquals($qtyOrdered[0], $productInfo[0]['qty']);
    }
 
    public function orderProvider() {
        // Billing address used for order
        $orderBilling = array();
        $orderBilling['firstname'] = "Alex";
        $orderBilling['lastname'] = "Filin";
        $orderBilling['company'] = '';
        $orderBilling['email'] = 'alexandr.otchenashev@gmail.com';
        $orderBilling['street'] = array();
        $orderBilling['street'][0] = "Address part Line1";
        $orderBilling['street'][1] = "Address part Line2";
        $orderBilling['city'] = "Dnepropetrovsk";
        $orderBilling['region_id'] = 1;
        $orderBilling['region'] = "";
        $orderBilling['postcode'] = "49100";
        $orderBilling['country_id'] = "US";
        $orderBilling['telephone'] = "0238559322";
        $orderBilling['fax'] = "0838559111";
 
        // Order Shipping information
        $orderShipping = array();
        $orderShipping['title'] = "UPS";
        $orderShipping['price'] = 100.00;
 
        $dataArray = array(
            "Simple Product" => array(
                array(          // Product Info
                      0 =>  array(
                        'id' => 47, // Simple Product ID without custom options
                        'qty'=> 3,
                        'price' => 123
                     )
                ),
                $orderBilling,
                $orderShipping,
            ),
            "Simple Product With Custom Options" => array(
                array(          // Product Info
                      0 =>  array(
                        'id' => 48, // Simple product ID with custom options
                        'qty'=> 3,
                        'price' => 123
                     )
                ),
                $orderBilling,
                $orderShipping,
            )
        );
 
 
        return $dataArray;
    }
 
    public function tearDown() {
        parent::tearDown();
    }

В setUp производим загрузку модели для создания ордеров. Метод orderProvider возвращает набор данных используемых для тестирования (я произвожу тестирования для simpleproduct с произвольными опциями и без). Метод testCreateOrder производит тестирование создания ордера для наших наборов данных. Обртите внимание что после вызова метода создания, мы проверяем на наличие в возврате id созданного заказа. Если id = 0, тест завершается неудачей и выводом ошибки. При успешном создании производится сверка на правильность добавленного продукта, правильной установки цены и количества товара.

Производим запуск тестов. Для этого в дирректории /tests/ выполняем команду phpunit, результат выполнения будет слейдующий:

Как видно из результата, второй тест завершился неудачей. Возвратилась ошибка.

В результате выполнения тестов был сгенерирован report с информацией о покрытии кода.

Выводы

Unit-тестирование выполяемое для magento не сильно отличается от тестирования любого другого приложение. Единственным недостатком следует признать сложность начальной конфигурации, и проблемотичность отладки итоговых тестов. Так же для меня остается не раскрытым вопрос о тестировании контроллеров.
Спасибо за внимание. Буду раз вашим комментариям и уточнением. А как производите тестирование magento модулей, Вы?

Похожие посты:

Leave a Reply