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->_...
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 ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
PersonTest.php

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

class PersonTest extends ...
怎麼樣算是好測試
說明開發者要表達的意圖
說明開發者要表達的意圖


測試就像程式的說明文件,要能
清楚傳達出如何使用這支程式
分離程式,以便測試
分離程式,以便測試


切細程式的功能,將能夠單獨測
試的部份獨立出來
測試結果要跟預期一樣
測試結果要跟預期一樣


測試出預期結果是應該的,而且
不能只有一次正確,而且在修改
程式後都要是一直正確的 (除非功
能改變)
好測試要可被依賴
好測試要可被依賴


單元測試要保證程式的正確性,
那麼單元測試也一定要值得信任
PHPUnit 的幾大特色
Data Providers
Data Providers


不需要為不同的測試資料重複相
同的程式碼
範例

多筆資料同時測試
Add.php

<?php
class Add
{
    protected $_num1 = 0, $_num2 = 0;

    public function setNum1($num)
    {
        $this->_...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
AddTest.php

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

class AddTest extends PHPUnit_F...
# phpunit AddTest.php
# phpunit AddTest.php
PHPUnit 3.3.17 by Sebastian Bergmann.

...F
Time: 0 seconds

There was 1 failure:

1) testAdd(AddTes...
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
{


...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
ExceptionTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ExceptionTest extends PHPUnit_Framework_TestCase
{
  ...
為什麼要測試錯誤?
輸入的資料千千百百種
輸入的資料千千百百種


因為不能預期使用者會按照正常
方法來使用程式,所以測試各種
狀況就是必要的動作
沒錯反而是錯的?
沒錯反而是錯的?


當程式明顯不能接受某些輸入值
時,程式應該出現異常,而我們
要能確保這件事的發生
Fixtures
Fixtures


為每個測試案例準備同一組測試
物件,以省去在每個測試方法中
建立測試物件的成本
範例

共用測試物件的測試案例
FixtureTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    prot...
FixtureTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    prot...
FixtureTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    prot...
FixtureTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    prot...
FixtureTest.php

<?php
require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    prot...
執行步驟:
執行步驟:

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_Tes...
Package/Class1Test.php

<?php
require_once 'PHPUnit/Framework.php';
class Package_Class1Test extends PHPUnit_Framework_Tes...
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 s...
Package/AllTests.php

<?php
require_once 'PHPUnit/Framework.php';



class Package_AllTests
{
    public static function s...
Package/AllTests.php

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


class Package_A...
Package/AllTests.php

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Package/Class1Test.php';
require_once 'Pac...
Package/AllTests.php

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Package/Class1Test.php';
require_once 'Pac...
Package/AllTests.php

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Package/Class1Test.php';
require_once 'Pac...
範例 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()
    {
    ...
AllTests.php

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

class AllTests
{
    publi...
AllTests.php

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

class AllTests
{
    publi...
AllTests.php

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

class AllTests
{
    publi...
範例 3

套件中使用 setUp 及 tearDown
MyTest.php

<?php
require_once 'PHPUnit/Framework.php';
class MyTest extends PHPUnit_Framework_TestCase
{
    protected fu...
MySuite.php

<?php
require_once 'MyTest.php';
class MySuite extends PHPUnit_Framework_TestSuite
{
    public static functi...
執行步驟:

MySuite::setUp
執行步驟:

MySuite::setUp

MyTest::setUp
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
MyTest::tearDown
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
MyTest::tearDown

MyTest::setUp
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
MyTest::tearDown

MyTest::setUp
MyTest::testTwo
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
MyTest::tearDown

MyTest::setUp
MyTest::testTwo
MyTest::tearDown.
執行步驟:

MySuite::setUp

MyTest::setUp
MyTest::testOne
MyTest::tearDown

MyTest::setUp
MyTest::testTwo
MyTest::tearDown.

My...
範例 4

在組織測試中共用測試物件
MySuite.php

<?php
require_once 'MyTest.php';
class MySuite extends PHPUnit_Framework_TestSuite
{
    public static functi...
MyTest.php

<?php
require_once 'PHPUnit/Framework.php';
class MyTest extends PHPUnit_Framework_TestCase
{
    public funct...
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 ...
StubTest.php

<?php
require_once 'PHPUnit/Framework.php';

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

<?php
require_once 'PHPUnit/Framework.php';

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

<?php
require_once 'PHPUnit/Framework.php';

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

<?php
require_once 'PHPUnit/Framework.php';

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

<?php
require_once 'PHPUnit/Framework.php';

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

預測 Mock Object 的方法執行的次數
Subject.php

<?php
class Subject
{
    protected $observers = array();

    public function attach(Observer $observer)
   ...
SubjectTest.php

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

class Observer // 雖然實際的 Observer...
SubjectTest.php

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

class Observer // 雖然實際的 Observer...
SubjectTest.php

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

class Observer // 雖然實際的 Observer...
為什麼要 Mock Object?
隔離未實作的類別
隔離未實作的類別


在團隊開發時,伙伴負責的類別
尚未完成,我們必須隔離掉該部
份對目前單元的影響
無法使用線上開發
無法使用線上開發


像是線上刷卡等需要跟外部連線
的環境等
Code Coverage Analysis
Code Coverage Analysis


透過 Xdebug 來讓 PHPUnit 找出
測試所涵蓋到的程式碼
範例
# phpunit --coverage-html ./report SubjectTest.php
# phpunit --coverage-html ./report SubjectTest.php
PHPUnit 3.3.17 by Sebastian Bergmann.

.
Time: 0 seconds

OK (1 test, 1...
# phpunit --coverage-html ./report SubjectTest.php
PHPUnit 3.3.17 by Sebastian Bergmann.

.
Time: 0 seconds

OK (1 test, 1...
為什麼要知道測試涵蓋範圍?
找出尚未測試的部份
找出尚未測試的部份


程式碼測試的涵蓋範圍越廣,程
式就更加可靠
掌握開發進度
掌握開發進度


看著涵蓋範圍百分比的增加….
會很有成就感
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...
謝謝大家
Upcoming SlideShare
Loading in...5
×

PHPUnit 入門介紹

15,041

Published on

介紹 PHPUnit 的基本概念

Published in: Technology, Business
0 Comments
55 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
15,041
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
319
Comments
0
Likes
55
Embeds 0
No embeds

No notes for slide

PHPUnit 入門介紹

  1. 1. PHPUnit 入門 讓 Web 程式更加可靠 JACE JU 哇寶資訊技術總監
  2. 2. 軟體測試
  3. 3. 軟體測試 單元測試、功能測試、整合測試、 系統測試、壓力測試、使用者測 試… 註:google://軟體測試
  4. 4. 單元測試是什麼?
  5. 5. 單元測試是什麼? 單元:程式中類別的方法或是函式
  6. 6. 單元測試是什麼? 單元:程式中類別的方法或是函式 測試:驗證程式執行後預期的結果
  7. 7. 單元測試的優點
  8. 8. 為重構提供了保障
  9. 9. 為重構提供了保障 在我們新增或修改程式時,測試 能確保舊的功能不受影響
  10. 10. 讓開發者思考設計的範疇
  11. 11. 讓開發者思考設計的範疇 在測試驅動開發 (TDD) 模式下 , 測試其實就是設計
  12. 12. 自動測試
  13. 13. 自動測試 不需人工來一項一項地測試,讓 測試工具一次幫我們完成
  14. 14. 單元測試的缺點?
  15. 15. 測試太花時間?
  16. 16. 測試太花時間? 測試寫的少, Debug 到老~ 利用測試來節省尋找 Bug 的時間
  17. 17. 沒有程式?怎麼測試?
  18. 18. 沒有程式?怎麼測試? 測試就是讓我們確認程式應該要 表現出來的行為,所以反過來從 結果去推演出程式會更容易
  19. 19. Test-Driven Development 測試驅動開發
  20. 20. 測試先行
  21. 21. 測試先行 將客戶的需求都轉換成測試,通 過測試就是完成需求
  22. 22. 讓程式開發的方向有所依據
  23. 23. 讓程式開發的方向有所依據 測試先行會讓程式開發方向收歛 在一定的合理範圍中
  24. 24. PHP 上的單元測試工具
  25. 25. SimpleTest http://www.simpletest.org/ 最大特色: PHP 4/5 相容
  26. 26. PHPUnit http://www.phpunit.de/ 最大特色:完整利用 PHP5 特色 (例如 SPL, Reflection…) 註:SPL: Standard PHP Library
  27. 27. 安裝 PHPUnit
  28. 28. 安裝 PHPUnit •從 PEAR 安裝
  29. 29. 安裝 PHPUnit •從 PEAR 安裝 •手動安裝
  30. 30. 範例 第一個 PHPUnit 測試案例
  31. 31. 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 . '.'; } }
  32. 32. PersonTest.php <?php require_once 'PHPUnit/Framework/TestCase.php';
  33. 33. PersonTest.php <?php require_once 'PHPUnit/Framework/TestCase.php'; require_once 'Person.php';
  34. 34. PersonTest.php <?php require_once 'PHPUnit/Framework/TestCase.php'; require_once 'Person.php'; class PersonTest extends PHPUnit_Framework_TestCase { }
  35. 35. PersonTest.php <?php require_once 'PHPUnit/Framework/TestCase.php'; require_once 'Person.php'; class PersonTest extends PHPUnit_Framework_TestCase { public function testSayHelloTo() { } }
  36. 36. 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'); } }
  37. 37. 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( ); } }
  38. 38. 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.', ); } }
  39. 39. 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') ); } }
  40. 40. 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
  41. 41. 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)
  42. 42. 怎麼樣算是好測試
  43. 43. 說明開發者要表達的意圖
  44. 44. 說明開發者要表達的意圖 測試就像程式的說明文件,要能 清楚傳達出如何使用這支程式
  45. 45. 分離程式,以便測試
  46. 46. 分離程式,以便測試 切細程式的功能,將能夠單獨測 試的部份獨立出來
  47. 47. 測試結果要跟預期一樣
  48. 48. 測試結果要跟預期一樣 測試出預期結果是應該的,而且 不能只有一次正確,而且在修改 程式後都要是一直正確的 (除非功 能改變)
  49. 49. 好測試要可被依賴
  50. 50. 好測試要可被依賴 單元測試要保證程式的正確性, 那麼單元測試也一定要值得信任
  51. 51. PHPUnit 的幾大特色
  52. 52. Data Providers
  53. 53. Data Providers 不需要為不同的測試資料重複相 同的程式碼
  54. 54. 範例 多筆資料同時測試
  55. 55. 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; } }
  56. 56. AddTest.php <?php require_once 'PHPUnit/Framework/TestCase.php'; require_once 'Add.php'; class AddTest extends PHPUnit_Framework_TestCase { }
  57. 57. AddTest.php <?php require_once 'PHPUnit/Framework/TestCase.php'; require_once 'Add.php'; class AddTest extends PHPUnit_Framework_TestCase { public function testAdd( ) { } }
  58. 58. 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()); } }
  59. 59. 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()); } }
  60. 60. 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()); } }
  61. 61. 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() { } }
  62. 62. 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) ); // 可以用迴圈或讀檔的方式去產生大量的測試資料 } }
  63. 63. # phpunit AddTest.php
  64. 64. # phpunit AddTest.php PHPUnit 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>. xxxxxxxxAddTest.php:15 FAILURES! Tests: 4, Assertions: 4, Failures: 1.
  65. 65. Expected Exception
  66. 66. Expected Exception 預期程式會出現語法不正確以外 的錯誤或異常
  67. 67. 範例 預期會出現異常的測試
  68. 68. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php';
  69. 69. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php'; class ExceptionTest extends PHPUnit_Framework_TestCase { public function testException() { } }
  70. 70. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php'; class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } }
  71. 71. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php'; class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } } # phpunit ExceptionTest.php
  72. 72. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php'; class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } } # phpunit ExceptionTest.php PHPUnit 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.
  73. 73. ExceptionTest.php <?php require_once 'PHPUnit/Framework.php'; class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } }
  74. 74. 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 來完成 } }
  75. 75. 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
  76. 76. 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 PHPUnit 3.3.17 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertion)
  77. 77. 為什麼要測試錯誤?
  78. 78. 輸入的資料千千百百種
  79. 79. 輸入的資料千千百百種 因為不能預期使用者會按照正常 方法來使用程式,所以測試各種 狀況就是必要的動作
  80. 80. 沒錯反而是錯的?
  81. 81. 沒錯反而是錯的? 當程式明顯不能接受某些輸入值 時,程式應該出現異常,而我們 要能確保這件事的發生
  82. 82. Fixtures
  83. 83. Fixtures 為每個測試案例準備同一組測試 物件,以省去在每個測試方法中 建立測試物件的成本
  84. 84. 範例 共用測試物件的測試案例
  85. 85. FixtureTest.php <?php require_once 'PHPUnit/Framework.php'; class ArrayTest extends PHPUnit_Framework_TestCase { protected $fixture; }
  86. 86. 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)); } }
  87. 87. 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)); } }
  88. 88. 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)); } }
  89. 89. 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); } }
  90. 90. 執行步驟:
  91. 91. 執行步驟: setUp()
  92. 92. 執行步驟: setUp() testNewArrayIsEmpty()
  93. 93. 執行步驟: setUp() testNewArrayIsEmpty() tearDown()
  94. 94. 執行步驟: setUp() testNewArrayIsEmpty() tearDown() setUp()
  95. 95. 執行步驟: setUp() testNewArrayIsEmpty() tearDown() setUp() testArrayContainsAnElement()
  96. 96. 執行步驟: setUp() testNewArrayIsEmpty() tearDown() setUp() testArrayContainsAnElement() tearDown()
  97. 97. Organizing Tests
  98. 98. Organizing Tests 將同性質 (套件、專案) 的測試案 例組織起來一起測試,就不需要 一個一個案例測試了
  99. 99. 範例 1 組織套件中多個的類別的測試
  100. 100. Package/Class1Test.php <?php require_once 'PHPUnit/Framework.php'; class Package_Class1Test extends PHPUnit_Framework_TestCase { public function testOne() {} public function testTwo() {} }
  101. 101. 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() {} }
  102. 102. Package/AllTests.php <?php require_once 'PHPUnit/Framework.php'; class Package_AllTests { }
  103. 103. Package/AllTests.php <?php require_once 'PHPUnit/Framework.php'; class Package_AllTests { public static function suite() { } }
  104. 104. Package/AllTests.php <?php require_once 'PHPUnit/Framework.php'; class Package_AllTests { public static function suite() { $suite = new PHPUnit_Framework_TestSuite('Package'); return $suite; } }
  105. 105. 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; } }
  106. 106. 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; } }
  107. 107. 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
  108. 108. 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 PHPUnit 3.3.17 by Sebastian Bergmann. .... Time: 0 seconds OK (4 tests, 0 assertions)
  109. 109. 範例 2 組織專案中多個套件的測試
  110. 110. AllTests.php <?php require_once 'PHPUnit/Framework.php'; class AllTests { public static function suite() { } }
  111. 111. AllTests.php <?php require_once 'PHPUnit/Framework.php'; class AllTests { public static function suite() { $suite = new PHPUnit_Framework_TestSuite('Project'); return $suite; } }
  112. 112. 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; } }
  113. 113. 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
  114. 114. 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 PHPUnit 3.3.17 by Sebastian Bergmann. .... Time: 0 seconds OK (4 tests, 0 assertions)
  115. 115. 範例 3 套件中使用 setUp 及 tearDown
  116. 116. 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__; } }
  117. 117. 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__; } }
  118. 118. 執行步驟: MySuite::setUp
  119. 119. 執行步驟: MySuite::setUp MyTest::setUp
  120. 120. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne
  121. 121. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne MyTest::tearDown
  122. 122. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne MyTest::tearDown MyTest::setUp
  123. 123. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne MyTest::tearDown MyTest::setUp MyTest::testTwo
  124. 124. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne MyTest::tearDown MyTest::setUp MyTest::testTwo MyTest::tearDown.
  125. 125. 執行步驟: MySuite::setUp MyTest::setUp MyTest::testOne MyTest::tearDown MyTest::setUp MyTest::testTwo MyTest::tearDown. MySuite::tearDown
  126. 126. 範例 4 在組織測試中共用測試物件
  127. 127. 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; } }
  128. 128. MyTest.php <?php require_once 'PHPUnit/Framework.php'; class MyTest extends PHPUnit_Framework_TestCase { public function testOne() { $this->assertEquals('something', $this->sharedFixture); } }
  129. 129. PHPUnit 基本結構
  130. 130. Composite 模式
  131. 131. Doubles
  132. 132. Doubles 對未產生或建立過程複雜的物件 進行模擬以區隔它們對測試的影 響,也就是 Mock Object 註:PHPUnit 的 Mock 仿照自 jMock 1
  133. 133. 範例 1 指定 Mock Object 的方法會輸出的值
  134. 134. StubTest.php <?php require_once 'PHPUnit/Framework.php'; class SomeClass // 給 Mock Object 用的類別宣告,不一定要有實際內容 { public function doSomething() { } } class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { } }
  135. 135. 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 } }
  136. 136. 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()) // 不論呼叫幾次 } }
  137. 137. 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') // 指定方法 } }
  138. 138. 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')); // 指定回傳值 } }
  139. 139. 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()); } }
  140. 140. 範例 2 預測 Mock Object 的方法執行的次數
  141. 141. 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); } } }
  142. 142. 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() { } }
  143. 143. 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')); } }
  144. 144. 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(); } }
  145. 145. 為什麼要 Mock Object?
  146. 146. 隔離未實作的類別
  147. 147. 隔離未實作的類別 在團隊開發時,伙伴負責的類別 尚未完成,我們必須隔離掉該部 份對目前單元的影響
  148. 148. 無法使用線上開發
  149. 149. 無法使用線上開發 像是線上刷卡等需要跟外部連線 的環境等
  150. 150. Code Coverage Analysis
  151. 151. Code Coverage Analysis 透過 Xdebug 來讓 PHPUnit 找出 測試所涵蓋到的程式碼
  152. 152. 範例
  153. 153. # phpunit --coverage-html ./report SubjectTest.php
  154. 154. # phpunit --coverage-html ./report SubjectTest.php PHPUnit 3.3.17 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertion) Generating code coverage report, this may take a moment.
  155. 155. # phpunit --coverage-html ./report SubjectTest.php PHPUnit 3.3.17 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertion) Generating code coverage report, this may take a moment.
  156. 156. 為什麼要知道測試涵蓋範圍?
  157. 157. 找出尚未測試的部份
  158. 158. 找出尚未測試的部份 程式碼測試的涵蓋範圍越廣,程 式就更加可靠
  159. 159. 掌握開發進度
  160. 160. 掌握開發進度 看著涵蓋範圍百分比的增加…. 會很有成就感
  161. 161. PHP 需要單元測試嗎?
  162. 162. 小案子就放它一馬
  163. 163. 小案子就放它一馬 對 Web 開發而言,一般的 CRUD 並沒有特別需要做測試,像是留 言板、活動頁等 註:CRUD: create, read, update, delete
  164. 164. 測試有複雜演算的程式
  165. 165. 測試有複雜演算的程式 資料樣本很多,而且容易出現不 同結果的狀況之下,就需要測試
  166. 166. 測試有複雜演算的程式 資料樣本很多,而且容易出現不 同結果的狀況之下,就需要測試 不然就會像 Dell 一樣!
  167. 167. 更多進階資訊
  168. 168. 更多進階資訊 •PHPUnit 官方手冊 (http://www.phpunit.de/manual/3.3/en/index.html) •jUnit (http://www.junit.org) •jMock (http://www.jmock.org/)
  169. 169. 謝謝大家
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×