测试用例难写?来试试Sharness

**愿你我相遇,皆有所获! 欢迎关注微信公众号:【伤心的辣条】 免费领取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!**

曾经尝试将Git测试用例用作其他项目:《替代git.git测试框架》 [1]。不过从Git项目中替换测试用例框架还是挺费事的。

一次偶然的机会发现已经有人(Christian Couder:Gitlab工程师,Git项目的领导委员会成员之一)已经将Git的测试用例框架替换出来,成为独立的开源项目Sharness。

有了Sharness,写测试用例不再是苦差事。

一Sharness是什么?

  • Sharness是一个用Shell脚本来编写测试用例的测试框架。
  • 可以在Linux,macOS平台上运行测试用例。
  • 测试输出符合TAP(测试任何协议),因此可以使用sharness自身工具或证明等TAP兼容测试夹具(harness)运行。
  • 是由Junio在2005年为Git项目开发的测试框架,由Christian
    Couder(chriscool)从Git中替换为独立测试框架。

地址:https://github.com/chriscool/sharness

二Sharness测试框架的优点

简洁

如果要在测试用例中创建/初始化一个文件(内容为“ Hello,world。”),看看sharness实现起来有多么简单:

cat >expect <<-EOF
Hello, world.
EOF

如果要对某应用(hello-world)的输出和预期的Expect文件进行比较,相同则测试用例通过,不同则展示差异。测试用例编写如下:

test_expect_success “app output test” ‘
    cat >expect <<-EOF &&
    Hello, world.
    EOF
    hello-world >actual &&
    test_cmp expect actual
‘

调试方便

每个测试用例脚本可以单独执行。使用-v参数,可以显示详细输出。使用-d参数,运行结束后保留​​用例的临时目录。

可以在要调试的测试案例后面增加test_pause语句,例如:

test_expect_success “name” ‘
    <Script…>
‘

test_pause
test_done

然后使用-v参数运行该脚本,会在test_pause语句处中断,进入一个包含sharness环境变量的子Shell中,目录会切换到测试用例单独的工作区。调试终止退出Shell即返回。

三Git项目的测试框架结构

Sharness源自于Git项目的测试用例框架。我们先来看看Git项目测试框架的结构。

Git项目测试相关文件

  • 待测应用放在项目的根目录。例如Git项目的待测应用:git和git-receive-pack等。
  • 测试框架修改PATH环境变量,进行测试用例在调用待测应用(如git命令)的时候,优先使用项目根目录下的待测应用。
  • 测试脚本命名为tNNNN- .sh ,即以字母t和四位数字开头的脚本文件。
  • 每一个测试用例在执行时会创建一个独立的临时目录,例如垃圾目录。t5323 -pack-redundant。测试用例执行成功,则该目录会被删除。

相关代码参见[2]。

四Git测试脚本的格式

以如下测试脚本为例[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测试框架结构

Sharness项目由Git项目的测试框架抽象而来,项目地址:https://github.com/chriscool/sharness

  • 待测应用放在项目的根目录。
  • 测试脚本命名为 .t ,即扩展名为.t的脚本文件。
  • 每一个测试用例在执行时会创建一个独立的临时目录,例如垃圾箱directory.simple.t 。测试用例执行成功,则该目录会被删除。
  • 在sharness.d目录下添加自定义脚本,可以扩展Sharness框架。即在框架代码加载时,自动加载该目录下文件。

我们对Sharness测试框架做了一些小植入:

  • 定制版本对测试框架代码进行了进一步封装,框架代码放置了单独的子目录。
  • 测试脚本的名称恢复为和Git项目测试脚本类似的名称(tNNNN- .sh ),即以字母t和四位数字开头的脚本文件。

定制版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方法后面是两个参数时,第一个参数用于描述测试用例,第二个参数是测试用例要执行的命令。

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
'

八Sharness语法规范和技巧

使用&&级联各个命令,确保所有命令都全部执行成功

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
}

涉及到目录切换,在子Shell中进行,以免影响后续测试用例执行时的工作目录

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的小技巧

使用Jundoc创建文本文件,如果其中的脚本要定义和使用变量,要对变量中的 字 符 进 行 转 移 。 J u n i o 称 为 了 一 个 h e r e d o c 语 法 的 小 技 巧 , 可 以 不 必 对 字符进行转移。Junio称为了一个heredoc语法的小技巧,可以不必对 Junioheredoc字符转义。

> +
> +  # 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

Shell编程语法规范

Git项目对于Shell编写的测试用例制定了语法规范,例如:

  • 使用tab缩进。
  • 规定case语句,if语句的缩进格式。
  • 输入输出重定向字符后面不要有空格。
  • 使用$(command)而不是command
  • 使用test方法,不要使用[…] 。

完整语法规范参考[5]。

九sharness常见的内置函数

  • test_expect_success

开始一个测试用例。

  • test_expect_failure

标记为存在已知问题,执行失败不报错,执行成功则警告该破碎的用例已经固定。

  • t est_must_fail

后面的一条命令必须失败。如果后面命令成功,测试失败。

  • test_expect_code

命令以给定返回值结束。

  • test_cmp

比较两个文件内容,相同成功,不同失败并显示差异。

  • test_path_is_file

参数必须是一个文件,且存在。

  • test_path_is_dir

参数必须是一个目录,且存在。

  • test_must_be_empty

参数指向的文件内容必须为空。

  • test_seq

跨平台的seq,用户生成数字序列。

  • test_pause

测试暂停,进入子Shell。

  • test_done

测试用例结束。

十扩展Sharness

Sharness提供了扩展功能。用户在sharness.d目录中添加以.sh结尾的脚本文件,即可对Sharness进行扩展。例如:

  • 在垃圾箱目录中。*目录下执行git init命令。目的是避免目录逃逸时错误执行git命令影响项目本身代码。

例如:测试脚本在工作区下创建了一个仓库(git init my.repo ),想要在该仓库下执行git clean ,却忘了进入到my.repo子目录再执行,结果导致待测项目中丢失文件。

  • 约会Git项目中的一些有用的测试方法。

如:test_tick方法,可以设置GIT_AUTHOR_DATE ,GIT_COMMITTER_DATE等环境变量,确保测试脚本多次运行时提交时间的一致性,并且产生一致的提交ID。

  • 约会项目需要的其他自定义方法。

例如git-repo项目为了避免工作区逃逸,在垃圾箱目录中。*目录下创建.repo文件。

十一Sharness在项目中的实战

git-repo是一个命令行工具,非常适合使用sharness测试框架编写测试用例。参见[6]。

对于非命令行工具,或者为了测试内置函数,需要先封装一个或多个假应用,再调用封装的命令行工具进行测试。例如在为Git项目开发proc-receive钩子扩展时,先开发一个假应用[7]。

之后再编写测试,调用仿造的app(test-tool proc-receive ),帮助完成测试用例的开发。参见以下提交中的测试用例[8]。

还可以使用一些Shell编程技巧,在多个测试文件中替换测试用例。例如如下测试用例在测试HTTP协议和本地协议时,替换了同一套测试用例(t5411目录下的测试脚本)[9] 。

技术行业,一定要提升技术功底,丰富自动化项目实战经验,这对于你未来几年职业规划,以及测试技术掌握的深度非常有帮助。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!下面有我近几年的收集和整理,整体是围绕着【软件测试】来进行整理的,主体内容包含:python自动化测试专属视频、Python自动化详细资料、全套面试题等知识内容。

对于软件测试的的朋友来说应该是最全面最完整的面试备战仓库,为了更好地整理每个模块,我也参考了很多网上的优质博文和项目,力求不漏掉每一个知识点,很多朋友靠着这些内容进行复习,拿到了BATJ等大厂的offer,这个仓库也已经帮助了很多的软件测试的学习者,希望也能帮助到你!

愿你我相遇,皆有所获! 欢迎关注微信公众号:【伤心的辣条】 免费领取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值