Bats无法识别被测函数导出的数组变量的解决方法
Bats是一款优秀的bash脚本测试工具,但在示例、说明等诸多方面的文档严重不足1。很多开发者在测试输出为全局数组的函数时,会遇到Bats无法识别该数组的问题——普通变量可以通过stdout导出内容,但数组处理起来更麻烦。
问题复现
以下是包含函数定义(_load_filetoarray)和测试的文件:
#!bats/bin/bats load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' _load_filetoarray() { # 假设数组数据来自外部文件 declare -a CREATEDARRAY CREATEDARRAY=("aaa" "bbb" "ccc") export CREATEDARRAY } @test "stackoverflow plain" { # 运行待测试函数 run _load_filetoarray # 验证数组加载的元素数量 assert [ "${#CREATEDARRAY[@]}" -eq 3 ] }
在Bats v1.10.0中运行该文件,会得到如下失败结果:
stackoverflow.bats ✗ stackoverflow plain (from function `assert' in file test_helper/bats-assert/src/assert.bash, line 40, in test file stackoverflow.bats, line 18) `assert [ "${#CREATEDARRAY[@]}" -eq 3 ]' failed -- assertion failed -- expression : [ 0 -eq 3 ] -- 1 test, 1 failure
单独运行bash脚本时数组能正常加载,但Bats无法识别,核心原因是:Bats的run命令会在子shell中执行目标函数,子shell内的环境变量(包括数组)无法传递回父shell,所以测试代码中访问到的CREATEDARRAY是空数组。
解决方案
方案1:直接调用函数(不使用run)
如果不需要捕获函数的输出、退出状态等信息,直接在当前shell中调用函数即可,这样函数创建的全局数组能被测试代码直接访问:
@test "stackoverflow plain" { # 直接调用函数,跳过run命令 _load_filetoarray # 验证数组长度 assert [ "${#CREATEDARRAY[@]}" -eq 3 ] # 也可以验证具体元素 assert_equal "${CREATEDARRAY[0]}" "aaa" }
方案2:将数组输出到stdout,在测试中解析
如果必须使用run(比如需要检查函数的退出状态、捕获错误输出),可以修改函数,将数组元素输出到标准输出,然后利用Bats提供的$lines变量(存储命令输出的每一行)来验证:
首先修改函数:
_load_filetoarray() { declare -a CREATEDARRAY CREATEDARRAY=("aaa" "bbb" "ccc") # 将数组元素按换行分隔输出到stdout printf "%s\n" "${CREATEDARRAY[@]}" }
然后修改测试代码:
@test "stackoverflow with run" { run _load_filetoarray # 验证函数执行成功 assert_success # 验证输出的行数等于数组元素个数 assert_equal "${#lines[@]}" 3 # 逐个验证数组元素 assert_equal "${lines[0]}" "aaa" assert_equal "${lines[1]}" "bbb" assert_equal "${lines[2]}" "ccc" }
1 关于Bats文档的吐槽:
抱歉,它是个很棒的工具,但文档让我回想起计算机行业的“旧日时光”,知识被资深人士垄断。我花了数年才发现Bats支持直接测试函数,还是通过别人的测试文件才得知。我曾摸索出一个需要run bash -c '...'搭配export -f funcname的测试。另外,$lines和$output孰先孰后?官方readthedocs.io教程中途逻辑混乱,开始引用未提前介绍的文件。
内容的提问来源于stack exchange,提问作者Cliff

