PHPUnit 入門介紹

Preview:

DESCRIPTION

介紹 PHPUnit 的基本概念

Citation preview

PHPUnit 入門

讓 Web 程式更加可靠

JACE JU

哇寶資訊技術總監

軟體測試

軟體測試

單元測試、功能測試、整合測試、

系統測試、壓力測試、使用者測

試…註:google://軟體測試

單元測試是什麼?

單元測試是什麼?

單元:程式中類別的方法或是函式

單元測試是什麼?

單元:程式中類別的方法或是函式

測試:驗證程式執行後預期的結果

單元測試的優點

為重構提供了保障

為重構提供了保障

在我們新增或修改程式時,測試

能確保舊的功能不受影響

讓開發者思考設計的範疇

讓開發者思考設計的範疇

在測試驅動開發 (TDD) 模式下 ,

測試其實就是設計

自動測試

自動測試

不需人工來一項一項地測試,讓

測試工具一次幫我們完成

單元測試的缺點?

測試太花時間?

測試太花時間?

測試寫的少, Debug 到老~

利用測試來節省尋找 Bug 的時間

沒有程式?怎麼測試?

沒有程式?怎麼測試?

測試就是讓我們確認程式應該要

表現出來的行為,所以反過來從

結果去推演出程式會更容易

Test-Driven Development測試驅動開發

測試先行

測試先行

將客戶的需求都轉換成測試,通

過測試就是完成需求

讓程式開發的方向有所依據

讓程式開發的方向有所依據

測試先行會讓程式開發方向收歛

在一定的合理範圍中

PHP 上的單元測試工具

SimpleTest

http://www.simpletest.org/

最大特色: PHP 4/5 相容

PHPUnit

http://www.phpunit.de/

最大特色:完整利用 PHP5 特色

(例如 SPL, Reflection…)註:SPL: Standard PHP Library

安裝 PHPUnit

安裝 PHPUnit

•從 PEAR 安裝

安裝 PHPUnit

•從 PEAR 安裝

•手動安裝

範例

第一個 PHPUnit 測試案例

Person.php

<?php

class Person{

protected $_name = '';

public function __construct($name)

{

$this->_name = (string) $name;}

public function sayHelloTo($name){

return 'Hello, ' . $name . '. I am ' . $this->_name . '.';

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');$this->assertEquals(

);

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');$this->assertEquals(

'Hello, Neo. I am Jace.',

);

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');$this->assertEquals(

'Hello, Neo. I am Jace.',$person->sayHelloTo('Neo')

);

}}

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');$this->assertEquals(

'Hello, Neo. I am Jace.',$person->sayHelloTo('Neo')

);

}}

# phpunit PersonTest.php

PersonTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Person.php';

class PersonTest extends PHPUnit_Framework_TestCase{

public function testSayHelloTo(){

$person = new Person('Jace');$this->assertEquals(

'Hello, Neo. I am Jace.',$person->sayHelloTo('Neo')

);

}}

# phpunit PersonTest.php

PHPUnit 3.3.17 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

怎麼樣算是好測試

說明開發者要表達的意圖

說明開發者要表達的意圖

測試就像程式的說明文件,要能

清楚傳達出如何使用這支程式

分離程式,以便測試

分離程式,以便測試

切細程式的功能,將能夠單獨測

試的部份獨立出來

測試結果要跟預期一樣

測試結果要跟預期一樣

測試出預期結果是應該的,而且

不能只有一次正確,而且在修改

程式後都要是一直正確的 (除非功

能改變)

好測試要可被依賴

好測試要可被依賴

單元測試要保證程式的正確性,

那麼單元測試也一定要值得信任

PHPUnit 的幾大特色

Data Providers

Data Providers

不需要為不同的測試資料重複相

同的程式碼

範例

多筆資料同時測試

Add.php

<?php

class Add{

protected $_num1 = 0, $_num2 = 0;

public function setNum1($num)

{

$this->_num1 = (int) $num;}

public function setNum2($num){

$this->_num2 = (int) $num;

}

public function getResult()

{return $this->_num1 + $this->_num2;

}

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

public function testAdd( )

{

}

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

public function testAdd( )

{$add = new Add();

$add->setNum1($num1);$add->setNum2($num2);$this->assertEquals($expection, $add->getResult());

}

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

public function testAdd($num1, $num2, $expection){

$add = new Add();

$add->setNum1($num1);$add->setNum2($num2);$this->assertEquals($expection, $add->getResult());

}

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

/**

* @dataProvider provider*/

public function testAdd($num1, $num2, $expection){

$add = new Add();

$add->setNum1($num1);$add->setNum2($num2);$this->assertEquals($expection, $add->getResult());

}

}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

/**

* @dataProvider provider*/

public function testAdd($num1, $num2, $expection){

$add = new Add();

$add->setNum1($num1);$add->setNum2($num2);$this->assertEquals($expection, $add->getResult());

}

public function provider(){

}}

AddTest.php

<?php

require_once 'PHPUnit/Framework/TestCase.php';require_once 'Add.php';

class AddTest extends PHPUnit_Framework_TestCase{

/**

* @dataProvider provider*/

public function testAdd($num1, $num2, $expection){

$add = new Add();

$add->setNum1($num1);$add->setNum2($num2);$this->assertEquals($expection, $add->getResult());

}

public function provider(){

return array(array(0, 0, 0),array(0, 1, 1),

array(1, 0, 1),array(1, 1, 3)

); // 可以用迴圈或讀檔的方式去產生大量的測試資料}

}

# phpunit AddTest.php

# phpunit AddTest.phpPHPUnit 3.3.17 by Sebastian Bergmann.

...F

Time: 0 seconds

There was 1 failure:

1) testAdd(AddTest) with data set #3 (1, 1, 3)Failed asserting that <integer:2> matches expected value <integer:3>.

xxxxxxxx\AddTest.php:15

FAILURES!

Tests: 4, Assertions: 4, Failures: 1.

Expected Exception

Expected Exception

預期程式會出現語法不正確以外

的錯誤或異常

範例

預期會出現異常的測試

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{

public function testException(){

}

}

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

}

}

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

}

}

# phpunit ExceptionTest.php

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

}

}

# phpunit ExceptionTest.phpPHPUnit 3.3.17 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testException(ExceptionTest)Expected exception InvalidArgumentException

FAILURES!Tests: 1, Assertions: 1, Failures: 1.

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

}

}

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

throw new InvalidArgumentException('Test');// 實作上應用 try … catch 來完成

}

}

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

throw new InvalidArgumentException('Test');// 實作上應用 try … catch 來完成

}

}

# phpunit ExceptionTest.php

ExceptionTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase

{/*** @expectedException InvalidArgumentException*/

public function testException(){

throw new InvalidArgumentException('Test');// 實作上應用 try … catch 來完成

}

}

# phpunit ExceptionTest.phpPHPUnit 3.3.17 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

為什麼要測試錯誤?

輸入的資料千千百百種

輸入的資料千千百百種

因為不能預期使用者會按照正常

方法來使用程式,所以測試各種

狀況就是必要的動作

沒錯反而是錯的?

沒錯反而是錯的?

當程式明顯不能接受某些輸入值

時,程式應該出現異常,而我們

要能確保這件事的發生

Fixtures

Fixtures

為每個測試案例準備同一組測試

物件,以省去在每個測試方法中

建立測試物件的成本

範例

共用測試物件的測試案例

FixtureTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ArrayTest extends PHPUnit_Framework_TestCase

{protected $fixture;

}

FixtureTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ArrayTest extends PHPUnit_Framework_TestCase

{protected $fixture;

public function testNewArrayIsEmpty()

{$this->assertEquals(0, sizeof($this->fixture));

}

}

FixtureTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ArrayTest extends PHPUnit_Framework_TestCase

{protected $fixture;

public function testNewArrayIsEmpty()

{$this->assertEquals(0, sizeof($this->fixture));

}

public function testArrayContainsAnElement(){

$this->fixture[] = 'Element';$this->assertEquals(1, sizeof($this->fixture));

}

}

FixtureTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ArrayTest extends PHPUnit_Framework_TestCase

{protected $fixture;

protected function setUp(){

$this->fixture = array();

}

public function testNewArrayIsEmpty()

{$this->assertEquals(0, sizeof($this->fixture));

}

public function testArrayContainsAnElement(){

$this->fixture[] = 'Element';$this->assertEquals(1, sizeof($this->fixture));

}

}

FixtureTest.php

<?php

require_once 'PHPUnit/Framework.php';

class ArrayTest extends PHPUnit_Framework_TestCase

{protected $fixture;

protected function setUp(){

$this->fixture = array();

}

public function testNewArrayIsEmpty()

{$this->assertEquals(0, sizeof($this->fixture));

}

public function testArrayContainsAnElement(){

$this->fixture[] = 'Element';$this->assertEquals(1, sizeof($this->fixture));

}

protected function tearDown(){

unset($this->fixture);}

}

執行步驟:

執行步驟:

setUp()

執行步驟:

setUp()testNewArrayIsEmpty()

執行步驟:

setUp()testNewArrayIsEmpty()tearDown()

執行步驟:

setUp()testNewArrayIsEmpty()tearDown()

setUp()

執行步驟:

setUp()testNewArrayIsEmpty()tearDown()

setUp()testArrayContainsAnElement()

執行步驟:

setUp()testNewArrayIsEmpty()tearDown()

setUp()testArrayContainsAnElement()tearDown()

Organizing Tests

Organizing Tests

將同性質 (套件、專案) 的測試案

例組織起來一起測試,就不需要

一個一個案例測試了

範例 1

組織套件中多個的類別的測試

Package/Class1Test.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_Class1Test extends PHPUnit_Framework_TestCase

{public function testOne() {}

public function testTwo() {}}

Package/Class1Test.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_Class1Test extends PHPUnit_Framework_TestCase

{public function testOne() {}

public function testTwo() {}}

Package/Class2Test.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_Class2Test extends PHPUnit_Framework_TestCase

{public function testThree() {}

public function testFour() {}}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_AllTests

{

}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_AllTests

{

public static function suite(){

}}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';

class Package_AllTests

{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Package');

return $suite;

}}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/Class1Test.php';

class Package_AllTests

{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Package');$suite->addTestSuite('Package_Class1Test');

return $suite;

}}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/Class1Test.php';require_once 'Package/Class2Test.php';

class Package_AllTests

{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Package');$suite->addTestSuite('Package_Class1Test');$suite->addTestSuite('Package_Class2Test');return $suite;

}}

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/Class1Test.php';require_once 'Package/Class2Test.php';

class Package_AllTests

{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Package');$suite->addTestSuite('Package_Class1Test');$suite->addTestSuite('Package_Class2Test');return $suite;

}}

# phpunit Package/AllTests.php

Package/AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/Class1Test.php';require_once 'Package/Class2Test.php';

class Package_AllTests

{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Package');$suite->addTestSuite('Package_Class1Test');$suite->addTestSuite('Package_Class2Test');return $suite;

}}

# phpunit Package/AllTests.phpPHPUnit 3.3.17 by Sebastian Bergmann.

....

Time: 0 seconds

OK (4 tests, 0 assertions)

範例 2

組織專案中多個套件的測試

AllTests.php

<?php

require_once 'PHPUnit/Framework.php';

class AllTests{

public static function suite(){

}

}

AllTests.php

<?php

require_once 'PHPUnit/Framework.php';

class AllTests{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Project');

return $suite;}

}

AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/AllTests.php';

class AllTests{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Project');$suite->addTest(Package_AllTests::suite());return $suite;

}

}

AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/AllTests.php';

class AllTests{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Project');$suite->addTest(Package_AllTests::suite());return $suite;

}

}

# phpunit AllTests.php

AllTests.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Package/AllTests.php';

class AllTests{

public static function suite(){

$suite = new PHPUnit_Framework_TestSuite('Project');$suite->addTest(Package_AllTests::suite());return $suite;

}

}

# phpunit AllTests.phpPHPUnit 3.3.17 by Sebastian Bergmann.

....

Time: 0 seconds

OK (4 tests, 0 assertions)

範例 3

套件中使用 setUp 及 tearDown

MyTest.php

<?php

require_once 'PHPUnit/Framework.php';

class MyTest extends PHPUnit_Framework_TestCase

{protected function setUp(){

echo "\n", __METHOD__;}

public function testOne(){

echo "\n", __METHOD__;

}

public function testTwo()

{echo "\n", __METHOD__;

}

public function tearDown(){

echo "\n", __METHOD__;}

}

MySuite.php

<?php

require_once 'MyTest.php';

class MySuite extends PHPUnit_Framework_TestSuite{

public static function suite(){

return new self('MyTest');}

protected function setUp(){

echo "\n", __METHOD__;

}

protected function tearDown(){

echo "\n", __METHOD__;}

}

執行步驟:

MySuite::setUp

執行步驟:

MySuite::setUp

MyTest::setUp

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOne

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOneMyTest::tearDown

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOneMyTest::tearDown

MyTest::setUp

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOneMyTest::tearDown

MyTest::setUpMyTest::testTwo

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOneMyTest::tearDown

MyTest::setUpMyTest::testTwoMyTest::tearDown.

執行步驟:

MySuite::setUp

MyTest::setUpMyTest::testOneMyTest::tearDown

MyTest::setUpMyTest::testTwoMyTest::tearDown.

MySuite::tearDown

範例 4

在組織測試中共用測試物件

MySuite.php

<?php

require_once 'MyTest.php';

class MySuite extends PHPUnit_Framework_TestSuite

{public static function suite()

{

return new self('MyTest');}

protected function setUp(){

$this->sharedFixture = 'something';

}

protected function tearDown()

{$this->sharedFixture = null;

}

}

MyTest.php

<?php

require_once 'PHPUnit/Framework.php';

class MyTest extends PHPUnit_Framework_TestCase

{public function testOne()

{

$this->assertEquals('something', $this->sharedFixture);}

}

PHPUnit 基本結構

Composite 模式

Doubles

Doubles

對未產生或建立過程複雜的物件

進行模擬以區隔它們對測試的影

響,也就是 Mock Object註:PHPUnit 的 Mock 仿照自 jMock 1

範例 1

指定 Mock Object 的方法會輸出的值

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

}

}

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

$stub = $this->getMock('SomeClass'); // 建立 Mock Object

}

}

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

$stub = $this->getMock('SomeClass'); // 建立 Mock Object

$stub->expects($this->any()) // 不論呼叫幾次

}}

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

$stub = $this->getMock('SomeClass'); // 建立 Mock Object

$stub->expects($this->any()) // 不論呼叫幾次->method('doSomething') // 指定方法

}

}

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

$stub = $this->getMock('SomeClass'); // 建立 Mock Object

$stub->expects($this->any()) // 不論呼叫幾次->method('doSomething') // 指定方法->will($this->returnValue('foo')); // 指定回傳值

}

}

StubTest.php

<?php

require_once 'PHPUnit/Framework.php';

class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容{

public function doSomething()

{

}}

class StubTest extends PHPUnit_Framework_TestCase{

public function testStub()

{

$stub = $this->getMock('SomeClass'); // 建立 Mock Object

$stub->expects($this->any()) // 不論呼叫幾次->method('doSomething') // 指定方法->will($this->returnValue('foo')); // 指定回傳值

$this->assertEquals('foo', $stub->doSomething());}

}

範例 2

預測 Mock Object 的方法執行的次數

Subject.php

<?php

class Subject{

protected $observers = array();

public function attach(Observer $observer)

{

$this->observers[] = $observer;}

public function doSomething(){

$this->notify('something');

}

protected function notify($arg)

{foreach ($this->observers as $observer) {

$observer->update($arg);

}}

}

SubjectTest.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Subject.php';

class Observer // 雖然實際的 Observer 未完成,但我們還是要給它一個類別宣告{

public function update($arg) {}

}

class SubjectTest extends PHPUnit_Framework_TestCase

{public function testUpdateIsCalledOnce()

{

}

}

SubjectTest.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Subject.php';

class Observer // 雖然實際的 Observer 未完成,但我們還是要給它一個類別宣告{

public function update($arg) {}

}

class SubjectTest extends PHPUnit_Framework_TestCase

{public function testUpdateIsCalledOnce()

{

// 建立一個 Observer 的 Mock Object $observer = $this->getMock('Observer');

// 預期 Observer::update 方法應該只跑一次// 而傳入 update 方法的參數值為 something $observer->expects($this->once())

->method('update')->with($this->equalTo('something'));

}

}

SubjectTest.php

<?php

require_once 'PHPUnit/Framework.php';require_once 'Subject.php';

class Observer // 雖然實際的 Observer 未完成,但我們還是要給它一個類別宣告{

public function update($arg) {}

}

class SubjectTest extends PHPUnit_Framework_TestCase

{public function testUpdateIsCalledOnce()

{

// 建立一個 Observer 的 Mock Object $observer = $this->getMock('Observer');

// 預期 Observer::update 方法應該只跑一次// 而傳入 update 方法的參數值為 something $observer->expects($this->once())

->method('update')->with($this->equalTo('something'));

$subject = new Subject();$subject->attach($observer);

// 我們預測這裡會呼叫 Observer::update() 一次$subject->doSomething();

}

}

為什麼要 Mock Object?

隔離未實作的類別

隔離未實作的類別

在團隊開發時,伙伴負責的類別

尚未完成,我們必須隔離掉該部

份對目前單元的影響

無法使用線上開發

無法使用線上開發

像是線上刷卡等需要跟外部連線

的環境等

Code Coverage Analysis

Code Coverage Analysis

透過 Xdebug 來讓 PHPUnit 找出

測試所涵蓋到的程式碼

範例

# phpunit --coverage-html ./report SubjectTest.php

# phpunit --coverage-html ./report SubjectTest.phpPHPUnit 3.3.17 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

Generating code coverage report, this may take a moment.

# phpunit --coverage-html ./report SubjectTest.phpPHPUnit 3.3.17 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

Generating code coverage report, this may take a moment.

為什麼要知道測試涵蓋範圍?

找出尚未測試的部份

找出尚未測試的部份

程式碼測試的涵蓋範圍越廣,程

式就更加可靠

掌握開發進度

掌握開發進度

看著涵蓋範圍百分比的增加….

會很有成就感

PHP 需要單元測試嗎?

小案子就放它一馬

小案子就放它一馬

對 Web 開發而言,一般的 CRUD

並沒有特別需要做測試,像是留

言板、活動頁等註:CRUD: create, read, update, delete

測試有複雜演算的程式

測試有複雜演算的程式

資料樣本很多,而且容易出現不

同結果的狀況之下,就需要測試

測試有複雜演算的程式

資料樣本很多,而且容易出現不

同結果的狀況之下,就需要測試

不然就會像 Dell 一樣!

更多進階資訊

更多進階資訊

•PHPUnit 官方手冊

(http://www.phpunit.de/manual/3.3/en/index.html)

•jUnit

(http://www.junit.org)

•jMock

(http://www.jmock.org/)

謝謝大家