本文分析了bats–Bash自动化测试工具的安装、语法、常用指令及常用函数等内容。
上篇文章回顾:
学习RAID 01/10/10E的区别
bats 是一个符合
TAP 标准
的 Bash 版测试框架,它使用了一种极为简便的方法来验证命令行程序是否正常运行。
bats 要求 Bash 的最低版本是 3.2.57 ,bats 测试文件实际上一个 bash 的脚本文件,完全可以使用 shell 的语法书写。
一、安装
强烈建议使用源码或者npm安装最新版本, bats 经过多人接手,代码存放比较混乱,某些系统下安装的是旧版本的,目前社区在维护的版本地址为:
https://github.com/bats-core/bats-core.git
01macOS下安装
brew install bats-core复制代码
02CentOS下安装
yum install bats复制代码
03UBuntu下安装
apt-get install bats复制代码
04Windows安装
-
Git for Windows Bash (MSYS2 based)
-
Windows Subsystem for Linux
-
MSYS2
-
Cygwin
Windows下推荐使用源码安装或者 npm 的方式进行安装
。
05使用源码安装
git clone https://github.com/bats-core/bats-core.gitcd bats-core
./install.sh /usr/local
复制代码
06使用Docker安装
docker pull bats/bats
docker run -it --rm bats/bats --version
docker run -it --rm -v "$(pwd):/code" bats/bats /code/test复制代码
07使用npm安装
npm install -g bats
复制代码
经过测试 CentOS 下的版本为 2014 年的版本,ubuntu下也不是最新版本,建议使用源码或者npm安装最新版本。
二、语法
测试用例的语法格式为:
#!/usr/bin/env bats@test "grep --version check" { # 测试用例名称
run grep --version # 运行的外部命令
[ $status -eq 0 ] # 断言
[ "${lines[0]%% *}" == 'grep' ] # 断言
}复制代码
每个 Bats 测试文件的评估次数为 n + 1 次,其中 n 是文件中的测试用例数。运行测试脚本时首先计算测试用例的数量,然后遍历测试用例并在独立进程中执行每个测试用例。
在运行测试用例时,Bats使用Bash的 errexit(set -e)选项,这样写在@test 里面的语句都是真理断言。一旦测试用例中的某一个断言失败(某条语句的状态码不是 0)则这个测试用例视为失败。
三、常用指令
01run:运行外部命令
用于测试外部命令然后对它的退出状态和输出状态进行断言。Bats 包含一个 run 的指令,它可以将传入的参数当成命令调用,并且将退出状态和输出状态保存到特殊的全局变量中,以便可以继续在测试用例中增加断言。
比如说我们正在测试 cat:
#!/usr/bin/env bats@test "cat nonexistent_filename check" { # 测试用例名称
run cat nonexistent_filename # 运行的外部命令
[ $status -eq 1 ] # 断言
[ "$output" == 'cat: nonexistent_filename: No such file or directory' ] # 断言
[ "${lines[0]}" == 'cat: nonexistent_filename: No such file or directory' ] # 断言
}复制代码
这里有三个 Bats 的特殊变量
$status 是命令退出状态码
$output 是命令的标准输出和标准错误的内容
$lines是命令输出内容的数组包含各行内容 cat nonexistent_filename 只有一行内容
02load:使用共享文件
如果想用跨越多个测试文件共享环境变量或者自定义的函数,可以使用 load 指令。共享文件的扩展文件名必须是.bash。load可以使用相对路径或者绝对路径。
使用相对路径的写法是(可以省略扩展文件名):
load test_helper复制代码
使用绝对路径的写法时(必须带上扩展文件名):
load /test_helpers/test_helper.bash复制代码
03skip:跳过测试
在测试过程中如果失败时如果想继续可以 skip 指令来跳过测试:
@test "A test I don't want to execute for now" {
skip
run foo
[ "$status" -eq 0 ]
}复制代码
也可以加入跳过原因:
@test "A test I don't want to execute for now" {
skip "This command will return zero soon, but not now"
run foo
[ "$status" -eq 0 ]
}复制代码
或者也可以根据条件判断是否跳过:
@test "A test which should run" { if [ foo != bar ]; then
skip "foo isn't bar"
fi
run foo
[ "$status" -eq 0 ]
}复制代码
四、常用函数
01@test
这是 Bats 主要的函数所有的测试用例都要按照这个函数的格式书写:
@test "grep --version check" { # 测试用例名称
run grep --version # 运行的外部命令
[ $status -eq 0 ] # 断言
[ "${lines[0]%% *}" == 'grep' ] # 断言
}复制代码
02setup/teardown初始化和善后函数
setup / teardown 是两个特殊的函数,用于在测试用例开始之前和结束之后进行初始化和善后工作。比如开始之前设置环境变量创建测试目录。以 soar 为测试用例为例:
setup() {
export SOAR_DEV_DIRNAME="${BATS_TEST_DIRNAME}/../"
export SOAR_BIN="${SOAR_DEV_DIRNAME}/bin/soar"
export SOAR_BIN_ENV="${SOAR_DEV_DIRNAME}/bin/soar -config ${SOAR_DEV_DIRNAME}/etc/soar.yaml"
export BATS_TMP_DIRNAME="${BATS_TEST_DIRNAME}/tmp"
export BATS_FIXTURE_DIRNAME="${BATS_TEST_DIRNAME}/fixture"
mkdir -p "${BATS_TMP_DIRNAME}"}
teardown(){ //TODO
......
}复制代码
五、特殊变量
Bats 中包含几个全局变量 :
-
$BATS_TEST_FILENAME Bats测试文件的绝对路径。
-
$BATS_TEST_DIRNAME Bats测试文件所在的目录。
-
$BATS_TEST_NAMES 每个测试用例的函数名称数组。
-
$BATS_TEST_NAME 包含当前测试用例的函数的名称。
-
$BATS_TEST_DESCRIPTION 当前测试用例的描述。
-
$BATS_TEST_NUMBER 测试文件中当前测试用例的(从1开始)索引。
-
$BATS_TMPDIR 用于存储临时文件的目录的位置。
六、注意事项
01写在@test之外的代码
写在 @test 函数之外代码一旦失败 Bats 会立刻中断执行,某些情况下这样做会很有用比如检查依赖项,但是如果在@test、setup、teardown之外打印的任何输出必须重定向到stderr(>&2),否则这些输出内容可能会污染TAP流导致Bats 测试失败。
经过测试 @test 之外的代码会优先执行
02文件描述符3(FD3)
如果Bats卡死可以读这一块内容。
Bats 将测试代码的输出流和 TAP 输出流分开,这样做的目的是为了确保 TAP 的输出不被污染。在输出至终端的部分详细介绍了如何使用 FD3 正确打印自定义文本。
但是使用 FD3 的一个已知的问题是:在某些情况下(如程序的子进程在后台运行的时候),它会导致 Bats 卡死。在这种情况下在生成子进程的时候,子进程会从父进程继承 FD3 ,导致Bats 会等待子进程执行完成之后关闭 FD3 。如果子进程需要花费大量时间完成,例如,如果子进程是 sleep 100 命令或是后台服务,那么 Bats 也会阻塞同样的时间。
为了避免这种情况,启动可能长时间运行的子进程之前显式关闭 FD3 command_name 3>&-
举例说明:
1.会卡死的情况:
@test "cat nonexistent_filename check" { # 测试用例名称
run cat nonexistent_filename
sleep 100 & # 后台执行
[ $status -eq 1 ]
[ "$TTTT" -eq 1 ] # 断言
[ "$output" == 'cat: nonexistent_filename: No such file or directory' ] # 断言
}复制代码
2.
不会卡死的情况:
@test "cat nonexistent_filename check" { # 测试用例名称
run cat nonexistent_filename
sleep 100 3>&- & # 后台执行并且关闭文件描述符3
[ $status -eq 1 ]
[ "$TTTT" -eq 1 ] # 断言
[ "$output" == 'cat: nonexistent_filename: No such file or directory' ] # 断言
}复制代码
03命令输出到终端
-
在 @test 函数内部输出
(1)如果从@test 内部输出字符你需要将输出重定向到 FD3 例如 echo ‘test’>&3。这时输出将变成 TAP 流的一部分。为了生成100%符合 TAP 流格式的输出,我们推荐的写法是 echo ‘# text’ >&3 。否则,在使用分析 TAP 流的第三方工具时可能会遇到意外错误。
(2)Bats 默认使用友好的输出格式(-p, –pretty)。 TAP 流默认不会从 FD3 中输出任何字符
(3)无论指定何种输出格式,直接输出到 stdout、stderr 的文本(例如 echo “aaaa”)会被@test 视为测试的一部分,仅仅在测试失败的时候显示。
在 setup/teardown 函数内部输出
这一部分内容输出行为和@test 中一样。
在 @test 或者setup/teardown 外部输出
(1)无论输出的字符被重定向到何处(FD1、FD2、FD3)字符都会立刻显示到终端
(2)按照这种方式打印的文本将会禁用友好的输出格式(-p, –pretty)。此外他也会使得输出不符合 TAP 流规范。
(3)由内部管道或者重定向输出到标准错误的字符总会第一时间显示出来。
七、Bats命令行用法
bats 命令行用法:
Bats 1.1.0 Usage: bats [-cr] [-f <regex>] [-p | -t] <test>...
bats [-h | -v]
<test> 为一个 bats 测试用例的文件,或者是一个包含后缀名为 .bats 文件的目录
-c, --count 计算没有运行的测试用例的个数
-f, --filter 通过正则表达式指定运行某些测试用例
-h, --help 显示帮助信息
-p, --pretty 以比较友好的方式展现测试用例的输出结果(默认是使用这种方式)
-r, --recursive 在子目录中包含测试
-t, --tap 以 TAP 格式显示输出结果
-v, --version 显示版本号信息复制代码
更多信息见:
https://github.com/bats-core/bats-core复制代码
八、开发工具推荐
在常用的开发工具中安装 Bats 插件,增加代码高亮显示和代码完成提示,提高开发效率。
01VS Code
02Sublime Text
03Vim
九、其他开发工具
更多开发工具和插件参考:
十、参考文献
bats-core:
Bat Evaluation Process:
本文首发于公众号”小米运维“,
点击查看原文
。