[Linux] 用 shunit2 來做 shell script 的 unit testing
老實說我以前是不寫 unit-testing 的,
反正要測試的程式碼就編譯好,直接在產品環境上面測試就行了,
不過當然這樣的缺點就是不容易測到所有的參數組合,
或是要測試一些錯誤處理的部分時,會比較麻煩,
可能就得改程式,或是用除錯器單步執行來改變執行的路徑…
不過最近在 python 的專案上已經很習慣用 pytest 這個 unit-testing framework,
尤其是 unit-testing 可以方便地測試某個函式各種輸入輸出,
這個特性非常的不錯~可以加快測試的速度~
因此今天在改一個 shell script 時,也很想用 unit-testing 來測一下某些函式~
找了一下,似乎 shunit2 是不少人推薦使用的,試用起來也蠻簡單~
1. 安裝 shunit2
到 shunit2 官網 就可以下載 shunit2,看是要直接抓 ZIP 檔,
還是用 git clone 下來都可以,像我是用 git clone:
git clone https://github.com/kward/shunit2.git
抓下來的 shunit2 目錄下有許多東西,主要的執行檔是在 shunit2/source/2.1/src/shunit2~
2. 撰寫第一個 unit-testing 程式
在 shunit2/source/2.1/examples 目錄下有幾個測試的範例可以參考,
下面直接拿我寫的 unit-testing 程式來看看吧~
因為我要測試的函式是寫在 common.sh,所以一開始先用 . common.sh 讀進來,
接著你可以有一個 setUp() 和 tearDown() 函式,
如果有定義的話,就會在跑每一個 testXXX() 函式前先跑 setUp(),跑完後跑 tearDown(),
因此可以在裡面做一些變數或資料的初始化~
如果是想在跑第一個 testXXX() 前做整體的初始化,
可以用 oneTimeSetUp/oneTimeTearDown:
#!/bin/sh UT_FOLDER="$(dirname "$0")" . "${UT_FOLDER}/../common.sh" oneTimeSetup() { PYTHON_BIN="python" }
接著,把測試碼寫在 testXXX() 函式裡面,只要名稱開頭是 test 就行了,
所以 testGetFolder() 或是 test_get_folder() 這兩種風格都行~
在測試碼裡用像 assertEquals 就能測試兩個值是否相等,
test_get_backup_folder() { # DST_FOLDER is not set unset DST_FOLDER assertEquals "$(get_backup_folder)" "" # DST_FOLDER is set with empty value DST_FOLDER="" assertEquals "$(get_backup_folder)" "" # DST_FOLDER is set with non-empty value DST_FOLDER=/var/log/dst assertEquals "$(get_backup_folder)" "${DST_FOLDER}/backup" } test_compare_version() { assertEquals "$(compare_version 1.10.1000 1.2.1001)" "older" assertEquals "$(compare_version 1.2.1000 1.2.1001)" "older" assertEquals "$(compare_version 1.2.1002 1.2.1001)" "newer" assertEquals "$(compare_version 1.3.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.0.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.2.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.3.1000 1.2.1001)" "newer" }
shunit2 提供了好幾種 assert 函式,
詳細的說明可以看一下 shunit2/source/2.1/doc/shunit2.txt,像是有
– assertEquals, assertNotEquals
– assertNull, assertNotNull
– assertTrue, assertFalse
意義應該都蠻直覺的,只有 assertNull 稍微讓人不確定 Null 指的是什麼,
不過其實就是看字串是否為空字串囉~
在這個 shell script 的最後面,要加上 . shunit2,
就能讀入 shunit2 並開始跑 unit-testing 囉:
# Load shunit2 to run unittesting . "${UT_FOLDER}/../3rd_party/shunit2/src/shunit2"
完整的測試程式如下:
#!/bin/sh UT_FOLDER="$(dirname "$0")" . "${UT_FOLDER}/../common.sh" oneTimeSetUp() { PYTHON_BIN="python" } test_get_backup_folder() { # DST_FOLDER is not set unset DST_FOLDER assertEquals "$(get_backup_folder)" "" # DST_FOLDER is set with empty value DST_FOLDER="" assertEquals "$(get_backup_folder)" "" # DST_FOLDER is set with non-empty value DST_FOLDER=/var/log/dst assertEquals "$(get_backup_folder)" "${DST_FOLDER}/backup" } test_compare_version() { assertEquals "$(compare_version 1.10.1000 1.2.1001)" "older" assertEquals "$(compare_version 1.2.1000 1.2.1001)" "older" assertEquals "$(compare_version 1.2.1002 1.2.1001)" "newer" assertEquals "$(compare_version 1.3.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.0.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.2.1000 1.2.1001)" "newer" assertEquals "$(compare_version 2.3.1000 1.2.1001)" "newer" } # Load shunit2 to run unittesting . "${UT_FOLDER}/../3rd_party/shunit2/src/shunit2"
3. 執行 unit-testing 程式
將剛才的 shell script 儲存起來,假設是存成 test_common.sh,
那麼只要執行 sh test_common.sh 就能開始跑 unit-testing 了:
testuser@localhost ~/src $ sh unittest/test_common.sh
test_get_backup_folder
test_compare_version
Ran 2 tests.
OK
如果 assert 不成立的話,就會列出錯誤:
testuser@localhost ~/src $ sh unittest/test_common.sh test_get_backup_folder test_compare_version ASSERT:expected:<newer> but was:<older> Ran 2 tests. FAILED (failures=1)
有了 shunit2 之後,shell script 也能輕鬆愉快的寫 unit-testing 了,
感覺真不錯呀~~^^/