TDD Using PHPUnit

PHPUnit Test

  • Composer.json定義版本號
  • PHPUnit.xml定義單元測試的規則
    • 檔案名稱用Test當後綴
    • 方法名稱用test當前綴
  • 增加可讀性
    • @test註解
    • function的名稱可以用底線來連結
    • CamelCase plugin
      • (V) a_product_has_a_name
      • (X) AProductHasAName
  • 新增一個屬性,結果原先的unit test變成不通過
    • construct少傳一個參數,兩種解法
      • 給預設值
      • 修改舊的測試方法,把要傳的參數也加上去
    • filter方法,可以針對要測試的方法指定方法名稱或類別名稱
  • 原則
    • 根據unit test回傳的錯誤訊息來調整代碼
  • 測試Order
    • 一個Order有多個商品,加總起來個數和總經額要經過測試
    • assertCount()計算陣列元素個數
  • 重構
    • setUp()把物件整理出來,單元測試在執行前會先執行setUp的方法
    • 缺點:耦合度提高,牽一髮動全身
    • stop-on-error:Stop execution upon first error
  • 測試Model的資料
    • 測試最多觀看文章資料
    • use DatabaseTransaction,執行完之後會在執行rollback
    • 安裝sqlite
      • sudo apt-get install php7.2-sqlite
      • 修改/etc/php/php7.2/php.ini的,把分號拿掉
    • 自己做一次發現卡住
      • 少做了修改database的預設配置文件(config/database.php)
        • env(‘DB_CONNECTION’, ‘sqlite’),
      • php artisan make:modol Article -m
        • 建立model文件和migration文件
        • 修改migration的定義
      • 用model::class來創建紀錄
  • 新增團隊案例
    • 一個團隊可以加入多個User,多對多關係
    • 拋出例外可以整理成protected的function比較容易閱讀
  • Feature和Unit Test的差異
    • Feature測試模仿實際操作的過程
    • Unit Test則是每一個拆開的方法(向洋蔥一樣,最核心的部分就是單元測試)
  • 安裝phpunit watch
    • composer global require spatie/phpunit-watcher
    • composer require spatie/phpunit-watcher --dev
      • 要require進來才可以用,光是安裝是沒有用的
    • phpunit-watcher watch
  • laravel 5.4之後才支持dusk針對瀏覽器的測試

PHPUnit In Action

  • 踩坑了,結果書中已經有說這個問題
    • 使用了 Data Provider 的測試,它的輸出將無法注入到其他相依於它的測試
  • 加上namespace測試一下會不會重現laravel例外的問題
    • 結果依然沒有辦法重現QQ
    • 用get_class打上完整的namespace類別名稱,有取到那個類別,但是單元測試不給過
  • 測試了一般的Exception才給過,感動
  • phpunit --configuration
    • 產生phpunit.xml
    • 方便整合測試使用(CI)
  • 產生測試報告
    • phpunit --coverage-html ./report tests

移除團隊成員(Unit Test)

  • 一個團隊有多個人,每個人記錄一組team_id(所屬的團隊)
  • 新增多個團隊成員
    • instanceof判斷是不是User的Model
    • 如果是的話就用save,不是就用saveMany(Collection)
  • 移除多個團隊成員
    • remove():移除User的team_id
    • 注意:User的team_id要是fillable才可以
    • 兩種方法可以移除collection的team_id
      • 參數法:each每一個user做update
      • 關聯法:透過members()關聯,whereIn條件搭配使用pluck update
  • php 7.2會有count不能count collection的問題
  • 测试团队移除成员的功能
1
2
3
4
5
6
7
// 方法1:參數法
$users->each(function($user){
return $user->update(['team_id'=>null]);
});
// 方法2:關聯法pluck
return $this->members()->whereIn('id', $users->pluck('id'))->update(['team_id'=>null]);

參考連結

Using NodeJs Unit Testing(Jest)

  • Why Unit Testing
    • project starts simple, but grows more and more complex
    • horrifying functions start a simple way
    • You must think longer than your nose goes(Swedish sayings proverb)
  • add shipping
    • if the total amount is greater than 1000, then it’s free
    • instead of thinking lots of cases at the same time, we break it down by several isolated steps
  • add taxes
  • Happy Test
    • the main function the most time program spend

TDD 3 ways

- obvious implementation
- fake it until you make it
- triangalation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function orderFilterTotal(order) {
// 直接返回結果不需要再用一個變數來接
// 三元表達式(item.quantity ? item.quantity : 1)
return order.items
.reduce((total, item) => {return (item.price * (item.quantity || 1) + total) }, 0);
}


if(orderFilterTotal({
items: [
{name:'cat food', price: 8, quantity: 2},
{name:'cat cage', price: 800, quantity: 3}
]
}) != 2416) {
throw new Error("Check Fail: Quantity Error)")
}

if(orderFilterTotal({
items: [
{name:'cat food', price: 3}
]
}) != 3) {
throw new Error("Check Fail: No Quantity")
}



if(orderFilterTotal({
items: [
{name:'cat food', price: 8},
{name:'cat cage', price: 800}
]
}) != 808) {
throw new Error("Check Fail: Happy Test(Example 1)")
}

if(orderFilterTotal({
items: [
{name:'cat toy', price: 30},
{name:'cat towel', price: 40}
]
}) != 70) {
throw new Error("Check Fail: Happy Test(Example 2)")
}

Jest Auto Test

  • You need to notice your test file name to be xxxx.test.js format
    • it doesn’t matter the test file goes, but the file name matter
1
2
3
4
5
// Happy Path
it('works', () => {
expect(1).toBe(1);
})

Javascript Functional Programming

Functional Programming

  • Less Bug/Code
    • Composable High order function(filter、map、reduce)
    • Functions are values
    • function inside a function, we call callback(stack)
  • Map
    • Arrow Function can write less code. It means less bug
      • you can remove function & return keyword when using arrow function
      • if you have multiple operations in our reduce function need return keyword
    • map function just like filter function and it takes callback function as parameter
  • Reduce
    • can do anything like find, map or filter do
    • the difference between map and reduce is that it need initial value behind the function
    • the second parameter must be value, can not be variable

CSV to json format(Using NodeJS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fs = require('fs');
var output = fs.readFileSync('data/data.txt', 'utf8')
.trim()
.split("\n")
.map((row) => row.split(','))
.reduce((customers,row)=>{
// 如果沒有值就用空的當預設值,之後push才不會有問題
customers[row[0]] = customers[row[0]] || [];
customers[row[0]].push({
'product':row[1],
'price':row[2],
'amount':row[3]
});
return customers;
}, {});

console.log('output',JSON.stringify(output, null, 2));

1
2
3
4
5
6
mark johansson,waffle iron,80,2
mark johansson,blender,200,1
mark johansson,knife,10,4
Nikita Smith,waffle iron,80,1
Nikita Smith,knife,10,2
Nikita Smith,pot,20,3

recursive

- maxium call of stack
- es5 having the maxium call of stack limitation, but es6 removed this limitation by using bubble simulation
    -  tail call optimization.
1
2
3
4
5
6
7
8
9
10
11
var getMenuTree = (category, parent) => {
let nodeResult = {};
category
.filter((c) => c.parent_id == parent)
.forEach((c) => nodeResult[c.id] = getMenuTree(category, c.id));
return nodeResult;
};
var categories = JSON.parse(fs.readFileSync('data/menutree.json', 'utf8'));

//'hello',categories,
console.log(JSON.stringify(getMenuTree(categories, null), null, 2));
1
[{"id":"animals","parent_id":null},{"id":"mammals","parent_id":"animals"},{"id":"cats","parent_id":"mammals"},{"id":"dogs","parent_id":"mammals"},{"id":"chihuahua","parent_id":"dogs"},{"id":"labrador","parent_id":"dogs"},{"id":"persian","parent_id":"cats"},{"id":"siamese","parent_id":"cats"}]

FuncDoor

  • Using FuncDoor
    • need passing one string parameter and one function parameter
    • they will map together when be called
    • pass each value to each function and return the new structures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var fs = require('fs');

var reduceCustomer = (output) => output.reduce((output,row)=>{
// 如果沒有值就用空的當預設值,之後push才不會有問題
output[row[0]] = output[row[0]] || [];
output[row[0]].push({
'product':row[1],
'price':row[2],
'amount':row[3]
});
return output;
}, {});

var funcDoor = (path, fn) => {
var rawData = (path) => fs.readFileSync(path, 'utf8')
.trim()
.split("\n");
// 要傳入參數,否則最後的結果還是function
var result = (path)=>fn(rawData(path).map((row) => {
return row.split(',')
} ));
return result(path);
}


console.log( funcDoor('data/data.txt', reduceCustomer));
// return;
// console.log('output',JSON.stringify(convertJson('data/data.txt'), null, 2));

var reduceCsv = (output) => output.reduce((output, row) => {
// 直接整理到一個result的key,之後就不用煩惱怎麼把key移掉
output['result'] = output['result']||[];
let input = {
'id':row[0],
'parent_id':row[1]!='null' ? row[1] : JSON.parse(row[1])
};
output['result'].push(input);
// output[i] = input;
return output
} ,{})['result']
let menuTreeResult;
console.log(menuTreeResult = funcDoor('data/menutree.txt', reduceCsv));
fs.writeFile('./data/menutree.json',JSON.stringify(menuTreeResult, null ,2));

Jquery Form Validate

  • normalize css和jquery來製作表單
    • 能夠在各個瀏覽器呈現差不多的結果
    • 用input-control而不是form-control,比較清楚
  • 版面居中
    • wrap定義max-width不一定會用到
    • 實際用到的是wrap-sm
  • 使用*確認每個元素的布局
    • 先把結構確定下來,具體的不在乎
  • 搞清楚我們要甚麼?
    • 可以直接挖一口井就完事了
      • 直接定義到main.js
    • 也可以搞一個自然水系統,雖然一開始要照顧的情況比較多,之後只要幾個設定就完事了
      • 在input裡面建立data-rule
  • ‘use strict’
    • 不汙染全局變量
    • main.js中負責傳入string和驗證規則
    • 另外建立一個validate.js來接收string和驗證規則
  • 使用jquery內建的方法過濾資料
    • trim()和isNumeric()
    • val!==0的判斷是用來處理trim之後為空的情況
    • use strict其實用前面!val的寫法來判斷即可
  • JSON.parse
    • 如果要讓值變成字串,要用雙引號
    • 會用到正規表達式
  • 寫一個專門找Input的模組
    • *:至少0個以上的字元
    • +:至少1個以上的字元(因為有require的規則可以驗證,不用+)
    • isValid的方法寫道validate的模組裡面

Bootstrap 4 Navbar/Dropdown Cheatsheet

Navbar導行列

分結構和樣式

  • 結構
    • nav.navbar
    • nav.navbar-expand-xl
    • ul.navbar-nav
    • li.nav-item
    • a.nav-link
  • 樣式
    • nav.navbar-dark
    • nav.navbar-light
    • nav.bg-dark
    • nav.bg-light
  • 位置
    • nav.fixed-top
    • nav.fixed-bottom
    • nav.sticky-top
  • 顯示/隱藏
    • button.navbar-toggler
      • data-toggle = “collapse”
      • data-target = “#target_name”
    • span.navbar-toggler-icon
    • div.collapse
    • div.navbar-collapse
    • id = “target_name”
  • 下拉選單
    • li.dropdown
    • a.dropdown-toggle
      • data-toggle = ‘dropdown’
      • id = ‘#target_name’
    • div.dropdown-menu
    • div.dropdown-divider
      • aria-labelledby = target_name
    • a.dropdown-item

踩坑

  • 如果沒有加上下拉選單的aria-labelledby的屬性,會出現只能展開無法收合的問題
  • caret已經不支持了,改用dropdown-toggle的class

Bootstrap 4

  • 一般不要去改依賴庫的程式,那個工作讓依賴包管理來處理就好
    • 對修改封閉
  • 快捷鍵
    • ctrl+delete:刪除一個單詞
    • lipsum:產生dumy words
      • 不知道為什麼一開始cloud9會沒有辦法識別
      • 之後反而要打Lorem了,怪怪的
    • cloud 9預設Emmet plugin
  • 三塊的布局
    • col-sm-xx(加起來要是12)
  • 導覽列
    • 上方navbar
      • 最外層用div.navbar
      • 內層有個div.nav.navbar-header
      • 導航列用ul.nav.navbar-nav
    • bootstrap 4:
      • mr-auto:向左靠(margin-right-auto)
      • ml-auto:向右靠(margin-left-auto)
      • navbar-expand-lg:當網頁是一般螢幕的時候展開
      • navbar-collapse:隱藏起來
      • navbar-toggler-icon
    • 和上方文字對齊,加上row的div
      • 因為row有css的樣式,把他的margin和padding覆蓋掉
    • 踩到官網3.3.1 slim.js的問題
      • 用的太新了,改用六角學院的連結就有用(3.2.1.slim)
      • 小畫面的時候會沒有辦法toggle導覽列
    • 修改Logo
      • background-image:url(設定圖片位置)
      • background-size:
        • cover(圖片填充,會發現整個圖片重複了,而且覺得太滿了一點)
        • 80%
      • background-repeat:no-repeat
        • 調整background-size圖片重複的問題
      • background-position:調整圖片位置
  • 左方sidebar
    • 多增加一個sidebar的類,比較明確,避免改到其他的地方的樣式
    • list-group-item
    • 學會了怎麼用emmet添加多個假的item
      • div.list-group>(a[#].list-group-item.list-group-item-hover{item$})*5
  • 中間區塊
    • 圖片設置
      • 讓圖片限制在一個範圍內
      • max-width:100%(讓他不會超過外面元素的100%)
      • col-xs-xx:在bootstrap 4不管用拉(用col-md-{})
    • 整理共用樣式用逗號串接
    • grid system如果要顯示在同一列要用row包起來
    • 用一個div包起來info
      • 也方便統一裡面的樣式
    • 調整title行距
      • 改成超連結,display:block
    • clearfix:bootstrap 3需要清除float
  • 右方區塊
    • 一般放業務有關的訊息
    • 不能用數字命名阿
    • 把標題字調大
    • 不確定怎麼把右方標題的上下間距調大一些
  • 新聞詳細頁
    • lorem*10
    • Label用法

參考連結

Git Memo

還魂藥Git

  • 兩個方法建立repository
    • git init:沒有資料夾會創建一個以repository為名稱的資料夾
    • git clone:從既有的專案取得檔案
  • 基本用法
    • git diff:比對差異
    • git log -p:查看commit的結果(把差異也顯示到命令提示字元上)
    • git add .:把異動加到暫存區()
    • git commit -m “[init]在這裡打上異動”
    • git status:查看commit狀態
    • git checkout 1234567(七位以上commit-id) #還原到哪一個版本
  • 異動的三個狀態
    • 修改(modified) => add改變狀態到staged
    • 暫存(staged) => commit改變狀態到committed
    • 提交(committed) => log -p看修改紀錄
    • 還原(checkout) => checkout 1234567
  • git log
    • –oneline:全部一行顯示
    • p:查看異動內容
    • –all顯示所有
  • git tag
    • 在當前的分支中建立tag,和commit一樣也可以加上描述
    • 應用情境:非常複雜的節點
    • -a v1 -m “第一版完成”
      • a:annotation(加了之後訊息比較全面)
    • –delete v1
    • checkout v1
  • git show v1(標籤名稱)
    • 列出所有commit和tag標籤的描述
  • git remote
  • git push
    • origin branch
  • git pull
    • git fetch && git merge
    • 同步資料
  • git merge
    • 上面的異動是自己的
    • 下面的是已經在master上面的異動

Using RegExr

正規表達式

  • .+:某
    • .+searchItem
  • .:代表任何東西
    • 任意一個東西
    • 小丑卡,可以代表任何東西
    • 萬用任意(wildcard)
    • 如果想要match到點的話,要跳脫/.
    • 王…
    • 匹配3.14 => 3.14
  • \w:
    • 所有的數字、文字和底線(可以用來檢查用戶名稱)
    • \W:\w的補集(加減乘除)
  • \d:
    • 匹配數字
    • \D:\d的補集(除了數字以外)
  • \s:
    • 匹配空格、tab
    • \S:\s的補集(除了空白以外)
  • []:可以是甚麼的字元
    • [^b]og:不是b
    • 轉成unicode:19968-40869
  • ():分組(常用)
    • (\w+)
  • *:匹配所有
    • {0,}
  • +:匹配至少一個字元
    • {1,} at least 1
  • ?:匹配零個或1個
    • {0,1}
  • {}:要出現幾次甚麼東西
    • z{3}:zzz

範例

參考連結

Using Composer Learning Unit Test Part 2

PHP Unit Test

  • 要如何直接用assertEquals的function呢?
    • 因為每一次都會用到$this->assertEquals()有點多餘
    • 找到實際定義assertEquals的function整理成一個檔案
    • 讓phpunit.xml直接參考到bootstrap.php自定義的檔案
1
2
3
4
// tests/bootstrap.php
require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/../vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';

  • 測試的種類

    • Unit Test < Integration Test < Function Test < Acceptance Test
    • Unit Test
      • Basic Test
    • Integration Test
      • 測試兩個獨立系統是否能正常運作
    • Function/Acceptance Test
      • 測試過Unit Test和Integration Test不一定這一階段就會過
      • Acceptance Test/配合Selenium測試UI
  • mock的概念

    • 不在乎run的內容,只關注那個function是否有被呼叫到了
    • swap out the actual action
    • 和new 物件的作法比較(會實際呼叫到物件定義的方法)
    • 可以用於依賴注入(controller有使用到其他物件的資料)
1
2
3
4
5
6
$mock = Mockery::mock('MockDependency\Type\StripeBilling'); //要寫完整路徑
$mock->shouldReceive('charge')->once()->andReturn('mocked Billing by Test');
$purchase = new PurcharseController($mock);
$result = $purchase->buy();
var_dump($result);

  • 依賴注入
    • 如果有定義新的namespace,記得先用composer dump,autoload才找的到檔案
    • 如果程式有在classmap中定義,require的時候就不用寫__DIR__取路徑
    • Single Responsibility
  • 驗證controller的時候,記得加上return返回結果
    • mock andReturn最後不會返回一個值,不需要再用一個變數來接
    • php指令+檔案位置來確認是否頁面能正常執行
1
2
3
4
5
6
7
// 方法1:未使用依賴注入的寫法
require 'vendor/autoload.php';
$controller = new PurcharseController();
var_dump($controller->buy());
// 方法2:使用依賴注入
require 'vendor/autoload.php';
var_dump((new PurcharseController(new StripeBilling))->buy());

Using Composer Learning Unit Test

PHPUnit Test卡關了

  • 定義了User的Model還是找不到?
    • 用Classmap的寫法來找到對應的model
  • 定義classmap
    • composer install:產生compsoer.json
    • composer dump-autoload
      • 每次有新的class的時候記得要用dump-autoload一次
      • 定義好namespace的時候也要使用
      • 用找歌曲的概念來解釋,一開始只有說要找甚麼歌,但是沒有說要找哪個歌手的甚麼專輯
    • alias p=./vendor/bin/phpunit
  • 盡量把要測試的方法名稱寫清楚一點
    • 寫的長沒有關係,要有描述性
    • Annotation可以達到
      • 把資料抽離出來
      • 處理例外狀況
  • 踩到exception的坑了
    • 如果傳入錯誤的參數進去,拋出例外的時候要用多行註解,格式錯誤就無法正確的判斷拋出的例外類型
  • alias 是一個好東西,可以打很少的指令達到一樣的效果
    • alias p=./vendor/bin/phpunit
    • alias hexoServer=‘hexo server --port $PORT --host $IP’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"require": {
"phpunit/phpunit": "^7.4",
"codeception/codeception": "^2.5"
},

"autoload": {
"classmap" : [
"App/Models",
"App/Libraries"
],
"psr-4": {
"App\\": "app"
}
}
}

Testing System Functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// 用namespace把Foo用到的time重新定義過
// 注意:如果使用use的話會有重複定義的問題
namespace App\Libraries;
function time() {
return 'stud';
}

class FooTest extends \PHPUnit\Framework\TestCase {
public function setUp() {

}

public function testDynamicFooTime() {
$this->assertEquals('stud', (new Foo)->go());
}
}

參考教學:
Testing System Functions
Composer, Autoloading, Namespacing, and PHPUnit
Phpunit Annotations
Linux Alias