Laradebut #8: PHPUnit 新手村 === * 導讀者: Mouson(陳佑竹) * Laradebut \#8: PHPUnit 新手村 - HackMD - https://hackmd.io/s/Sk4xL6jAe * GitHub: https://github.com/mouson/laradebut8_phpunit_demo ## 零、環境需求 * PHP >= 5.6.4 * Composer ready * 想一起實作的朋友,先建立一個新的 Laravel 專案 ```shell= 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) ![](https://i.imgur.com/AJrlcl0.png) * 紅燈 -> 綠燈 -> 重構 * **紅燈**:寫一個測試並且執行讓他 test fail * **綠燈**:使程式碼可以執行,並且 test pass * **重構**:重構程式碼,並且保持 test pass ## 五、範例: ### 1. Function PHPUnit - array_until 1. 目標: 使大家對於 PHPUnit 有基本的認識,並且完成第一個 function PHPUnit test 2. 專案描述: 撰寫一個取出陣列中某個元素之前的所有元素的 function。例如: ```php= $names = ['Taylor', 'Dayle', 'Matthew', 'Shawn', 'Neil']; $result = array_until($names, 'Matthew'); var_dump($result); /** * array ( * 0 => 'Taylor', * 1 => 'Dayle', * ) */ ``` 3. 初始專案: ```shell= composer create-project --prefer-dist laravel/laravel phpunit ``` 4. 環境設定: * Console: ```shell= cd project ./vendor/bin/phpunit ``` ![](https://i.imgur.com/UKRinmj.png) * PhpStorm: ![](https://i.imgur.com/x908Hgp.png) ![](https://i.imgur.com/AVfFSxZ.png) * Path to phpunit.phar: `{Project Path}/vendor/bin/phpunit` * Default configuration file: `{Project Path}/phpunit.xml` * Default bootstrap file: `{Project Path}/bootstrap/autoload.php` ![](https://i.imgur.com/ZaRtCIs.png) 執行測試: ![](https://i.imgur.com/AfcTr7c.png) ![](https://i.imgur.com/0JPOIZC.png) 4. 建立第一個測試 ```PHP= namespace Tests\Unit; use Tests\TestCase; class ArrayUntilTest extends TestCase { } ``` 由於為 `Laravel` 專案,因此必須 use `Test\TestCase`,底層為 Laravel 再包過一層自己的工具的版本。 ```php= // 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 { } ``` 5. 建立 array_until function 測試 ```PHP= 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); } ``` 4. 實作 array_until function ```PHP= function array_until(array $array, $search) { $position = array_search($search, $array); if ($position == false) { throw new InvalidArgumentException; } return array_slice($array, 0, $position); } ``` 6. 新增規格 test key 不存在時,應跳 exception 驗證 ```php= /** * @test */ public function testArrayUntilKeyDoesNotExistThrowException() { /** Arrange */ $names = ['Mark', 'Duncan', 'Erica', 'Fish']; /** Assume */ $this->expectException(\InvalidArgumentException::class); /** Act */ $actual = array_until($names, 'NotExist'); /** Assert */ } ``` 7. 實作跳出 exception ```php= if ($position == false) { throw new InvalidArgumentException; } ``` ### 2. calculator 計算機 class 實作 1. 目標: 實作計算機的運算(加減乘除)等功能,採用 TDD 的方法。(範例參考來源:[Laravel Testing Decoded by JeffreyWay](https://leanpub.com/laravel-testing-decoded) / 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 當有測試案例保護時,進行比較大幅度的修改也相對的不會害怕 16. 實作:建立 Operation Interface 及 Addition Class 17. 測試:修改新增的案例,改為使用 Operation 的使用方法 18. 實作:更新計算機使用方法 19. 重構:修改所有與加法有關的測試案例 20. 實作:實作減法類別,也使用 operation 套用至 Subtract Class 21. 測試:新增乘法的驗算 22. 實作:乘法運算 23. 重構:以 Mockery mock 掉運算 24. 測試:針對 Addition 進行單元測試 ## 參考資源: * [\[30天快速上手TDD\]目錄與附錄 \| In 91 \- 點部落](https://dotblogs.com.tw/hatelove/2013/01/11/learning-tdd-in-30-days-catalog-and-reference) * [Laravel Testing Decoded by JeffreyWay](https://leanpub.com/laravel-testing-decoded) / Ch5)