簡介: Sharness 是一個用 Shell 腳原本編寫測試用例的測試框架。本文將詳細介紹 Sharness 的結構及測試用例的編寫格式,以及語法規範和技巧,教你們如何使用 Sharness 編寫測試用例,同時還分享了 Sharness 的擴展功能和項目實戰。html
參與過 Git 項目的測試用例開發,爲其測試框架的簡潔、高效而折服。曾經嘗試將 Git 測試用例用於其餘項目:《複用 git.git 測試框架》[1]。不過從 Git 項目中剝離測試用例框架仍是挺費事的。git
一次偶然的機會發現已經有人(Christian Couder:Gitlab 工程師,Git項目的領導委員會成員之一)已經將 Git 的測試用例框架剝離出來, 成爲獨立的開源項目 Sharness。github
有了 Sharness,寫測試用例再也不是苦差事。shell
若是要在測試用例中建立/初始化一個文件(內容爲 「Hello, world.」), 看看 sharness 實現起來有多麼簡單:編程
cat >expect <<-EOF Hello, world. EOF
若是要對某應用(hello-world)的輸出和預期的 expect 文件進行比較, 相同則測試用例經過,不一樣則展現差別。測試用例編寫以下:app
test_expect_success 「app output test」 ‘ cat >expect <<-EOF && Hello, world. EOF hello-world >actual && test_cmp expect actual ‘
每一個測試用例腳本能夠單獨執行。使用 -v 參數,能夠顯示詳細輸出。使用 -d 參數,運行結束後保留用例的臨時目錄。框架
能夠在要調試的test case後面增長 test_pause 語句,例如:less
test_expect_success 「name」 ‘ <Script…> ‘ test_pause test_done
而後使用 -v 參數運行該腳本,會在 test_pause 語句處中斷,進入一個包含 sharness 環境變量的子 Shell 中,目錄會切換到測試用例單獨的工做區。調試完畢退出 Shell 即返回。ide
Sharness 源自於 Git 項目的測試用例框架。咱們先來看看 Git 項目測試框架的結構。函數
相關代碼參見[2]。
以以下測試腳本爲例[3]:
(1)在文件頭,定義 test_description 變量,提供測試用例的簡單說明,一般使用一行文本。本測試用例較爲複雜,使用了多行文本進行描述。
#!/bin/sh # # Copyright (c) 2018 Jiang Xin # test_description='Test git pack-redundant In order to test git-pack-redundant, we will create a number of objects and packs in the repository `master.git`. The relationship between packs (P1-P8) and objects (T, A-R) is showed in the following chart. Objects of a pack will be marked with letter x, while objects of redundant packs will be marked with exclamation point, and redundant pack itself will be marked with asterisk. | T A B C D E F G H I J K L M N O P Q R ----+-------------------------------------- P1 | x x x x x x x x P2* | ! ! ! ! ! ! ! P3 | x x x x x x P4* | ! ! ! ! ! P5 | x x x x P6* | ! ! ! P7 | x x P8* | ! ----+-------------------------------------- ALL | x x x x x x x x x x x x x x x x x x x Another repository `shared.git` has unique objects (X-Z), while other objects (marked with letter s) are shared through alt-odb (of `master.git`). The relationship between packs and objects is as follows: | T A B C D E F G H I J K L M N O P Q R X Y Z ----+---------------------------------------------- Px1 | s s s x x x Px2 | s s s x x x '
(2)包含測試框架代碼。
. ./test-lib.sh
(3)定義全局變量,以及定義要在測試用例中用到的函數封裝。
master_repo=master.git shared_repo=shared.git # Create commits in <repo> and assign each commit's oid to shell variables # given in the arguments (A, B, and C). E.g.: # # create_commits_in <repo> A B C # # NOTE: Avoid calling this function from a subshell since variable # assignments will disappear when subshell exits. create_commits_in () { repo="$1" && if ! parent=$(git -C "$repo" rev-parse HEAD^{} 2>/dev/null) then ... ...
(4)用 test_expect_success 等方法撰寫測試用例。
test_expect_success 'setup master repo' ' git init --bare "$master_repo" && create_commits_in "$master_repo" A B C D E F G H I J K L M N O P Q R ' ############################################################################# # Chart of packs and objects for this test case # # | T A B C D E F G H I J K L M N O P Q R # ----+-------------------------------------- # P1 | x x x x x x x x # P2 | x x x x x x x # P3 | x x x x x x # ----+-------------------------------------- # ALL | x x x x x x x x x x x x x x x # ############################################################################# test_expect_success 'master: no redundant for pack 1, 2, 3' ' create_pack_in "$master_repo" P1 <<-EOF && $T $A $B $C $D $E $F $R EOF create_pack_in "$master_repo" P2 <<-EOF && $B $C $D $E $G $H $I EOF create_pack_in "$master_repo" P3 <<-EOF && $F $I $J $K $L $M EOF ( cd "$master_repo" && git pack-redundant --all >out && test_must_be_empty out ) '
(5)在腳本的結尾,用 test_done 方法結束測試用例。
test_done
Sharness 項目由 Git 項目的測試框架抽象而來,項目地址:
https://github.com/chriscool/sharness
咱們對 Sharness 測試框架作了一些小改動:
以以下測試腳本爲例[4]:
(1)在文件頭,定義 test_description 變量,提供測試用例的簡單說明,一般使用一行文本。
#!/bin/sh test_description="git-repo init"
(2)包含測試框架代碼。
. ./lib/sharness.sh
(3)定義全局變量,以及定義要在測試用例中用到的函數封裝。
# Create manifest repositories manifest_url="file://${REPO_TEST_REPOSITORIES}/hello/manifests"
(4)用 test_expect_success 等方法撰寫測試用例。
test_expect_success "setup" ' # create .repo file as a barrier, not find .repo deeper touch .repo && mkdir work ' test_expect_success "git-repo init -u" ' ( cd work && git-repo init -u $manifest_url ) ' test_expect_success "manifest points to default.xml" ' ( cd work && test -f .repo/manifest.xml && echo manifests/default.xml >expect && readlink .repo/manifest.xml >actual && test_cmp expect actual ) '
(5)在腳本的結尾,用 test_done 方法結束測試用例。
test_done
test_expect_success 能夠有兩個參數或者三個參數。
當 test_expect_success 方法後面是兩個參數時,第一個參數用於描述測試用例, 第二個參數是測試用例要執行的命令。
test_expect_success 'initial checksum' ' ( cd bare.git && git checksum --init && test -f info/checksum && test -f info/checksum.log ) && cat >expect <<-EOF && INFO[<time>]: initialize checksum EOF cat bare.git/info/checksum.log | sed -e "s/\[.*\]/[<time>]/" >actual && test_cmp expect actual '
當 test_expect_success 方法後面是三個參數時,第一個參數是前置條件, 第二個參數用於描述測試用例, 第三個參數是測試用例要執行的命令。
例如以下有三個參數的測試,第一個參數定義了前置條件,在 CYGWIN 等環境, 不執行測試用例。
test_expect_success !MINGW,!CYGWIN \ ’correct handling of backslashes' ' rm -rf whitespace && mkdir whitespace && >"whitespace/trailing 1 " && >"whitespace/trailing 2 \\\\" && >"whitespace/trailing 3 \\\\" && >"whitespace/trailing 4 \\ " && >"whitespace/trailing 5 \\ \\ " && >"whitespace/trailing 6 \\a\\" && >whitespace/untracked && sed -e "s/Z$//" >ignore <<-\EOF && whitespace/trailing 1 \ Z whitespace/trailing 2 \\\\Z whitespace/trailing 3 \\\\ Z whitespace/trailing 4 \\\ Z whitespace/trailing 5 \\ \\\ Z whitespace/trailing 6 \\a\\Z EOF echo whitespace/untracked >expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && test_must_be_empty err '
test_expect_success 'shared: create new objects and packs' ' create_commits_in "$shared_repo" X Y Z && create_pack_in "$shared_repo" Px1 <<-EOF && $X $Y $Z $A $B $C EOF create_pack_in "$shared_repo" Px2 <<-EOF $X $Y $Z $D $E $F EOF '
create_pack_in () { repo="$1" && name="$2" && pack=$(git -C "$repo/objects/pack" pack-objects -q pack) && eval $name=$pack && eval P$pack=$name:$pack }
test_expect_success 'master: one of pack-2/pack-3 is redundant' ' create_pack_in "$master_repo" P4 <<-EOF && $J $K $L $M $P EOF create_pack_in "$master_repo" P5 <<-EOF && $G $H $N $O EOF ( cd "$master_repo" && cat >expect <<-EOF && P3:$P3 EOF git pack-redundant --all >out && format_packfiles <out >actual && test_cmp expect actual ) '
以下內容是 Junio 在代碼評審時,對測試用例中定義的 format_git_output 方法的評審意見。Junio 指出要在給函數命名時,要使用更有意義的名稱。
> +format_git_output () { Unless this helper is able to take any git output and massage, please describe what kind of git output it is meant to handle. Also, "format" does not tell anything to the readers why it wants to transform its input or what its output is supposed to look like. It does not help readers and future developers.
使用 heredoc 建立文本文件,若是其中的腳本要定義和使用變量,要對變量中的 $ 字符進行轉移。Junio 給出了一個 heredoc 語法的小技巧,能夠無需對 $ 字符轉義。
> + > + # setup pre-receive hook > + cat >upstream/hooks/pre-receive <<-EOF && Wouldn't it make it easier to read the resulting text if you quoted the end-of-here-text marker here, i.e. "<<\-EOF"? That way, you can lose backslash before $old, $new and $ref. > + #!/bin/sh > + > + printf >&2 "# pre-receive hook\n" > + > + while read old new ref > + do > + printf >&2 "pre-receive< \$old \$new \$ref\n" > + done > + EOF
Git 項目對於 Shell 編寫的測試用例制定了語法規範,例如:
command
。完整語法規範參考[5]。
開始一個測試用例。
標記爲存在已知問題,執行失敗不報錯,執行成功則警告該 broken 的用例已經 fixed。
後面的一條命令必須失敗。若是後面命令成功,測試失敗。
命令以給定返回值結束。
比較兩個文件內容,相同成功,不一樣失敗並顯示差別。
參數必須是一個文件,且存在。
參數必須是一個目錄,且存在。
參數指向的文件內容必須爲空。
跨平臺的 seq,用戶生成數字序列。
測試暫停,進入子 Shell。
測試用例結束。
Sharness 提供了擴展功能。用戶在 sharness.d 目錄中添加以 .sh 結尾的腳本文件,便可對 Sharness 進行擴展。例如:
例如:測試腳本在工做區下建立了一個倉庫(git init my.repo),想要在該倉庫下執行 git clean,卻忘了進入到 my.repo 子目錄再執行,結果致使待測試項目中丟失文件。
如:test_tick 方法,能夠設置 GIT_AUTHOR_DATE、GIT_COMMITTER_DATE 等環境變量,確保測試腳本屢次運行時提交時間的一致性,進而產生一致的提交ID。
例如 git-repo 項目爲了不工做區逃逸,在 trash directory.* 目錄下建立 .repo 文件。
git-repo 是一個命令行工具,很是適合使用 sharness 測試框架編寫測試用例。參見[6]。
對於非命令行工具,或者爲了測試內置函數,須要先封裝一個或多個 fake app,再調用封裝的命令行工具進行測試。例如在爲 Git 項目開發 proc-receive 鉤子擴展時,先開發一個 fake app[7]。
以後再編寫測試,調用 fake app(test-tool proc-receive),幫助完成測試用例的開發。參見下列提交中的測試用例[8]。
還可使用一些 Shell 編程技巧,在多個測試文件中複用測試用例。例如以下測試用例在測試 HTTP 協議和本地協議時,複用了同一套測試用例(t5411目錄下的測試腳本)[9]。
相關連接
[1] https://www.worldhello.net/2013/10/26/test-gistore-using-git-test-framework.html
[2] https://sourcegraph.com/github.com/git/git@master/-/tree/t
[3] https://github.com/git/git/blob/master/t/t5323-pack-redundant.sh
[4] https://github.com/alibaba/git-repo-go/blob/master/test/t0100-init.sh
[5] https://github.com/git/git/blob/master/Documentation/CodingGuidelines
[6] https://github.com/alibaba/git-repo-go
[7] https://github.com/jiangxin/git/blob/jx/proc-receive-hook/t/helper/test-proc-receive.c
[8] https://github.com/jiangxin/git/commit/9654f5eda1153634ab09ca5c6e490bcabdd57e61
[9] https://github.com/jiangxin/git/blob/jx/proc-receive-hook/t/t5411-proc-receive-hook.sh