跟主題無關的 murmur:來嘗試一種筆記方式,以「解決某個問題」或「達到某個目標」為主,記錄中間的試驗跟操作等過程。因為是過程記錄,路途中可能歪掉(?)、出現好像有關但最後跟解決方式無關的東西。


目標

目標是 Win10 下的 PhpStorm 可以用不同版本 PHP 執行程式。

一個方式是裝多個版本的 PHP (覺得把環境弄得很亂不蘇胡),既然 PhpStorm 的 CLI Interpreter 可以用 Docker,乾脆來玩一下 Docker~

雖然是在 Win10,但除了安裝 Docker 的版本不同,其他 Docker 操作基本在 Linux 應該是可以用的(畢竟這沒有牽涉到更細節的什麼 linux container、windows container 之類的,我也不確定那有沒有關係)。

Win10 Docker Installation

google it,裝完就忘了,大概是 Hyper-V 要開、Win10 要某個版本之後,然後去裝 Docker for Windows,可以參考這篇

Docker 的 Image & Container 極簡概念

  • image:類似 VM image 的東西,而且 image 可以一層層疊起來。
  • container:依據 image 開起來的 instance,container 的環境是互相隔離的。
Read more »

一般 #define macro 不會將 parameter 展開成字串,只會把 parameter 放到對應位置,例如:

php
1
2
<?php
echo "Hello world";
1
2
3
4
5
6
#define ECHO(str) printf("%s\n", str)
int main() {
char s[] = "hello";
ECHO(s);
return 0;
}

經過 preprocess:

1
2
3
4
5
6
7
$ cpp stringify.c

int main() {
char s[] = "hello";
printf("%s\n", s);
return 0;
}

如果在 parameter 前加 #,preprocessor 會把 actual argument 變成字串,稱為 Stringizing。用個例子說明:

1
2
3
4
5
6
#define ECHO(str) printf("%s\n", #str)
int main() {
char s[] = "hello";
ECHO(s);
return 0;
}

經過 preprocess(只是例子,code 本身不太 make sense):

1
2
3
4
5
int main() {
char s[] = "hello";
printf("%s\n", "s");
return 0;
}

Linux kernel 使用這個技巧將 macro 展開成字串。

__stringify 定義在 include/linux/stringify.h

1
2
#define __stringify_1(x...)	#x
#define __stringify(x...) __stringify_1(x)

為什麼 __stringify 要 define 兩次呢?

Argument Prescan 提到 macro 的參數如果也是 macro,一般在被替換進 macro body 前就會被展開,但如果是 stringized 則不會被展開。

1
2
3
4
5
6
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
#define FOO bar

__stringify_1(FOO) // become "FOO"
__stringify(FOO) // become "bar"

這例子裡 __stringify_1(FOO) 因為是 stringized 的 macro 參數,所以 FOO 不會被展開,macro 替換後最後變成 "FOO"。而 __stringify(FOO) 會先展開 FOO 並替換,變成 __stringify_1(bar),接著再 scan 一次將 macro 展開為 "bar"

__stringify define 兩次是為了讓參數可以使用 macro。像上面的例子,通常期望 __stringify(FOO) 得到 "bar" 而非 "FOO"

... 是跟 function 一樣的不定參數,可參考 Variadic Macros

如何測試 event 裡提到 event 的測試有兩個方面,其中之一是測試 event 是否有被正確 trigger,有些 test framework 有檢驗 event 是否 trigger(或稱 emit)的驗證。(我應該是在某個 js test framework 看到的)

現在工作是用 Yii 這套 PHP framework,配合的 test framework 是 Codeception。

Yii2 有它的 Event 機制,看了下 Codeception 的 Yii2 module 沒有提供 event trigger 的驗證,上星期無聊就自己寫了個 codeception-yii2-event-tester 啦。

實作方面沒什麼困難,都在搞怎麼包 composer package 跟搞定 Travis。

Problem

Travis 在 run composer install 會出現類似以下錯誤:

1
2
3
4
5
Failed to clone the git@github.com:jquery/jquery-dist.git repository, try running in interactive mode so that you can enter your GitHub credentials

[Composer\Repository\InvalidRepositoryException]
No valid bower.json was found in any branch or tag of https://github.com/jq
uery/jquery-dist.git, could not load a package from it.

失敗的 repository 不一定,可能這次 jquery 下次別的。

找了一陣,說是踩到 Github 的 rate limit(我理解是 composer install 會一直從 github 抓東西所以容易踩到),但又有文章說這個問題已經被修正。我還是踩到了啊

Solution

  1. 在 Github 產生 Personal access token
  2. 在 Travis 設定有使用 Travis 的 project 的環境變數(environment variable),指定變數名稱,值是剛剛在 Github 產生的 access token。
  3. 修改 .travis.yml,在 composer install 前加入 composer config github-oauth.github.com ${環境變數名稱}

Ref

測試 event 分成兩邊:

  • 測試 event listener:有沒有在 event 發生後做該做的事。
  • 測試 trigger event:有沒有正確 trigger event。

基本概念是驗證一方時假造另一方。

測試 event listener

檢查 event 有沒有註冊到 event listener。用「event 發生後 event listener 有沒有做該做的事」來驗證,而不是試圖 access class 的內部 event 資訊來看是否有註冊成功。例如發生 event 後系統某些狀態會改變,測試方式是在測試裡 trigger event(假造 trigger event),驗證系統狀態是否有正確改變。

如果發生 event 後會 call 某些 third party function,測試方式是先做個 mock object、inject mock object 到被測試 class,接著 trigger event,最後驗證 mock object 是否有被 call 到該 call 的 function。

測試是否有 trigger event

在測試中給要測試的 event 註冊一個假的 event listener,接著讓被測試 class 做應該要 trigger event 的事情,最後驗證假 event listener 有沒有被 call 到。

驗證假 event listener 有沒有被 call 到不一定要用 mock object,也可以是假 event listener 在被 call 到時修改 test 裡的變數,最後直接驗證該變數,這跟語言支援有關。

test framework 支援

有些 test framework 的支援「某個 event 是否有被 trigger 或 emit 的驗證」。

Download kernel source code

我用的 kernel source code 是從 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git clone 的,寫這篇的版本是 5.0.0-rc3

1
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git linux-stable

Add system call

首先在 kernel source code root 建立一個資料夾 mysyscall/

1
2
$ cd linux-stable
$ mkdir mysyscall

新增 mysyscall/hello.c、加入新的 system call sys_hello()(不可免俗的來 hello world 一下):

mysyscall/hello.c
1
2
3
4
5
6
#include <linux/kernel.h>

asmlinkage long sys_hello(void) {
printk("Hello Kernel World!\n");
return 0;
}
Read more »

Extract and Override 是另一種 injection 方式,它幾乎不會改變程式的語意(增加 constructor 的參數或者其他 public 介面等等),寫起來乾淨漂亮。它適合用於模擬回傳值或回傳 stub 或 mock object,不適合用在確認被測試程式與 dependency object 的互動。

Override factory method to inject stub

  1. 在被測試 class 加入可被繼承並 override 的 factory method 來取得 dependency object。
  2. 在測試裡新增一個 class 繼承被測試 class,override 該 factory method 回傳 stub object,接著使用測試 class 進行測試。
Read more »

接下來要 inject dependency object 啦~

Constructor & Setter Injection

Constructor Injection

在被測試 class 加新的 constructor 或在原本的 constructor 加新參數,傳進剛抽出來的 interface 的 object,將它存在被測試 class 的 member,被測試 class 裡的程式邏輯使用這個 member 做事。

Read more »

External Dependency 是系統中與被測試程式互動但你無法控制的物件。被測試程式受到 external dependency 行為影響可能有不同結果,為了保持 unit test 穩定,不會一下結果該是 A、一下結果該是 B,我們希望能掌控 external dependency──藉由 stub object 模擬 external dependency 的行為並將其 inject 到被測試程式中,基本步驟如下:

  1. 抽出 external dependency object 的 interface
  2. stub implement 該 interface 並實作 function
  3. inject stub 到被測試程式
    • Constructor Injection
    • Setter Injection
    • Extract and Override

本篇用例子說明步驟 1 跟 2:MyClass 是被測試程式,Foo 是 external dependency。(為了讓 code 短一點直接在 header 實作)

Read more »

External Dependency 是系統中與被測試程式互動但你無法掌控的物件。互動就是有 call 啊、使用回傳值之類的。

stub 是在系統中產生一個可以控制的替代 object 來取代 external dependency object。

使用 stub 可以解決直接相依帶來的測試問題:無法控制相依物件的行為及回傳值(例如每次 call third party API 得到的結果不同)或者相依物件不穩定,而難以有穩定的環境(固定的 input 及 output)測試要測試的程式邏輯。

一種典型的 stub 是回傳假資料,藉由假造不同的回傳值來測試程式在不同情境下的運作,例如假造其他 function 的各種可能的回傳值。