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)

* 紅燈 -> 綠燈 -> 重構
* **紅燈**:寫一個測試並且執行讓他 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
```

* 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`

執行測試:


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)