您好,登錄后才能下訂單哦!
小編給大家分享一下如何為Bash腳本寫單元測試,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
為什么要為 Bash 腳本寫單元測試?
因為 Bash 腳本通常都是在執行一些與操作系統有關的操作,可能會對運行環境造成一些不可逆的操作,比如修改或者刪除文件、升級系統中的軟件包等。
所以為了確保 Bash 腳本的安全可靠,在生產環境中部署之前一定需要做好足夠的測試以確保其行為符合我們的預期。
如何能夠安全可靠的去測試 Bash 腳本呢?有人可能會說我們可以用 Docker 容器。是的,這樣做即安全又方便。在容器隔離出來的環境中不用擔心腳本會破壞我們的系統,而且也能非常簡單的快速重建出一個可用的測試環境。不過呢,請考慮以下的幾個常見的場景:
場景一:在執行 Bash 腳本測試前,我們需要需要事先安裝好所有在 Bash 腳本中會用到的第三方工具,否則這些測試將會因為命令找不到而執行失敗。例如,我們在腳本中使用了 Bazel 這個構建工具。我們必須提前安裝并配置好 Bazel,而且不要忘記為了能夠正常使用 Bazel 還得需要一個支持使用 Bazel 構建的工程。
場景二:測試結果的穩定性可能取決于腳本中訪問的第三方服務的穩定性。比如,我們在腳本中使用 curl 命令從一個網絡服務中獲取數據,但這個服務有時候可能會訪問失敗。有可能是因為網絡不穩定導致的,也可能是因為這個服務本身不穩定。再或者如果我們需要第三方服務返回不同的數據以便測試腳本的不同分支邏輯,但我們可能很難去修改這個第三方服務的數據。
場景三:Bash 腳本的測試用例的執行時間取決于腳本中使用的命令的執行時間。例如,如果我們中腳本中使用了 Gradle 來構建一個工程,由于不同的工程大小 Gradle 的一個構建可能要執行3分鐘或者3個小時。這還只是一個測試用例,如果我們還有20個或者100個測試用例呢?我們是否還能在幾秒內獲得測試報告呢?
即使使用了容器來執行 Bash 腳本測試,也一樣無法避免上面的幾個問題。環境的準備過程可能會隨著測試用例的增多而變的繁瑣,測試用例的穩定性和執行時長取決于第三方命令和服務的穩定性和執行時長,還可能很難做到使用不同數據來覆蓋不同的測試場景。
對于測試 Bash 腳本來說,我們真正要驗證的是 Bash 腳本的執行邏輯。比如在 Bash 腳本中可能會根據傳入的參數來組合出內部所調用的命令的選項和參數,我們要驗證的是這些選項和參數確實如我們預期的。至于調用的命令在接受了這些選項和參數后由于什么原因而失敗,可能我們并不關心這所有的可能原因。
因為這會有更多的外部影響因素,比如硬件和網絡都是否工作正常、第三方服務是否正常運行、構建工程所需的編譯器是否安裝并配置妥當、授權和認證信息是否都有效、等等。但對于 Bash 腳本來說,這些外部原因導致的結果就是所調用的命令執行成功或者失敗了。所以 Bash 腳本只要關注的是腳本中調用的命令是否能夠成功執行,以及命令輸出了哪些,并決定隨后執行腳本中的哪些不同分支邏輯。?
如果說我們就是想知道這個命令搭配上這些選項參數是否能按我們預期的那樣工作呢?很簡單,那就單獨在命令行里面去執行一下。如果在命令行中也不能按預期的工作,放到 Bash 腳本里面也一樣不會按預期的工作。這種錯誤和 Bash 腳本幾乎沒什么關系了。所以,為了盡量去除影響 Bash 腳本驗證的那些外部因素,我們應該考慮為 Bash 腳本編寫單元測試,以關注在 Bash 腳本的執行邏輯上。
什么樣的測試才是 Bash 腳本的單元測試?
首先,所有存在于 PATH 環境變量的路徑中的命令都不應該在單元測試中被執行。對 Bash 腳本來說,被調用的這些命令可以正常運行,有返回值,有輸出。但腳本中調用的這些命令都是被模擬出來的,用于模擬對應的真實命令的行為。這樣,我們在 Bash 腳本的單元測試中就避免了很大一部分的外部依賴,而且測試的執行速度也不會受到真實命令的影響了。其次,每個單元測試用例之間都應該是獨立的。這意味著,這些測試用例可以獨立執行或者被任意亂序執行,而不會影響驗證結果。最后,這些測試用例可以在不同的操作系統上執行,且都應該得到相同的驗證結果。比如 Bash 腳本中使用了只有 GNU/Linux 上才有的命令,對應的單元測試也可以在 Windows 或者 macOS 上執行,且結果一致。
怎樣為 Bash 腳本寫單元測試?
與其他編程語言一樣,Bash 也有多個測試框架,比如 Bats、Shunit2 等,但這些框架實際上并不能隔離所有 PATH 環境變量中的命令。有一個名為 Bach Testing Framework 的測試框架是目前唯一一個可以為 Bash 腳本編寫真正的單元測試的框架。Bach Testing Framework 的最獨特的特性就是默認不會執行任何位于 PATH 環境變量中的命令,因此 Bach Testing Framework 非常適用于驗證 Bash 腳本的執行邏輯。并且還帶來了以下好處:
簡單
什么也不用安裝。我們就可以執行這些測試。比如可以在一個全新的環境中執行一個調用了大量第三方命令的 Bash 腳本。
快
因為所有的命令都不會被真正執行,所以每一個測試用例的執行都非常快。
安全
因為不會執行任何外部的命令,所以即使因為 Bash 腳本中的某些錯誤導致執行了一個危險的命令,比如 rm -rf *。Bach 會保證這些危險命令不會被執行。
與運行環境無關
可以在 Windows 上去執行只能工作在 GNU/Linux 上的腳本的測試。
由于操作系統和 Bash 的一些限制,Bach Testing Framework 無法做到:
攔截使用絕對路徑調用的命令
事實上我們應該避免在 Bash 腳本中使用絕對路徑,如果不可避免的要使用,我們可以把這個絕對路徑抽取為一個變量,或者放入到一個函數中,然后用 @mock API 去模擬這個函數。
攔截諸如 >、>>、<< 等等這樣的 I/O 重定向
是的,無法攔截 I/O 重定向。我們也同樣可以把這些重定向操作隔離到一個函數中,然后再模擬這個函數。
Bach Testing Framework 的使用
Bach Testing Framework 需要 Bash v4.3 或更高版本。在 GNU/Linux 上還需要 Coreutils 和 Diffutils,在常用的發行版中都已經默認安裝好了。Bach 在 Linux/macOS/Cygwin/Git Bash/FreeBSD 等操作系統或者運行環境中驗證通過。
Bash v4.3+
Coreutils (GNU/Linux)
Diffutils (GNU/Linux)
安裝 Bach Testing Framework
Bach Testing Framework 的安裝很簡單,只需要下載 https://github.com/bach-sh/bach/raw/master/bach.sh 到你的項目中,在測試腳本中用 source 命令導入 Bach Testing Framework 的 bach.sh 即可。
比如:
source path/to/bach.sh
一個簡單的例子
與其它的測試框架不同,Bach Testing Framework 的每一個測試用例都是由兩個 Bash 函數組成,一個是以test- 開頭的測試執行函數,另一個是同名的以-assert 結尾的測試驗證函數。比如在下面的例子中,有兩個測試用例,分別是
– test-rm-rf – test-rm-your-dot-git
一個完整的測試用例:
#!/usr/bin/env bash set -euo pipefail source bach.sh # 導入 Bach Testing Framework test-rm-rf() { # Bach 的標準測試用例是由兩個方法組成 # - test-rm-rf # - test-rm-rf-assert # 這個方法 `test-rm-rf` 是測試用例的執行 project_log_path=/tmp/project/logs sudo rm -rf "$project_log_ptah/" # 注意,這里有個筆誤!} test-rm-rf-assert() { # 這個方法 `test-rm-rf-assert` 是測試用例的驗證 sudo rm -rf / # 這就是真實的將會執行的命令 # 不要慌!使用 Bach 測試框架不會讓這個命令真的執行!} test-rm-your-dot-git() { # 模擬 `find` 命令來查找你的主目錄下的所有 `.git` 目錄,假設會找到兩個目錄 @mock find ~ -type d -name .git === @stdout ~/src/your-awesome-project/.git \ ~/src/code/.git # 開始執行!刪除你的主目錄下的所有 `.git` 目錄!find ~ -type d -name .git | xargs -- rm -rf } test-rm-your-dot-git-assert() { # 驗證在 `test-rm-your-dot-git` 這個測試執行方法中最終是否會執行以下這個命令。rm -rf ~/src/your-awesome-project/.git ~/src/code/.git }
Bach 會分別運行每一個測試用例的兩個方法,去驗證兩個方法中執行的命令及其參數是否是一致的。
比如,第一個方法 test-rm-rf 是 Bach 的測試用例的執行,與之對應的測試驗證方法就是 test-rm-rf-assert這個方法。
在第二個測試用例 test-rm-your-dot-git中使用了 @mockAPI 來模擬了命令find ~ type d -name .git的行為,這個命令用來找出用戶目錄下的所有 .git 目錄。模擬之后,這個命令并不會真的執行,而是利用了 @stdout API 在標準終端上輸出了兩個虛擬的目錄名。
然后我們就可以執行真正的命令了,將 find命令的輸出結果傳遞給xargs 命令,并組合到 rm -rf 命令之后。
在對應的測試驗證函數 test-rm-your-dot-git-assert 里面就驗證是 find ~ -type d -name .git | xargs -- rm -rf的運行結果是否等同于命令rm -rf ~/src/your-awesome-project/.git~/src/code/.git
@mock 是 Bach Testing Framework 中很重要的一個 API,利用這個 API 我們就可以模擬 Bash 腳本中所使用的任意命令的行為或者輸出。
比如
@mock curl --silent google.com === \ @stdout "baidu.com"
模擬了命令 curl --silent google.com的執行結果是輸出 baidu.com。在真實的正常場景下,我們是無法做到訪問google.com得到的是 baidu.com。這樣模擬之后就可以用來驗證 Bash 腳本中處理一個命令不同響應時的行為了。@mockAPI 甚至還支持更復雜的行為模擬,我們可以自定義一個復雜的模擬邏輯,比如:
@mock ls <<\CMD if [[ "$var" -eq 1 ]]; then @stdout one else @stdout others fi CMD
在這個模擬中,會根據變量$var的值來決定命令ls的輸出one還是 others。用 @mock API 模擬的命令在任何時候執行的時候都是同樣的行為。但如果要模擬同一個命令重復執行的時候要返回不同的值,Bach Testing Framework 還提供了一個 @@mock 這個 API,比如:
@@mock uuid === @stdout aaaa-1111-2222 @@mock uuid === @stdout bbbb-3333-4444 @@mock uuid === @stdout cccc-5555-6666
這三個模擬命令模擬了 uuid 在重復執行三次的時候都返回不同的結果,按照模擬的先后順序分別輸出對應的模擬輸出。如果在執行完所有的模擬輸出后,再重復執行將會始終輸出最后一個模擬的輸出。更詳細的 API 介紹請在 Bach Testing Framework 的官網 https://bach.sh 查看。使用 Bach Testing Framework 還可以讓我們更安全方便的練習 Bash 編程。
比如,我們希望實現一個函數 cleanup 用來刪除參數上指定的文件。一個實現可能是:
function cleanup() { rm $1 }
這個函數的實現其實是有安全問題的,因為對于 Bash 來說,有沒有把一個變量用雙引號包含起來是非常重要的。在這個實現中,變量 $1 就沒有用雙引號,這會帶來嚴重的后果。下面我們將使用 @touch API 來創建幾個文件,其中將有一個文件名中含有特殊字符的文件bar。
我們都知道,對于含有特殊字符的文件名是要放入到雙引號中的。現在這個這個 cleanup 的實現里面沒有使用雙引號,但是傳參的時候使用了雙引號,那是否還會按照我們的預期來執行呢?
function cleanup() { rm -rf $1 } test-learn-bash-no-double-quote-star() { # 創建了三個文件,其中有一個名為 "bar*" 的文件 @touch bar1 bar2 bar3 "bar*" # 要刪除這個錯誤的文件名 bar*,而不刪除其他文件,使用了雙引號來傳參,這是正確的 cleanup "bar*" } test-learn-bash-no-double-quote-star-assert() { rm -rf "bar*" }
這個測試用例將會失敗,從驗證結果中我們可以看到,期望只刪除文件 bar,但是在函數 cleanup 里面,因為遺漏了雙引號,會導致變量被二次展開。實際執行的命令是 rm -rf "bar*" bar1 bar2 bar3。現在修復函數 cleanup,把變量 $1 放入雙引號:
function cleanup() { rm -rf "$1" }
再次執行測試,會發現確實執行的是命令 rm -rf "bar*"。
Bach Testing Framework 目前已經在寶馬集團和華為內部使用了。在寶馬集團的一個有數千人規模的大型項目里,Bach Testing Framework 保證了數個非常重要的構建腳本的維護。
這些腳本的可靠性和穩定性決定了數千人團隊的工作效率,現在就可以在本地快速驗證這些構建腳本的執行邏輯,也避免了在本地很難復現一些構建集群中的特殊場景的問題。
以上是“如何為Bash腳本寫單元測試”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。