advanced php testing in action

92
2011.12@果子

Upload: jace-ju

Post on 17-May-2015

2.433 views

Category:

Technology


0 download

DESCRIPTION

WebDev Party #1

TRANSCRIPT

Page 1: Advanced php testing in action

2011.12@果子

Page 2: Advanced php testing in action

AdvancedPHP Testing

in action

Page 3: Advanced php testing in action

關於我

Jace Ju / jaceju / 大澤木小鐵

Plurk: http://www.plurk.com/jaceju

Page 4: Advanced php testing in action

傳統的 PHPUnit 用法

Page 5: Advanced php testing in action

視窗切換法

Page 6: Advanced php testing in action

指令監看法

watch -n 15 -d \find tests/ -mmin -1 -iname '"*.php"' -exec \'phpunit -c tests/phpunit.xml {} \;'

註: Mac 的 watch 指令有問題

每 15 秒就找出一分鐘之內有異動的 php 檔案來測試

Page 7: Advanced php testing in action

找個好用的 IDE

Page 9: Advanced php testing in action

在 NetBeans 設定PHPUnit

Page 10: Advanced php testing in action
Page 11: Advanced php testing in action

建立 NetBeans 專案

Page 12: Advanced php testing in action
Page 13: Advanced php testing in action

NetBeans 測試快捷鍵

Page 14: Advanced php testing in action

動作 WindowsLinux Mac

Test Project Alt + F6 Ctrl + F6

Test Target File Ctrl + F6 Cmd + F6

Run Test Case F6 F6

註: Mac 上的 Ctrl + F6 這個鍵有時似乎無法正常動作

Page 15: Advanced php testing in action

傳統 PHP 程式的問題所在

Page 16: Advanced php testing in action

➡ 無法達到分工的目的➡ 出現錯誤時難以確認問題點➡ 無法單獨測試每個環節➡ 看起來就是醜

Page 17: Advanced php testing in action

Why Framework?

Why MVC ?

Page 18: Advanced php testing in action

一致性

Page 19: Advanced php testing in action

分工

Page 20: Advanced php testing in action

邏輯可以重複使用

Page 21: Advanced php testing in action

Which Framework?

Page 22: Advanced php testing in action

A simple mvc framework

https://github.com/jaceju/simple-mvc-framework

Why not ZF?

Page 23: Advanced php testing in action

library!"" Controller.php!"" Response.php!"" Request.php!"" View.php#!"" Request#   $"" Http.php!"" Response#   $"" Http.php!"" Test#   $"" ControllerTestCase.php$"" View    $"" Html.php

Core Library

Page 24: Advanced php testing in action

範例

Page 25: Advanced php testing in action

Todo

Page 26: Advanced php testing in action

project!"" application#   !"" controllers#   #   $"" IndexController.php#   !"" models#   #   $"" Todo.php#   $"" views#   $"" index.phtml$"" tests !"" application #   !"" controllers #   #   $"" IndexControllerTest.php #   !"" models #   #   $"" TodoTest.php #   $"" views #   $"" InterfaceTest.php !"" bootstrap.php $"" phpunit.xml

Application & Tests

Page 27: Advanced php testing in action

Model應用邏輯

Page 28: Advanced php testing in action

Model 怎麼測?

Page 29: Advanced php testing in action

➡ 準備一個測試用的乾淨資料庫➡ 儘可能測試 Model 的應用邏輯

Page 30: Advanced php testing in action

準備 Schema

Page 31: Advanced php testing in action

CREATE DATABASE `testing` CHARSET=utf8;USE `testing`;

CREATE TABLE `todo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自動編號', `task` varchar(100) NOT NULL COMMENT '工作', `done` enum('y','n') NOT NULL DEFAULT 'n' COMMENT '是否完成', PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Todo';

doc/schema.sql

Page 32: Advanced php testing in action

資料庫環境相關參數以 PDO 為例

http://www.php.net/manual/en/pdo.construct.php

Page 33: Advanced php testing in action

<phpunit colors="true" bootstrap="./bootstrap.php"> <testsuite name="Application Test Suite"> <directory>./application</directory> </testsuite> <testsuite name="Library Test Suite"> <directory>./library</directory> </testsuite> <php> <var name="DB_DSN" value="mysql:dbname=testing;host=127.0.0.1" /> <var name="DB_USER" value="username" /> <var name="DB_PASSWD" value="password" /> </php></phpunit>

tests/phpunit.xml

Page 34: Advanced php testing in action

➡ fetchAll()

➡ add($task)

➡ done($id)

Model: Todo

Page 35: Advanced php testing in action

先從測試開始

Page 36: Advanced php testing in action

<?php

class TodoTest extends PHPUnit_Framework_TestCase{ private $_pdo = null;

private $_todo = null;

public function setUp() { $this->_pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']); $this->_pdo ->query('TRUNCATE TABLE todo');

Todo::setDb($this->_pdo); $this->_todo = new Todo(); }

public function tearDown() { $this->_pdo ->query('TRUNCATE TABLE todo'); }

tests/application/models/TodoTest.php

<?php

class Todo{    protected static $_pdo = null;        public static function setDb(PDO $pdo)    {        self::$_pdo = $pdo;    }

application/models/Todo.php

Page 37: Advanced php testing in action

public function testAdd() { $this->assertEquals( 1, $this->_todo->add('Task 1') );

$this->assertEquals( 2, $this->_todo->add('Task 2') ); }

    public function add($task)    {        $query = 'INSERT INTO todo (task) '  . 'VALUES (?)';

        self::$_pdo->prepare($query) ->execute(array($task));

        return self::$_pdo->lastInsertId();    }    

Page 38: Advanced php testing in action

public function testFetchAll() { $this->_todo->add('Task 1', 'm'); $this->_todo->add('Task 2', 'f');

$result = $this->_todo->fetchAll();

$this->assertEquals( 2, count($result) );

$this->assertContains( 'Task 1', $result[0] );

$this->assertContains( 'Task 2', $result[1] ); }

    public function fetchAll()    {        $query = 'SELECT * FROM todo';        $stmt  = self::$_pdo ->query($query);

        return $stmt ->fetchAll(PDO::FETCH_ASSOC);    }

Page 39: Advanced php testing in action

public function testDone() { $this->_todo->add('Task 1'); $this->_todo->add('Task 2');

$this->assertEquals( 1, $this->_todo->done(1) );

$this->assertEquals( 1, $this->_todo->done(2) );

$this->assertEquals( 0, $this->_todo->done(3) ); }}

    public function done($id)    {        $query = 'UPDATE todo '  . 'SET done = \'y\''  . 'WHERE id = ?';        $stmt  = self::$_pdo->prepare($query);

        $stmt->execute(array($id));

        return $stmt->rowCount();    }}

Page 40: Advanced php testing in action

寫完一個測試後就寫相對應的程式碼

Page 41: Advanced php testing in action

寫好就用快速鍵測試

Page 42: Advanced php testing in action
Page 43: Advanced php testing in action

就是這麼簡單

Page 44: Advanced php testing in action

資料...真的存進去了?

Page 45: Advanced php testing in action

View資料呈現

Page 46: Advanced php testing in action

View 究竟是什麼?

Page 47: Advanced php testing in action

➡ 樣版引擎➡ 輸出 HTML / XML / JSON

Page 48: Advanced php testing in action

無法知道瀏覽器的行為

Page 49: Advanced php testing in action

如何測試介面?

Page 50: Advanced php testing in action

Selenium IDE

http://seleniumhq.org/projects/ide/

Page 51: Advanced php testing in action

➡ 錄製使用者的操作行為➡ 針對瀏覽器來修正腳本➡ 重新測試腳本➡ 轉存為 PHPUnit 測試腳本

Page 52: Advanced php testing in action
Page 53: Advanced php testing in action

<?php

class InterfaceTest extends PHPUnit_Extensions_SeleniumTestCase{

protected function setUp() { $this->setBrowser("*chrome"); $this->setBrowserUrl("http://test.dev/"); }

public function testMyTestCase() { $this->open("/advanced_php_testing/mvc/"); $this->type("id=new-todo", "Task 1"); $this->keyPress("id=new-todo", "13"); $this->waitForPageToLoad("30000"); $this->assertEquals("Task 1", $this->getText("//ul[@id='todo-list']/div[1]/div/div")); $this->type("id=new-todo", "Task 2"); $this->keyPress("id=new-todo", "13"); $this->waitForPageToLoad("30000"); $this->assertEquals("Task 2", $this->getText("//ul[@id='todo-list']/div[2]/div/div")); }}

tests/application/views/InterfaceTest.php

Page 54: Advanced php testing in action

資料存取的問題

Page 55: Advanced php testing in action

<?php

class InterfaceTest extends PHPUnit_Extensions_SeleniumTestCase{

protected function setUp() { $this->_pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']); $this->_pdo->query('TRUNCATE TABLE todo');

Todo::setDb($this->_pdo); $this->_todo = new Todo();

$this->setBrowser("*chrome"); $this->setBrowserUrl("http://test.dev/"); }

tests/application/views/InterfaceTest.php

Page 56: Advanced php testing in action

PHPUnit Selenium如何執行?

Page 57: Advanced php testing in action

Selenium Server

http://seleniumhq.org/projects/remote-control/

Page 58: Advanced php testing in action
Page 59: Advanced php testing in action

➡ Run Selenium Server

➡ Run Testing in NetBeans

Page 60: Advanced php testing in action
Page 61: Advanced php testing in action

介面測試的時機

Page 62: Advanced php testing in action

Controller流程控制

Page 63: Advanced php testing in action

單純測試流程的難題

Page 64: Advanced php testing in action

DatabaseAccess

Web Service

Session

HTTP Header CookieBrowser

Request

Web ServerResponse

XmlHTTPRequest

HTML JSON

FileUpload

Page 65: Advanced php testing in action

PHPUnit 沒有提供流程測試的機制

Page 66: Advanced php testing in action

自己來比較快

Page 67: Advanced php testing in action

以 HTTP Header 為例

Page 68: Advanced php testing in action

Request

Page 69: Advanced php testing in action

<?php

class Request{ protected $_headers = array( 'REQUEST_METHOD' => 'GET', );

public function setHeader($name, $value) { $this->_headers[$name] = $value; }

public function isPost() { return ('POST' === $this->_headers['REQUEST_METHOD']); }

public function isAjax() { return ('XMLHttpRequest' === $this->_headers['X_REQUESTED_WITH']); }

library/Request.php

Page 70: Advanced php testing in action

<?php

class Request_Http extends Request{ public function isPost() { return ('POST' === $_SERVER['REQUEST_METHOD']); }

public function isAjax() { return ('XMLHttpRequest' === $_SERVER['X_REQUESTED_WITH']); }}

library/Request/Http.php

Page 71: Advanced php testing in action

Response

Page 72: Advanced php testing in action

<?php

class Response{ protected $_headers = array( 'Content-Type' => 'text/html; charset=utf-8', );

public function setHeader($name, $content) { $this->_headers[$name] = $content; }

public function getHeader($name) { return isset($this->_headers[$name]) ? $this->_headers[$name] : null; }

protected function sendHeaders() { // do nothing }

library/Response.php

Page 73: Advanced php testing in action

Dependency Injection

Page 74: Advanced php testing in action

$controller = new IndexController(new Todo());$controller->setRequest(new Request_Http()) ->setResponse(new Response_Http()) ->sendResponse(true) ->dispatch();

實際執行

$controller = new IndexController(new Todo());$controller->setRequest(new Request()) ->setResponse(new Response()) ->dispatch();

測試

Page 75: Advanced php testing in action

將流程當做 Test Case

Page 76: Advanced php testing in action

<?php

class Test_ControllerTestCase extends PHPUnit_Framework_TestCase{ protected $_controller = null;

protected $_request = null;

protected $_response = null;

public function setUp() { $this->_controller->setRequest($this->_request) ->setResponse($this->_response); }

public function dispatch($url) { $this->_parseUrl($url); $this->_controller->dispatch(); return $this; }

protected function _parseUrl($url) { $urlInfo = parse_url($url); if (isset($urlInfo['query'])) { parse_str($urlInfo['query'], $_GET); } }

library/Test/ControllerTestCase.php

Page 77: Advanced php testing in action

➡ assertAction($action)

➡ assertResponseCode($code)

➡ assertRedirectTo($url)

Page 78: Advanced php testing in action

<?php

class IndexControllerTest extends Test_ControllerTestCase{ public function setUp() { $todo = new Todo(); $this->_request = new Request(); $this->_response = new Response(); $this->_controller = new IndexController($todo); parent::setUp(); }

public function tearDown() { $this->_request->reset(); $this->_response->reset(); }

public function testHome() { $this->dispatch('/'); $this->assertAction('index') ->assertResponseCode(200); }

tests/application/tests/IndexControllerTest.php

Page 79: Advanced php testing in action

隔離資料來源

Page 80: Advanced php testing in action

Mock & Stub讓同事沒有藉口

Page 81: Advanced php testing in action

Phake

https://github.com/mlively/Phake

Page 82: Advanced php testing in action

<?php

class IndexControllerTest extends Test_ControllerTestCase{ public function setUp() { $todo = $this->_setUpTodo(); $this->_controller = new IndexController($todo); // ... }

protected function _setUpTodo() { $todo = Phake::mock('Todo'); Phake::when($todo)->fetchAll()->thenReturn(array( array( 'id' => 1, 'task' => 'Task 1', 'done' => 'n', ), )); return $todo; }

tests/application/tests/IndexControllerTest.php

Page 83: Advanced php testing in action

驗證輸出結果

Page 84: Advanced php testing in action

➡ assertQuery($selector)

➡ assertQueryContain($selector, $text)

Page 85: Advanced php testing in action

phpQuery

http://code.google.com/p/phpquery/

Page 86: Advanced php testing in action

<?php

class IndexControllerTest extends Test_ControllerTestCase{ // ...

public function testHome() { $this->dispatch('/'); $this->assertAction('index') ->assertResponseCode(200) ->assertQuery('#todo-list'); }

public function testAdd() { $this->_request->setMethod('POST'); $_POST['task'] = 'Task 1'; $this->dispatch('/?act=add') ->assertAction('add') ->assertRedirectTo('./') ->assertResponseCode(200) ->assertQueryContain( '#todo-list>.todo>.display>.todo-text', 'Task 1' ); }

tests/application/tests/IndexControllerTest.php

Page 87: Advanced php testing in action

總結

Page 88: Advanced php testing in action

➡ Model測試資料應用邏輯

➡ View測試介面在瀏覽器上的運作

➡ Controller將干擾隔離以便測試流程

Page 89: Advanced php testing in action

其他參考資源

Page 90: Advanced php testing in action

➡ Zend_Test http://framework.zend.com/manual/en/zend.test.html

➡ CakePHP Testinghttp://book.cakephp.org/2.0/en/development/testing.html

➡ Planet PHPUnit http://planet.phpunit.de/

➡ PHPUnit Manualhttp://www.phpunit.de/manual/3.6/en/

Page 91: Advanced php testing in action

謝謝大家

Page 92: Advanced php testing in action

Q & A