Post on 15-Jun-2015
TTD in ZF2
Пространства имен Замыкания ZF2 поставляемые в стандартной поставке тесты Поддержка нового phpunit 3.5
# lsBootstrap.php docs phpunit.xml resources runtests.sh TestConfiguration.php.dist Zend
# cat TestConfiguration.php.dist/** * Zend_Db_Adapter_Sqlsrv * Note: Make sure that you create the "test" database and set a * username and password * */define('TESTS_ZEND_DB_ADAPTER_SQLSRV_ENABLED', false);define('TESTS_ZEND_DB_ADAPTER_SQLSRV_HOSTNAME', '');define('TESTS_ZEND_DB_ADAPTER_SQLSRV_USERNAME', null);define('TESTS_ZEND_DB_ADAPTER_SQLSRV_PASSWORD', null);define('TESTS_ZEND_DB_ADAPTER_SQLSRV_DATABASE', 'test');
Стукрутра папок
- library - модульные- application - интеграционные+ functional - функциональные+ perfomance — функциональные
Тестировать не нужно
- Zend Framework- Doctrine / Propel- Jquery / Prototype / Dojo- Symphony DI / Crafty / DI- all extenal libraries ...
Тестировать нужно
- Абстрактные классы и интефейсы- Базовые компонеты- Магическую инициализацию- Любые не очевидные вещи
DRY – не повторяться1 тест – 1 проверкаХороший тест – 5 строкЕсли что-то протестировано, не тетсируем это снова
Крохобор (The Nitpicker) Уклонист (The Dodger)Гигант (Giant) Любитель Порядка (The Sequencer) Счётчик (The Enumerator)
Избранный (The One) Тормоз (The Slow Poke) ...
Основные принципы
Class => ClassName + Test.php Namespace => TestPhpDoc => bugNumber_files — для тестовых файлов_fixtures — для фикстурphpunit.xml@cover Class::method
Организация тестов
<phpunit colors="true" bootstrap="./bootstrap.php"> <testsuite name="Test/Library"> <directory>./library/</directory> </testsuite> <testsuite name="Test/Application"> <directory>./application/</directory> </testsuite> <testsuite name="Test/Functional"> <directory>./functional/</directory> </testsuite>
<logging> <log type="coverage-html" target="/tmp" charset="UTF-8" yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/> </logging></phpunit>
/test/tests/application/controllers# cat IndexControllerTest.php
namespace Test;class IndexController extends \Zend\Test\PHPUnit\ControllerTestCase{ public $bootstrap = '../../bootstrap.php'; /** * @group Bug123 * @cover IndexController::indexAction */ public function testIndexAction() { $this->dispatch('/'); $this->assertController("index"); $this->assertAction("index"); }}
Тестирование Controller
Тестирование Controller
class IndexController extends \Zend\Test\PHPUnit\ControllerTestCase public function testIndexAction() $this->dispatch('/'); $this->assertController("index"); $this->assertAction("index"); $this->assertQueryContentContains('#content', 'Hello Im here'); $this->assertQueryContentContains('div.content', 'Hello Im here'); $this->assertQueryContentContains('body .content', 'Hello Im here'); $this->assertQueryCount('#categories li', 3);
<?phpclass IndexController extends AbstractController
public function indexAction() $this->view->hello = 'Im here';
Тестирование Controller::init
<?php
abstract class AbstractController extends \Zend\Controller\Action{ public function init() { $this->_helper->contextSwitch() ->addActionContext('index', array('xml', 'json')) ->setAutoJsonSerialization(true) ->initContext();
$this->view->categories = new Application\Model\CategoryMapper(); }}
Тестирование Controller:init
class AbstractController extends \Zend\Test\PHPUnit\ControllerTestCase
public function testInitContext() $controller = new AbstractControllerStub($this->getRequest(), $this->getResponse()); $this->assertEquals( $controller->getHelper('ContextSwitch')->getActionContexts('index'), array('xml', 'json'));
public function testInitCategories() $controller = new AbstractControllerStub($this->getRequest(), $this->getResponse()); $this->assertType('Application\\Model\\CategoryMapper', $controller->view->categories);
Тестирование Form
<?phpnamespace Application\Form;class Registration extends \Zend\Form\Form public function init() $this->addElement('text', 'username'); $this->addElement('text', 'password'); $this->addElement('text', 'password_retype');
public function isValid($params) $result = parent::isValid($params); if ($this->getValue('password')!=$this->getValue('password_retype')) { return false; } return $result;
Тестирование Form
class Registration extends \PHPUnit_Framework_TestCase public function testValidate() $form = new \Application\Form\Registration; $this->assertTrue($form->isValid(array( 'username' => 'test', 'password' => '123', 'password_retype' => '123', ))); public function testValidateFail() $form = new \Application\Form\Registration; $this->assertFalse($form->isValid(array( 'username' => 'test', 'password' => '123', 'password_retype' => '1234', )));
Архитектура
→ getConnection→ getDataSet→ createDbTableDataSet→ createDbTable→ createDbRowset
Тестирование DbTable
$categories = new \Application\Model\DbTable\Categories($this->getAdapter()); $categories->insert(array('id'=>4, 'name'=>'test')); $this->assertDataSetsEqual( $this->createFlatXmlDataSet("_fixtures/categoriesInsert.xml"), $this->createDbTableDataSet(array($categories)) );
Тестирование DbTable
class CategoriesTest extends \Zend\Test\PHPUnit\DatabaseTestCase
public function getConnection() $application = new Application (_ENV, _PATH . '/configs/application.ini'); $application->bootstrap(); $resource = $application->getBootstrap()->getPluginResource('db'); return $this->createZendDbConnection($resource->getDbAdapter(), 'any');
public function getDataSet() return $this->createFlatXMLDataSet(__DIR__ . '/_fixtures/categories.xml');
public function testFecthAll() $categories = new \Application\Model\DbTable\Categories($this->getAdapter()); $rowset = $categories->fetchAllOrdered(); $this->assertEquals(count($rowset), 3);
Тестирование DbTable
<?xml version="1.0" encoding="UTF-8"?><dataset> <categories id="1" name="cat1" /> <categories id="3" name="cat3" /> <categories id="2" name="cat2" /></dataset>
<?xml version="1.0" encoding="UTF-8"?><dataset> <categories id="1" name="cat1" /> <categories id="3" name="cat3" /> <categories id="2" name="cat2" /> <categories id="4" name="test" /></dataset>
Тестирование Mapper
1. Использует сервис/сервисы2. Используется моделями3. Содержит код setTable/getTable/__construct/init которые идентичны и миргурируют из одного маппера в другой
+ есть еще внешние источники данных SOAP/XML-RPC/REST/RSS
Тестирование Mapper
<?phpnamespace Application\Model;
class XXXMapper{ protected $_table = null;
protected $_defaultServiceName = '';
public function __construct(DbTable $table=null)
public function getTable()
public function setTable(DbTable $table) …}
Тестирование Mapper
Выделить общий класс AbstractMapper Выделить интерфейс для сервисов Наследовать все мапперы от абстрактного Имплементировать интерфейс Service в DbTable
Тестирование Mapper
<?phpnamespace Application\Model;class AbstractMapper{ protected $_service = null; protected $_defaultServiceName = ''; public function __construct(Service $service=null) { if (is_null($service)) { $name = $this->_defaultServiceName; $this->setService(new $name); } else { $this->setService($this->_service = $service); } }
public function getService()
public function setService(Service $service)}
Тестирование Mapper
Создаем реализации Складываем в подпапку _files в текущей директории
__Construct / Abstract class / Interface
<?phpnamespace Test;
class AbstractMapperMockAutoload extends \Application\Model\AbstractMapper{ protected $_defaultServiceName = 'Test\ServiceMock';}
Тестирование Mapper
class AbstractMapperTest extends \PHPUnit_Framework_TestCase
public function testInit() $mock = new AbstractMapperMockAutoload(); $this->assertType('Test\\ServiceMock', $mock->getService());
public function testInitFail() try { $mock = new AbstractMapperMock(); } catch (\Exception $e) { return ; } $this->fail('An expected exception has not been raised.'); public function testSet() $mock = new AbstractMapperMockAutoload(); $mock->setService(new ServiceMock()); $this->assertType('Test\\ServiceMock', $mock->getService());
Тестирование Selenium
Selenium RC Selenium IDESelenium GRID
Тестирование Selenium
Тестирование Selenium
Native = left <div id=”left”> <input name='left'>Xpath = //div[@id='left'] <div id=”left”>Css = css=ul#recordlist li:nth-child(4)
css=input[name='continue'][type='button'] css=a[id^='id_prefix_'] css=a:contains('Log Out')
Тестирование Selenium
class Example extends PHPUnit_Extensions_SeleniumTestCase{ protected $captureScreenshotOnFailure = TRUE; protected $screenshotPath = '~/conf/public/screenshots'; protected $screenshotUrl = 'http://conf.local/screenshots';
protected function setUp() { $this->setBrowser("*chrome"); $this->setBrowserUrl("http://conf.local/"); }
public function testMyTestCase() { $this->open("index/index"); $this->assertEquals("index", $this->getText("content")); }}
Тестирование Selenium
public function testAjaxLoading() { $this->open("/index/index/"); $this->assertEquals("index", $this->getText("content")); $this->click("showIndexInAjax"); for ($second = 0; ; $second++) { if ($second >= 60) $this->fail("timeout"); try { if ("Im here" == $this->getText("content")) break; } catch (\Exception $e) {} sleep(1); } $this->assertEquals("Im here", $this->getText("content")); }
Тестирование Selenium
class IndexController extends \PHPUnit_Extensions_SeleniumTestCase{ public function testHighlight() { $this->open("/index/index/"); $this->mouseOver("//div[@id='left']/li"); $this->assertEquals("rgb(255, 0, 0)", $this->getEval(
"this.findEffectiveStyleProperty(this.page().findElement(\"//div[@id='left']/li\"),
'backgroundColor')")); }
Тестирование Selenium
Вопросы ?