Page 1

Влияние тестирования на дизайн кода Сергей Юдин aka SYFisher syfisher@mail.ru http://limb-project.com

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Основные темы Роль изоляции в тестировании. Запахи тестов и их причины. Отказ от использования статических вызовов. Влияние тестирования на использование наследования и делегирования Рефакторинг классов, работающих с базой данных. Прочие моменты, касающиеся влияния тестирования на рабочий код.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Запахи тестов Медленный тест. Хрупкий тест. Сложный тест или тест-лапша. Неадекватный тест. Чрезмерное доверие мокам. Недостаточное доверие мокам. и т.д.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Почему тесты необходимо изолировать Делегируемые классы еще не готовы, а рабочий код уже необходимо тестировать. Внешние ресурсы, от которых зависит рабочий код, не всегда доступны. Использование реальных классов значительно снижает скорость тестов. Использование реальных классов раздувает SetUp(), что делает тесты сложными и хрупкими.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Последствия использования моков Изоляция рабочего кода в тестах моками от всех классов, которые связаны с внешними ресурсами. Передача делегируемых классов в конструкторе, использование фабричных методов и Registry. Отказ от явных вызовов конструкторов и статических методов. Распад системы на несколько независимых подсистем с четкими интерфейсами.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Использование статических вызовов Делает изоляцию невозможной. Нарушает принцип инверсии зависимостей. Тесты начинают «пахнуть»: дублирование фикстуры текущего теста и теста на сам статический вызов, увеличение времени выполнения тестов. зависимость теста от деталей статического метода. увеличение сложности теста ввиду неочевидности содержимого фикстуры Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Пример См. классы и схемы тестов в раздаточном материале. Класс B нарушает принцип инверсии зависимостей. B зависит от A. Тест на класс B будет зависеть от деталей класса A (запахи «хрупкий тест» и «сложный тест»). Класс B будет закрыт для расширения по причине явного указания имени класса A (нарушение принципа открытия-закрытия).

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Методы решения проблемы Сокрытие вызова статического метода в тестируемом классе и использование частичного мока. Не решает проблем нарушения принципов, лишь облегчает тестирование. Создание интерфейса для того, чтобы класс A реализовывал этот интерфейс. Передача класса, реализующего интерфейс в конструктор класса B. Использование моков при тестировании класса B.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Отказываемся от одиночек. Переходим на Registry Одиночки (Singletons) имеют все недостатки статических методов. Использование одиночек оправдано только тогда, когда они делегируют все свои обязанности объектам, которые можно подменить. Паттерн Registry – лучший способ получения часто используемых объектов с точки зрения тестов.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Одиночка – тот же статический вызов class A { function method1() { $b =& B :: instance(); $b->doSomething(); } } class B { function & instance(){...} function doSomething(){// method implementation} } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Использование одиночек для делегирования

class A { function method1() { $b =& B :: instance(); $b->doSomething(); } }

class B // implements Server { var $server; function & instance(){...} function doSomething(){$this->server->doSomething(); } function setServer(&$server){$this->server =& $server;} } class C // implements Server { function doSomething() { // method implementation } } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Registry – единственная правильная одиночка Применяется для получения других часто используемых классов. Реализуется через одиночку. Имеет стековую структуру, что позволяет легко делать изоляцию тестов. Имеет по возможности четкий интерфейс. См. пример вариации Registry: LimbToolkit(). Позволяет отказаться от одиночек, которые являются таковыми из-за частого использования другими классами или же из-за экономии на инициализации. Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Новый взгляд на наследование после тестирования Больше не используем TemplateMethod для расширения неабстрактных родительских классов, так как это связано с дублированием при тестировании. При необходимости протестировать закрытый метод – выделяем класс, тестируем его и делегируем ему из базового класса. Предпочитаем наследованию делегирование. Паттерны Декоратор, Композиция и Посетитель – теперь наши самые лучшие друзья.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Неправильное использование паттерна TemplateMethod

class A{ function perform(){ $this->_doSomethingA();

if ($this->_someConditionA()) $this->_extentionMethod1(); else $this->_extentionMethod2(); } function _extentionMethod1(){} function _extentionMethod2(){} function _someConditionA(){// Some useful functionality} function _doSomethingA(){// Some useful functionality} } class B extends A { function _extentionMethod1(){ // Some useful functionality } function _extentionMethod2(){ // Some useful functionality } } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Тест на класс A Mock :: generatePartial('A', 'ATestVersion', array('_extentionMethod1', '_extentionMethod2')); class ClassATest extends TestCase{ function testSomeCondition1() { $this->_provideConditionResult1(); $a = new ATestVersion($this); $a->expectOnce('_extentionMethod1'); $a->perform(); $a->tally(); } function testSomeCondition2(){ $this->_provideConditionResult2(); $a = new ATestVersion($this); $a->expectOnce('_extentionMethod2'); $a->perform(); $a->tally(); } } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Тест на класс B - дублирование class ClassBTest extends TestCase{ function testOneForExtention1 () { $this->_provideConditionResult1(); $b = new B(); … some expectations and asserts } function testTwoForExtention1(){ $this->_provideConditionResult1(); $b = new B(); … some expectations and asserts } function testOneForExtention2(){ $this->_provideConditionResult2(); $b = new B(); … some expectations and asserts } } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Выход – декомпозиция и делегирование

class A{function perform(){}} class ConditionA{ var $cons1; var $cons2;

function ConditionA(&$cons1, &$cons2){ $this->cons1 =& $cons1; $this->cons2 =& $cons2; } function perform(){ if ($this->_someConditionA()) $this->cons1->perform(); else $this->cons2->perform(); } function _someConditionA(){} } class Method1 {function perform(){}} class Method2 {function perform(){}}

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


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

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Тестируем классы, работающие с Persistence Layer Разносим по разным классам бизнес-логику и получение данных – делаем тесты простыми. Упрощаем классы, содержащие SQL, насколько это возможно – делаем тесты быстрыми. Тестируем классы, содержащие SQL-код, на реальной базе данных – делаем тесты адекватными. Изолируем тесты на классы бизнес-логики от базы данных при помощи моков – ускоряем тесты и избавляемся от дублирования.

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Пример.

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 } }

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Схема теста class FeaturedOrdersDAOTest extends TestCase{ function setUp() { $this->_cleanUp(); $this->_loadOrders(); } function tearDown(){$this->_cleanUp(); } function testManyDifferentOrderForWinter(){} function testManyDifferentOrderForSummer(){} function testManyDifferentOrderForSpring(){} }

Огромный размер, сложный и медленный тест

Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Выносим SQL код в отдельный класс 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); } ... } Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Измененная схема теста class FeaturedOrdersDAOTest extends TestCase{ function setUp(){ $orders_rs = … fill orders records set $mock_orders_dao = new MockDAO($this); $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(){ Limb :: restoreToolkit($mock_toolkit) } … те же тесты }

Значительно быстрее, но все также слишком сложный и большой. Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Выносим бизнес-логику в отдельный класс

Класс бизнес-логики class FeaturedOrderCriteria{ function pass(&$order){ // some complex logic that return true or false} } Тест на класс бизнес-логики class FeaturedOrderCriteriaTest extends TestCase{ function testOrder1ForWinter(){ $order = new Order(…); $criteria = new FeaturedOrderCriteria(); $this->assertTrue($criteria->pass($order)) } function testOrder2ForWinter(){} function testOrder3ForWinter(){} ... }

Быстрый, четкий тест Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев


Конечное состояние 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 12-13мая 2005, Киев


Конечный вариант теста class FeaturedOrdersDAOTest extends TestCase{ function testFetch(){ $orders_rs = new .... (2 orders in rs initially); $expected_rs = new .... (1 order in rs - only 1 should pass the criteria); $this->mock_orders_dao->expectOnce('fetch'); $this->mock_orders_dao->setReturnReference('fetch', $orders_rs); $mock_criteria = new MockOrderCriteria($this); $mock_criteria->expectCallCount('pass', 2); $mock_criteria->setReturnValueAt(0, 'pass', true); $mock_criteria->setReturnValueAt(1, 'pass', false); $dao = new FeaturedOrdersDAO($mock_criteria); $this->assertEqual($dao->fetch(), $expected_rs); $mock_criteria->tally(); }}

Небольшой, быстрый, полностью изолированный тест. Современные технологии эффективной разработки веб-приложений с использованием PHP 12-13мая 2005, Киев

Слайд 1  

s Использование реальных классов раздувает SetUp(), что делает тесты сложными и хрупкими. s Передача делегируемых классов в конструкторе, и...

Read more
Read more
Similar to
Popular now
Just for you