不寻常的单元测试,第1部分:带蝙蝠的bash脚本

介绍 (Introduction)

In my work I often see well-intended implementations of CI/CD pipelines that might have functional tests but lack unit testing. This impacts the reliability of the functional testing: since the major difference is white-box vs black-box testing, a functional test might succeed even if some components are failing (for instance due to some internal side effect).

在我的工作中,我经常看到CI / CD管道的良好实现,这些实现可能具有功能测试但缺乏单元测试 。 这会影响功能测试的可靠性:由于主要区别在于白盒测试与黑盒测试,因此即使某些组件出现故障(例如由于某些内部副作用),功能测试也可能会成功。

Developers in the Java, Python or Ruby world might be familiar with unit testing using tools like JUnit, PyTest and TestUnit. But what about system administrators that maintain Bash or Powershell scripts? These scripts can contain a lot of functionality and are often used as a dependency in automation.

Java,Python或Ruby世界的开发人员可能熟悉使用JUnitPyTestTestUnit之类的工具进行单元测试。 但是维护BashPowershell脚本的系统管理员呢? 这些脚本可以包含许多功能,并且经常用作自动化中的依赖项。

This challenge inspired me to experiment with some unit testing tools for scripting. So, if you are a System Admin, maintain important Bash and/or Powershell scripts and want to increase reliability through (automated) testing, this might be interesting to you.

这个挑战激发了我尝试一些用于脚本的单元测试工具的经验。 因此,如果您是系统管理员,请维护重要的Bash和/或Powershell脚本,并希望通过(自动)测试来提高可靠性,那么这可能对您很有趣。

使用Bats对Bash脚本进行单元测试 (Unit testing Bash scripts with Bats)

In this blog we will dive into unit testing Bash scripts. I do think that Bash is challenging as a programming language, especially when it comes to variable scope and isolation or how functions are implemented, so we’ll need our scripts to be testable.

在本博客中,我们将深入探讨单元测试Bash脚本。 我确实认为Bash作为一种编程语言具有挑战性,尤其是在涉及可变范围和隔离性或如何实现功能时,因此我们需要脚本是可测试的。

We’re going to use Bats for unit testing Bash scripts. Bats stands for BASH Automated Testing System. It’s a TAP-compliant testing framework for Bash and it provides a simple way to verify that the UNIX programs you write behave as expected.

我们将使用Bats对Bash脚本进行单元测试。 Bats代表BASH自动化测试系统 。 它是Bash的TAP兼容测试框架,它提供了一种简单的方法来验证您编写的UNIX程序是否按预期运行。

可测试的代码有人吗? (Testable code anyone?)

As you might already know, a unit test is a method that instantiates a small portion of an application and verifies its behavior independently from other parts. A good approach for Bash scripts is to define functions to isolate code and make it testable.

您可能已经知道,单元测试是一种实例化应用程序一小部分并独立于其他部分来验证其行为的方法。 Bash脚本的一种好方法是定义函数以隔离代码并使之可测试。

Often there is internal/external interaction between code parts, and we’ll need setup a test double to control the behavior.

通常,代码部分之间存在内部/外部交互,因此我们需要设置一个double测试来控制行为。

让我们开始吧-准备 (Let’s get started — preparation)

For creating and executing our unit tests we’ll use a docker container which will give us a consistent and ephemeral environment. The only thing you’ll need is to have docker engine installed.

为了创建和执行单元测试,我们将使用一个docker容器,该容器将为我们提供一个一致且短暂的环境。 您唯一需要做的就是安装docker引擎

The example code for this exercise can be found here. First we need to build our own container image and add additional Bats helpers for testing.

可以在此处找到此练习的示例代码。 首先,我们需要构建自己的容器映像,并添加其他Bats助手进行测试。

This is our Dockerfile:

这是我们的Dockerfile

FROM bats/bats:latest 
COPY ./temp_clone_dir /opt/bats-test-helpers
WORKDIR /code/

With that we can use this script to clone the bats-helpers code and build our container:

这样,我们可以使用此脚本克隆bats-helpers代码并构建我们的容器:

[ -d "temp_clone_dir" ] && rm -rf "temp_clone_dir"
mkdir temp_clone_dir git clone https://github.com/ztombol/bats-support temp_clone_dir/bats-support
git clone https://github.com/ztombol/bats-assert temp_clone_dir/bats-assert
git clone https://github.com/lox/bats-mock temp_clone_dir/lox-bats-mockdocker build . -t bats-with-helpers:latest

Now let’s write some tests!

现在让我们编写一些测试!

测试1 —断言函数的返回 (Test 1 — assert the return of a function)

Consider this very basic function:

考虑以下非常基本的功能:

# example1.shfunction func1() {
return 1
}

We want to make sure that this function always returns 1, and we can easily do this with Bats. We’re going to create a seperate test script and define a positive and a negative test:

我们要确保此函数始终返回1,并且可以使用Bats轻松地做到这一点。 我们将创建一个单独的测试脚本,并定义一个正面和负面的测试:

# test_example1.bats
#!/usr/bin/env bats@test "func1 function should return 1" {
source /code/example1.sh
run func1
[ "$status" -eq 1 ]
}@test "func1 function should return 2" {
source /code/example1.sh
run func1
[ "$status" -eq 2]
}

With ‘@test’ we define a test stanza with a description of our test. The first thing we do is source the function defined in example1.sh. Be aware that it actually executes the commands in the sourced script, as a best practice only define functions in there.

使用“ @test”,我们定义了一个测试节,其中包含对测试的描述。 我们要做的第一件事是获取example1.sh中定义的函数。 请注意,它实际上是在源脚本中执行命令,而最佳实践是仅在其中定义函数。

The run helper executes the function and saves the result and variables $status and $output which can be used for assertion.

运行助手将执行该函数并保存结果和变量$ status$ output ,这些变量可用于断言。

Let’s execute the tests:

让我们执行测试:

Image for post

The docker container has entrypoint of the bats command, so we simply point to the bats test file as an argument. Bats will then discover available tests (in our case 2) and execute the tests. As expected, the positive test succeeds and the negative test fails. Bats also shows what exactly failed.

docker容器具有bats命令的入口点,因此我们只需将bats测试文件作为参数即可。 蝙蝠将发现可用的测试(在我们的案例2中)并执行测试。 如预期的那样,正面测试成功,而负面测试失败。 蝙蝠还显示出什么完全失败了。

测试2-测试条件和文件突变 (Test 2 — test a condition and a file mutation)

function func2() {
if [ ${ENV_GRASS} = "green" ]
then
touch colors.conf
echo 'grass="green"' > colors.conf
fi
}

This function assesses the variable ENV_GRASS and will create a file when the value is ‘green’. Our basic test will assert the condition and the side effect of the file creation:

此函数评估变量ENV_GRASS,并且当值为“绿色”时将创建一个文件。 我们的基本测试将断言文件创建的条件和副作用:

# test_example1.bats
#!/usr/bin/env bats
load '/opt/bats-test-helpers/bats-support/load.bash'
load '/opt/bats-test-helpers/bats-assert/load.bash'@test "func2 function should create config file is grass is green" {
source example1.sh
ENV_GRASS="green"
run func2
assert [ -e 'colors.conf' ]
}@test "func2 function should not create config file if grass is not green" {
source example1.sh
ENV_GRASS="red"
run func2
assert [ ! -e 'colors.conf' ]
}

For this test we’ll use the bats-helper bats-assert which has a lot of testing functionality. Again we’re sourcing our function, declaring the ENV_GRASS variable, executing the function and assert that the file is created/not created.

对于此测试,我们将使用具有很多测试功能的bats-helper bats-assert 。 再次,我们提供函数,声明ENV_GRASS变量,执行函数并断言该文件已创建/未创建。

Image for post

测试3-测试Terraform包装器脚本 (Test 3 — test a terraform wrapper script)

When I use Terraform I’d like to use a wrapper script that sets things up. For instance, this function makes sure the .terraform directory does not exist before we do a terraform init:

当我使用Terraform时,我想使用一个包装器脚本来进行设置。 例如,此函数确保在执行terraform初始化之前.terraform目录不存在

init_tf() {
if [ -d ".terraform" ]; then
echo "unclean working dir, .terraform dir still exists. removing .terraform"
rm -rf .terraform
fi terraform init -input=false
}

This gives us ample testing angles:

这给了我们充足的测试角度:

  • We do not want terraform init to actually be called, so we’ll need to stub the terraform command which we can do with the bats-helper bats-mock

    我们不希望真正调用terraform init,因此我们需要对terraform命令进行存根处理 ,这可以通过bats-helper bats-mock来完成。

  • The init_tf function should remove the directory .terraform if exists

    如果存在,init_tf函数应删除目录.terraform
  • The init_tf function should print message if the directory .terraform exists

    如果目录.terraform存在,则init_tf函数应打印消息

Our test code looks like this:

我们的测试代码如下:

# test_example_tf_plan.bats
#!/usr/bin/env bats
load '/opt/bats-test-helpers/bats-support/load.bash'
load '/opt/bats-test-helpers/bats-assert/load.bash'
load '/opt/bats-test-helpers/lox-bats-mock/stub.bash'setup() {
stub terraform \
"'init -input=false' : echo 'I am stubbed for terraform init!'"
if [ -d .terraform ] ; then rm -rf .terraform ; fi
}@test "init_tf function should remove dir .terraform if exists" {
source example_tf_plan.sh
mkdir .terraform
run init_tf
assert [ ! -d '.terraform' ]
}@test "init_tf function should print message if dir. .terraform exists" {
source example_tf_plan.sh
mkdir .terraform
run init_tf
assert_output "unclean working dir, .terraform dir still exists. removing .terraform"
}

This time we’re loading the bat-mock helper and we have another section ‘setup’. In setup you can set things up that are needed for our tests. Firstly we are stubbing the terraform command with specific arguments, so that it doesn’t hit the actual terraform binary. Secondly we need the .terraform directory not to exist for our tests. Now that we have a setup we can define the tests.

这次我们正在加载蝙蝠模拟助手,我们还有另一部分“设置” 。 在设置中,您可以设置测试所需的内容。 首先,我们使用特定的参数对terraform命令进行存根处理,以便它不会命中实际的terraform二进制文件。 其次,我们需要测试不存在.terraform目录。 现在我们有了设置,可以定义测试了。

The first test asserts that the .terraform directory is deleted if it exists. We’re sourcing the function, deliberately creating the .terraform directory, run the function and asserting the directory.

第一个测试断言.terraform目录已删除。 我们正在寻找该函数,有意创建.terraform目录,运行该函数并声明该目录。

The second test is doing the same, but then asserting the message output.

第二个测试也在做相同的事情,但是随后声明了消息输出。

Image for post

结论 (Conclusion)

As you can see, it’s quite easy to increase test coverage for your Bash scripts which gives you more confident in the system. Whenever someone breaks a script, the unit tests in the pipeline should fail.

如您所见,增加Bash脚本的测试覆盖范围很容易,这使您对系统更有信心。 每当有人破坏脚本时,管道中的单元测试都将失败。

Next up is Powershell, stay tuned!

接下来是Powershell,敬请期待!

翻译自: https://medium.com/@marck.oemar/unusual-unit-testing-part-1-bash-scripts-with-bats-55ac78e61491

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值