溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何為Bash腳本寫單元測(cè)試

發(fā)布時(shí)間:2021-11-08 10:38:50 來源:億速云 閱讀:130 作者:小新 欄目:系統(tǒng)運(yùn)維

小編給大家分享一下如何為Bash腳本寫單元測(cè)試,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

為什么要為 Bash 腳本寫單元測(cè)試?

因?yàn)?Bash 腳本通常都是在執(zhí)行一些與操作系統(tǒng)有關(guān)的操作,可能會(huì)對(duì)運(yùn)行環(huán)境造成一些不可逆的操作,比如修改或者刪除文件、升級(jí)系統(tǒng)中的軟件包等。

所以為了確保 Bash 腳本的安全可靠,在生產(chǎn)環(huán)境中部署之前一定需要做好足夠的測(cè)試以確保其行為符合我們的預(yù)期。

如何能夠安全可靠的去測(cè)試 Bash 腳本呢?有人可能會(huì)說我們可以用 Docker  容器。是的,這樣做即安全又方便。在容器隔離出來的環(huán)境中不用擔(dān)心腳本會(huì)破壞我們的系統(tǒng),而且也能非常簡(jiǎn)單的快速重建出一個(gè)可用的測(cè)試環(huán)境。不過呢,請(qǐng)考慮以下的幾個(gè)常見的場(chǎng)景:

  • 場(chǎng)景一:在執(zhí)行 Bash 腳本測(cè)試前,我們需要需要事先安裝好所有在 Bash  腳本中會(huì)用到的第三方工具,否則這些測(cè)試將會(huì)因?yàn)槊钫也坏蕉鴪?zhí)行失敗。例如,我們?cè)谀_本中使用了 Bazel 這個(gè)構(gòu)建工具。我們必須提前安裝并配置好  Bazel,而且不要忘記為了能夠正常使用 Bazel 還得需要一個(gè)支持使用 Bazel 構(gòu)建的工程。

  • 場(chǎng)景二:測(cè)試結(jié)果的穩(wěn)定性可能取決于腳本中訪問的第三方服務(wù)的穩(wěn)定性。比如,我們?cè)谀_本中使用 curl  命令從一個(gè)網(wǎng)絡(luò)服務(wù)中獲取數(shù)據(jù),但這個(gè)服務(wù)有時(shí)候可能會(huì)訪問失敗。有可能是因?yàn)榫W(wǎng)絡(luò)不穩(wěn)定導(dǎo)致的,也可能是因?yàn)檫@個(gè)服務(wù)本身不穩(wěn)定。再或者如果我們需要第三方服務(wù)返回不同的數(shù)據(jù)以便測(cè)試腳本的不同分支邏輯,但我們可能很難去修改這個(gè)第三方服務(wù)的數(shù)據(jù)。

  • 場(chǎng)景三:Bash 腳本的測(cè)試用例的執(zhí)行時(shí)間取決于腳本中使用的命令的執(zhí)行時(shí)間。例如,如果我們中腳本中使用了 Gradle 來構(gòu)建一個(gè)工程,由于不同的工程大小  Gradle  的一個(gè)構(gòu)建可能要執(zhí)行3分鐘或者3個(gè)小時(shí)。這還只是一個(gè)測(cè)試用例,如果我們還有20個(gè)或者100個(gè)測(cè)試用例呢?我們是否還能在幾秒內(nèi)獲得測(cè)試報(bào)告呢?

即使使用了容器來執(zhí)行 Bash  腳本測(cè)試,也一樣無法避免上面的幾個(gè)問題。環(huán)境的準(zhǔn)備過程可能會(huì)隨著測(cè)試用例的增多而變的繁瑣,測(cè)試用例的穩(wěn)定性和執(zhí)行時(shí)長(zhǎng)取決于第三方命令和服務(wù)的穩(wěn)定性和執(zhí)行時(shí)長(zhǎng),還可能很難做到使用不同數(shù)據(jù)來覆蓋不同的測(cè)試場(chǎng)景。

對(duì)于測(cè)試  Bash 腳本來說,我們真正要驗(yàn)證的是 Bash 腳本的執(zhí)行邏輯。比如在 Bash  腳本中可能會(huì)根據(jù)傳入的參數(shù)來組合出內(nèi)部所調(diào)用的命令的選項(xiàng)和參數(shù),我們要驗(yàn)證的是這些選項(xiàng)和參數(shù)確實(shí)如我們預(yù)期的。至于調(diào)用的命令在接受了這些選項(xiàng)和參數(shù)后由于什么原因而失敗,可能我們并不關(guān)心這所有的可能原因。

因?yàn)檫@會(huì)有更多的外部影響因素,比如硬件和網(wǎng)絡(luò)都是否工作正常、第三方服務(wù)是否正常運(yùn)行、構(gòu)建工程所需的編譯器是否安裝并配置妥當(dāng)、授權(quán)和認(rèn)證信息是否都有效、等等。但對(duì)于  Bash 腳本來說,這些外部原因?qū)е碌慕Y(jié)果就是所調(diào)用的命令執(zhí)行成功或者失敗了。所以 Bash  腳本只要關(guān)注的是腳本中調(diào)用的命令是否能夠成功執(zhí)行,以及命令輸出了哪些,并決定隨后執(zhí)行腳本中的哪些不同分支邏輯。?

如果說我們就是想知道這個(gè)命令搭配上這些選項(xiàng)參數(shù)是否能按我們預(yù)期的那樣工作呢?很簡(jiǎn)單,那就單獨(dú)在命令行里面去執(zhí)行一下。如果在命令行中也不能按預(yù)期的工作,放到  Bash 腳本里面也一樣不會(huì)按預(yù)期的工作。這種錯(cuò)誤和 Bash 腳本幾乎沒什么關(guān)系了。所以,為了盡量去除影響 Bash 腳本驗(yàn)證的那些外部因素,我們應(yīng)該考慮為  Bash 腳本編寫單元測(cè)試,以關(guān)注在 Bash 腳本的執(zhí)行邏輯上。

什么樣的測(cè)試才是 Bash 腳本的單元測(cè)試?

首先,所有存在于 PATH 環(huán)境變量的路徑中的命令都不應(yīng)該在單元測(cè)試中被執(zhí)行。對(duì) Bash  腳本來說,被調(diào)用的這些命令可以正常運(yùn)行,有返回值,有輸出。但腳本中調(diào)用的這些命令都是被模擬出來的,用于模擬對(duì)應(yīng)的真實(shí)命令的行為。這樣,我們?cè)?Bash  腳本的單元測(cè)試中就避免了很大一部分的外部依賴,而且測(cè)試的執(zhí)行速度也不會(huì)受到真實(shí)命令的影響了。其次,每個(gè)單元測(cè)試用例之間都應(yīng)該是獨(dú)立的。這意味著,這些測(cè)試用例可以獨(dú)立執(zhí)行或者被任意亂序執(zhí)行,而不會(huì)影響驗(yàn)證結(jié)果。最后,這些測(cè)試用例可以在不同的操作系統(tǒng)上執(zhí)行,且都應(yīng)該得到相同的驗(yàn)證結(jié)果。比如  Bash 腳本中使用了只有 GNU/Linux 上才有的命令,對(duì)應(yīng)的單元測(cè)試也可以在 Windows 或者 macOS 上執(zhí)行,且結(jié)果一致。

怎樣為 Bash 腳本寫單元測(cè)試?

與其他編程語言一樣,Bash 也有多個(gè)測(cè)試框架,比如 Bats、Shunit2 等,但這些框架實(shí)際上并不能隔離所有 PATH 環(huán)境變量中的命令。有一個(gè)名為  Bach Testing Framework 的測(cè)試框架是目前唯一一個(gè)可以為 Bash 腳本編寫真正的單元測(cè)試的框架。Bach Testing  Framework 的最獨(dú)特的特性就是默認(rèn)不會(huì)執(zhí)行任何位于 PATH 環(huán)境變量中的命令,因此 Bach Testing Framework 非常適用于驗(yàn)證  Bash 腳本的執(zhí)行邏輯。并且還帶來了以下好處:

  • 簡(jiǎn)單

什么也不用安裝。我們就可以執(zhí)行這些測(cè)試。比如可以在一個(gè)全新的環(huán)境中執(zhí)行一個(gè)調(diào)用了大量第三方命令的 Bash 腳本。

因?yàn)樗械拿疃疾粫?huì)被真正執(zhí)行,所以每一個(gè)測(cè)試用例的執(zhí)行都非常快。

  • 安全

因?yàn)椴粫?huì)執(zhí)行任何外部的命令,所以即使因?yàn)?Bash 腳本中的某些錯(cuò)誤導(dǎo)致執(zhí)行了一個(gè)危險(xiǎn)的命令,比如 rm -rf *。Bach  會(huì)保證這些危險(xiǎn)命令不會(huì)被執(zhí)行。

  • 與運(yùn)行環(huán)境無關(guān)

可以在 Windows 上去執(zhí)行只能工作在 GNU/Linux 上的腳本的測(cè)試。

由于操作系統(tǒng)和 Bash 的一些限制,Bach Testing Framework 無法做到:

  • 攔截使用絕對(duì)路徑調(diào)用的命令

事實(shí)上我們應(yīng)該避免在 Bash 腳本中使用絕對(duì)路徑,如果不可避免的要使用,我們可以把這個(gè)絕對(duì)路徑抽取為一個(gè)變量,或者放入到一個(gè)函數(shù)中,然后用 @mock  API 去模擬這個(gè)函數(shù)。

  • 攔截諸如 >、>>、<< 等等這樣的 I/O 重定向

是的,無法攔截 I/O 重定向。我們也同樣可以把這些重定向操作隔離到一個(gè)函數(shù)中,然后再模擬這個(gè)函數(shù)。

Bach Testing Framework 的使用

Bach Testing Framework 需要 Bash v4.3 或更高版本。在 GNU/Linux 上還需要 Coreutils 和  Diffutils,在常用的發(fā)行版中都已經(jīng)默認(rèn)安裝好了。Bach 在 Linux/macOS/Cygwin/Git Bash/FreeBSD  等操作系統(tǒng)或者運(yùn)行環(huán)境中驗(yàn)證通過。

  • Bash v4.3+

  • Coreutils (GNU/Linux)

  • Diffutils (GNU/Linux)

安裝 Bach Testing Framework

Bach Testing Framework 的安裝很簡(jiǎn)單,只需要下載  https://github.com/bach-sh/bach/raw/master/bach.sh 到你的項(xiàng)目中,在測(cè)試腳本中用 source 命令導(dǎo)入  Bach Testing Framework 的 bach.sh 即可。

比如:

source path/to/bach.sh

一個(gè)簡(jiǎn)單的例子

與其它的測(cè)試框架不同,Bach Testing Framework 的每一個(gè)測(cè)試用例都是由兩個(gè) Bash 函數(shù)組成,一個(gè)是以test-  開頭的測(cè)試執(zhí)行函數(shù),另一個(gè)是同名的以-assert 結(jié)尾的測(cè)試驗(yàn)證函數(shù)。比如在下面的例子中,有兩個(gè)測(cè)試用例,分別是

&ndash; test-rm-rf  &ndash; test-rm-your-dot-git

一個(gè)完整的測(cè)試用例:

#!/usr/bin/env bash set -euo pipefail  source bach.sh # 導(dǎo)入 Bach Testing Framework  test-rm-rf() {     # Bach 的標(biāo)準(zhǔn)測(cè)試用例是由兩個(gè)方法組成     #   - test-rm-rf     #   - test-rm-rf-assert     # 這個(gè)方法 `test-rm-rf` 是測(cè)試用例的執(zhí)行      project_log_path=/tmp/project/logs     sudo rm -rf "$project_log_ptah/" # 注意,這里有個(gè)筆誤!} test-rm-rf-assert() {     # 這個(gè)方法 `test-rm-rf-assert` 是測(cè)試用例的驗(yàn)證     sudo rm -rf /   # 這就是真實(shí)的將會(huì)執(zhí)行的命令                     # 不要慌!使用 Bach 測(cè)試框架不會(huì)讓這個(gè)命令真的執(zhí)行!}  test-rm-your-dot-git() {     # 模擬 `find` 命令來查找你的主目錄下的所有 `.git` 目錄,假設(shè)會(huì)找到兩個(gè)目錄      @mock find ~ -type d -name .git === @stdout ~/src/your-awesome-project/.git \                                                 ~/src/code/.git      # 開始執(zhí)行!刪除你的主目錄下的所有 `.git` 目錄!find ~ -type d -name .git | xargs -- rm -rf } test-rm-your-dot-git-assert() {     # 驗(yàn)證在 `test-rm-your-dot-git` 這個(gè)測(cè)試執(zhí)行方法中最終是否會(huì)執(zhí)行以下這個(gè)命令。rm -rf ~/src/your-awesome-project/.git ~/src/code/.git }

Bach 會(huì)分別運(yùn)行每一個(gè)測(cè)試用例的兩個(gè)方法,去驗(yàn)證兩個(gè)方法中執(zhí)行的命令及其參數(shù)是否是一致的。

比如,第一個(gè)方法 test-rm-rf 是 Bach  的測(cè)試用例的執(zhí)行,與之對(duì)應(yīng)的測(cè)試驗(yàn)證方法就是 test-rm-rf-assert這個(gè)方法。

在第二個(gè)測(cè)試用例 test-rm-your-dot-git中使用了  @mockAPI 來模擬了命令find ~ type d -name .git的行為,這個(gè)命令用來找出用戶目錄下的所有 .git  目錄。模擬之后,這個(gè)命令并不會(huì)真的執(zhí)行,而是利用了 @stdout API 在標(biāo)準(zhǔn)終端上輸出了兩個(gè)虛擬的目錄名。

然后我們就可以執(zhí)行真正的命令了,將  find命令的輸出結(jié)果傳遞給xargs 命令,并組合到 rm -rf 命令之后。

在對(duì)應(yīng)的測(cè)試驗(yàn)證函數(shù) test-rm-your-dot-git-assert  里面就驗(yàn)證是 find ~ -type d -name .git | xargs -- rm -rf的運(yùn)行結(jié)果是否等同于命令rm -rf  ~/src/your-awesome-project/.git~/src/code/.git

@mock 是 Bach Testing Framework  中很重要的一個(gè) API,利用這個(gè) API 我們就可以模擬 Bash 腳本中所使用的任意命令的行為或者輸出。

比如

@mock curl --silent google.com === \     @stdout "baidu.com"

模擬了命令 curl --silent google.com的執(zhí)行結(jié)果是輸出  baidu.com。在真實(shí)的正常場(chǎng)景下,我們是無法做到訪問google.com得到的是 baidu.com。這樣模擬之后就可以用來驗(yàn)證 Bash  腳本中處理一個(gè)命令不同響應(yīng)時(shí)的行為了。@mockAPI 甚至還支持更復(fù)雜的行為模擬,我們可以自定義一個(gè)復(fù)雜的模擬邏輯,比如:

@mock ls <<\CMD     if [[ "$var" -eq 1 ]]; then         @stdout one     else         @stdout others     fi CMD

在這個(gè)模擬中,會(huì)根據(jù)變量$var的值來決定命令ls的輸出one還是 others。用 @mock API  模擬的命令在任何時(shí)候執(zhí)行的時(shí)候都是同樣的行為。但如果要模擬同一個(gè)命令重復(fù)執(zhí)行的時(shí)候要返回不同的值,Bach Testing Framework 還提供了一個(gè)  @@mock 這個(gè) API,比如:

@@mock uuid === @stdout aaaa-1111-2222 @@mock uuid === @stdout bbbb-3333-4444 @@mock uuid === @stdout cccc-5555-6666

這三個(gè)模擬命令模擬了 uuid  在重復(fù)執(zhí)行三次的時(shí)候都返回不同的結(jié)果,按照模擬的先后順序分別輸出對(duì)應(yīng)的模擬輸出。如果在執(zhí)行完所有的模擬輸出后,再重復(fù)執(zhí)行將會(huì)始終輸出最后一個(gè)模擬的輸出。更詳細(xì)的  API 介紹請(qǐng)?jiān)?Bach Testing Framework 的官網(wǎng) https://bach.sh 查看。使用 Bach Testing Framework  還可以讓我們更安全方便的練習(xí) Bash 編程。

比如,我們希望實(shí)現(xiàn)一個(gè)函數(shù) cleanup 用來刪除參數(shù)上指定的文件。一個(gè)實(shí)現(xiàn)可能是:

function cleanup() {     rm $1 }

這個(gè)函數(shù)的實(shí)現(xiàn)其實(shí)是有安全問題的,因?yàn)閷?duì)于 Bash 來說,有沒有把一個(gè)變量用雙引號(hào)包含起來是非常重要的。在這個(gè)實(shí)現(xiàn)中,變量 $1  就沒有用雙引號(hào),這會(huì)帶來嚴(yán)重的后果。下面我們將使用 @touch API 來創(chuàng)建幾個(gè)文件,其中將有一個(gè)文件名中含有特殊字符的文件bar。

我們都知道,對(duì)于含有特殊字符的文件名是要放入到雙引號(hào)中的。現(xiàn)在這個(gè)這個(gè) cleanup  的實(shí)現(xiàn)里面沒有使用雙引號(hào),但是傳參的時(shí)候使用了雙引號(hào),那是否還會(huì)按照我們的預(yù)期來執(zhí)行呢?

function cleanup() {     rm -rf $1 }  test-learn-bash-no-double-quote-star() {     # 創(chuàng)建了三個(gè)文件,其中有一個(gè)名為 "bar*" 的文件     @touch bar1 bar2 bar3 "bar*"      # 要?jiǎng)h除這個(gè)錯(cuò)誤的文件名 bar*,而不刪除其他文件,使用了雙引號(hào)來傳參,這是正確的     cleanup "bar*" }  test-learn-bash-no-double-quote-star-assert() {     rm -rf "bar*" }

這個(gè)測(cè)試用例將會(huì)失敗,從驗(yàn)證結(jié)果中我們可以看到,期望只刪除文件 bar,但是在函數(shù) cleanup  里面,因?yàn)檫z漏了雙引號(hào),會(huì)導(dǎo)致變量被二次展開。實(shí)際執(zhí)行的命令是 rm -rf "bar*" bar1 bar2 bar3。現(xiàn)在修復(fù)函數(shù) cleanup,把變量  $1 放入雙引號(hào):

function cleanup() {     rm -rf "$1" }

再次執(zhí)行測(cè)試,會(huì)發(fā)現(xiàn)確實(shí)執(zhí)行的是命令 rm -rf "bar*"。

Bach Testing Framework 目前已經(jīng)在寶馬集團(tuán)和華為內(nèi)部使用了。在寶馬集團(tuán)的一個(gè)有數(shù)千人規(guī)模的大型項(xiàng)目里,Bach Testing  Framework 保證了數(shù)個(gè)非常重要的構(gòu)建腳本的維護(hù)。

這些腳本的可靠性和穩(wěn)定性決定了數(shù)千人團(tuán)隊(duì)的工作效率,現(xiàn)在就可以在本地快速驗(yàn)證這些構(gòu)建腳本的執(zhí)行邏輯,也避免了在本地很難復(fù)現(xiàn)一些構(gòu)建集群中的特殊場(chǎng)景的問題。

以上是“如何為Bash腳本寫單元測(cè)試”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI