Try   HackMD

Laradebut #8: PHPUnit 新手村

零、環境需求

  • PHP >= 5.6.4
  • Composer ready
  • 想一起實作的朋友,先建立一個新的 Laravel 專案
composer create-project --prefer-dist laravel/laravel phpunit

ㄧ、單元測試 UNIT Testing ?!

  • 測試最小單位的邏輯,通常是 Class 的 method
  • 不依賴外部「不可控」的環境
  • 測試裡不具備邏輯:如 if else, switch case
  • 測試案例之間彼此獨立
  • 一次只測試一件事情

二、單元測試的 FIRST 原則

  • Fast:快速。可以很快看到結果。
  • Independent:獨立。測試案例之間不互相影響。
  • Repeatable:可重複。可重複執行而不影響預期結果。
  • Self-Validating:不需要人工介入驗證。
  • Timely:及時。程式完成即可立刻驗證。

三、單元測試的 3A 原則

  • Arrange : 初始化目標物件、相依物件、方法參數、預期結果,或是預期與相依物件的互動方式。
  • Assume:預期結果、或預期的互動
  • Act : 呼叫目標物件的方法。
  • Assert : 驗證是否符合預期。

四、TDD (Test Driven Design)

  • 紅燈 -> 綠燈 -> 重構
  • 紅燈:寫一個測試並且執行讓他 test fail
  • 綠燈:使程式碼可以執行,並且 test pass
  • 重構:重構程式碼,並且保持 test pass

五、範例:

1. Function PHPUnit - array_until

  1. 目標:

    使大家對於 PHPUnit 有基本的認識,並且完成第一個 function PHPUnit test

  2. 專案描述:

    撰寫一個取出陣列中某個元素之前的所有元素的 function。例如:

    $names = ['Taylor', 'Dayle', 'Matthew', 'Shawn', 'Neil']; ​ ​$result = array_until($names, 'Matthew'); ​ ​var_dump($result); ​/** ​ * array ( ​​​​ * 0 => 'Taylor', ​​​​ * 1 => 'Dayle', ​​​​ * ) ​ */
  3. 初始專案:

composer create-project --prefer-dist laravel/laravel phpunit
  1. 環境設定:
  • Console:
cd project ./vendor/bin/phpunit

  • PhpStorm:

  • Path to phpunit.phar: {Project Path}/vendor/bin/phpunit
  • Default configuration file: {Project Path}/phpunit.xml
  • Default bootstrap file: {Project Path}/bootstrap/autoload.php

執行測試:

  1. 建立第一個測試
namespace Tests\Unit; use Tests\TestCase; class ArrayUntilTest extends TestCase { }

由於為 Laravel 專案,因此必須 use Test\TestCase,底層為 Laravel 再包過一層自己的工具的版本。

// Laravel 的 PHPUnit 如下包裹: use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { } use PHPUnit\Framework\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { }
  1. 建立 array_until function 測試
public function testFetchesItemsInArrayUntilKey() { /** Arrange */ $names = ['Taylor', 'Dayle', 'Matthew', 'Shawn', 'Neil']; /** Assume */ $expected = ['Taylor', 'Dayle']; /** Act */ $result = array_until('Matthew', $names); /** Assert */ $this->assertEquals($expected, $result); }
  1. 實作 array_until function
function array_until(array $array, $search) { $position = array_search($search, $array); if ($position == false) { throw new InvalidArgumentException; } return array_slice($array, 0, $position); }
  1. 新增規格 test key 不存在時,應跳 exception 驗證
/** * @test */ public function testArrayUntilKeyDoesNotExistThrowException() { /** Arrange */ $names = ['Mark', 'Duncan', 'Erica', 'Fish']; /** Assume */ $this->expectException(\InvalidArgumentException::class); /** Act */ $actual = array_until($names, 'NotExist'); /** Assert */ }
  1. 實作跳出 exception
if ($position == false) { throw new InvalidArgumentException; }

2. calculator 計算機 class 實作

  1. 目標: 實作計算機的運算(加減乘除)等功能,採用 TDD 的方法。(範例參考來源:Laravel Testing Decoded by JeffreyWay / Ch5)

  2. 測試:新增驗證 Calculator 存在

  3. 實作:新增對應之 Class 使測試通過

  4. 測試:新增驗證 Result 預設為 0

  5. 實作:實現 calculator 之 Result 預設為 0

  6. 測試:新增 add 的 method 測試

  7. 實作:add method

  8. 測試:新增當遇到非數字時,應丟出 例外

  9. 實作:實作當輸入非數字時,丟出異常

  10. 測試:可以連加多筆數字

  11. 實作:連加多筆數字

  12. 測試:減法的 method

  13. 實作:減法並且可連減

  14. 重構:重構測試案例 萃取出 new Calculator 到 setUp method

  15. 重構:重構 production code 使 foreach 迴圈獨立到 calculateAll

當有測試案例保護時,進行比較大幅度的修改也相對的不會害怕

  1. 實作:建立 Operation Interface 及 Addition Class
  2. 測試:修改新增的案例,改為使用 Operation 的使用方法
  3. 實作:更新計算機使用方法
  4. 重構:修改所有與加法有關的測試案例
  5. 實作:實作減法類別,也使用 operation 套用至 Subtract Class
  6. 測試:新增乘法的驗算
  7. 實作:乘法運算
  8. 重構:以 Mockery mock 掉運算
  9. 測試:針對 Addition 進行單元測試

參考資源: