在 shUnit2 中重定向文件输出的单元测试 bash 个脚本
Unit testing bash scripts that redirect file output in shUnit2
我正在使用 shUnit2 在 Bash shell 脚本中进行单元测试。
我有这样的代码:
cat > /etc/somefile <<EOF
some file content
EOF
我想编写单元测试来测试此代码,但为此我需要避免文件 I/O 重定向。
我意识到我可以重构代码以将重定向文件输出的位移动到函数内部,例如
cat_somefile() {
cat > /etc/somefile <<EOF
some file content
EOF
}
然后我可以在我的测试中存根这个函数。
有没有什么方法可以在不重构代码的情况下测试它?
正如@chepner 所建议的(另见 ref)chroot 可以提供帮助。
要设置这一切:
1/ 授予 sudo 访问 运行 chroot 命令的权限。
$ sudo visudo
...
%admin ALL = (ALL) NOPASSWD: /usr/sbin/chroot
2/ 创建一个包含脚本和一个函数来设置监狱。
FAKE_ROOT=./fake_root
copy_to_jail() {
d=$(dirname )
mkdir -p $FAKE_ROOT/$d
cp -p $FAKE_ROOT/$d
}
jail_files() {
# All of these commands are used at least once by one of the
# scripts under test.
commands="
/bin/cat
/bin/chmod
/bin/date
/bin/mkdir
/bin/rm
/usr/bin/awk
/usr/bin/cut
/usr/bin/tee
"
local OPTIND script wrappers
while getopts "s:w:" o
do
case "${o}" in
s)
script="${OPTARG}"
;;
w)
wrappers="${wrappers} ${OPTARG}"
;;
esac
done
shift $((OPTIND-1))
# Without /bin/sh the jail doesn't work.
# I didn't spend time to figure out why.
for f in /bin/sh $script $wrappers $commands
do
copy_to_jail $f
done
# We figure out all the library files we need to build
# a working jail just by trying to start it, observing which file
# it says is missing, copying the missing file, and repeating
# until it starts.
case $(uname -s) in
Darwin)
# Copy Bash 4 and GNU Sed.
cp -p /usr/local/bin/bash $FAKE_ROOT/bin
cp -p /usr/local/bin/gsed "$FAKE_ROOT/bin/sed"
initial_libs=$(
for f in /usr/local/bin/bash
do
otool -L $f | awk 'NR > 1 {print }'
done | sort -u)
initial_libs="/usr/lib/dyld $initial_libs"
for f in $initial_libs
do
copy_to_jail $f
done
who_i_am=$(whoami)
set -o pipefail
while true
do
missing_lib=$(sudo chroot -u $who_i_am $FAKE_ROOT /bin/sh -c echo 2>&1 | \
awk '/dyld: Library not loaded:/ {print }')
[ "$?" -eq 0 ] && break
copy_to_jail $missing_lib
done
;;
*)
echo "Platform $(uname -s) not supported"
exit 1
;;
esac
}
clean_up_jail() {
rm -rf $FAKE_ROOT
}
3/ 测试文件中:
. shunit2/include.sh
script_under_test=path/to/my/script.sh
make_jail() {
# Copy any binaries that the script depends on
# to the jail.
jail_files \
-s $script_under_test
# Create any directories that are expected to exist.
mkdir -p $FAKE_ROOT/etc/apt/preferences.d
mkdir -p $FAKE_ROOT/etc/{default,init.d}
mkdir -p $FAKE_ROOT/etc/apache2/sites-enabled
mkdir -p $FAKE_ROOT/tmp
...
# Set up any files with initial content.
echo "some initial content" > $FAKE_ROOT/etc/hosts
who_i_am=$(whoami)
sudo chroot -u $who_i_am $FAKE_ROOT \
/bin/$(basename $script_under_test) > /dev/null
}
oneTimeSetUp() {
make_jail
}
oneTimeTearDown() {
clean_up_jail
}
# Write actual tests.
test_something() {
...
}
test_something_else() {
...
}
# Call shUnit2.
. shunit2
我会在某个阶段写一篇关于此的博客post。
我正在使用 shUnit2 在 Bash shell 脚本中进行单元测试。
我有这样的代码:
cat > /etc/somefile <<EOF
some file content
EOF
我想编写单元测试来测试此代码,但为此我需要避免文件 I/O 重定向。
我意识到我可以重构代码以将重定向文件输出的位移动到函数内部,例如
cat_somefile() {
cat > /etc/somefile <<EOF
some file content
EOF
}
然后我可以在我的测试中存根这个函数。
有没有什么方法可以在不重构代码的情况下测试它?
正如@chepner 所建议的(另见 ref)chroot 可以提供帮助。
要设置这一切:
1/ 授予 sudo 访问 运行 chroot 命令的权限。
$ sudo visudo
...
%admin ALL = (ALL) NOPASSWD: /usr/sbin/chroot
2/ 创建一个包含脚本和一个函数来设置监狱。
FAKE_ROOT=./fake_root
copy_to_jail() {
d=$(dirname )
mkdir -p $FAKE_ROOT/$d
cp -p $FAKE_ROOT/$d
}
jail_files() {
# All of these commands are used at least once by one of the
# scripts under test.
commands="
/bin/cat
/bin/chmod
/bin/date
/bin/mkdir
/bin/rm
/usr/bin/awk
/usr/bin/cut
/usr/bin/tee
"
local OPTIND script wrappers
while getopts "s:w:" o
do
case "${o}" in
s)
script="${OPTARG}"
;;
w)
wrappers="${wrappers} ${OPTARG}"
;;
esac
done
shift $((OPTIND-1))
# Without /bin/sh the jail doesn't work.
# I didn't spend time to figure out why.
for f in /bin/sh $script $wrappers $commands
do
copy_to_jail $f
done
# We figure out all the library files we need to build
# a working jail just by trying to start it, observing which file
# it says is missing, copying the missing file, and repeating
# until it starts.
case $(uname -s) in
Darwin)
# Copy Bash 4 and GNU Sed.
cp -p /usr/local/bin/bash $FAKE_ROOT/bin
cp -p /usr/local/bin/gsed "$FAKE_ROOT/bin/sed"
initial_libs=$(
for f in /usr/local/bin/bash
do
otool -L $f | awk 'NR > 1 {print }'
done | sort -u)
initial_libs="/usr/lib/dyld $initial_libs"
for f in $initial_libs
do
copy_to_jail $f
done
who_i_am=$(whoami)
set -o pipefail
while true
do
missing_lib=$(sudo chroot -u $who_i_am $FAKE_ROOT /bin/sh -c echo 2>&1 | \
awk '/dyld: Library not loaded:/ {print }')
[ "$?" -eq 0 ] && break
copy_to_jail $missing_lib
done
;;
*)
echo "Platform $(uname -s) not supported"
exit 1
;;
esac
}
clean_up_jail() {
rm -rf $FAKE_ROOT
}
3/ 测试文件中:
. shunit2/include.sh
script_under_test=path/to/my/script.sh
make_jail() {
# Copy any binaries that the script depends on
# to the jail.
jail_files \
-s $script_under_test
# Create any directories that are expected to exist.
mkdir -p $FAKE_ROOT/etc/apt/preferences.d
mkdir -p $FAKE_ROOT/etc/{default,init.d}
mkdir -p $FAKE_ROOT/etc/apache2/sites-enabled
mkdir -p $FAKE_ROOT/tmp
...
# Set up any files with initial content.
echo "some initial content" > $FAKE_ROOT/etc/hosts
who_i_am=$(whoami)
sudo chroot -u $who_i_am $FAKE_ROOT \
/bin/$(basename $script_under_test) > /dev/null
}
oneTimeSetUp() {
make_jail
}
oneTimeTearDown() {
clean_up_jail
}
# Write actual tests.
test_something() {
...
}
test_something_else() {
...
}
# Call shUnit2.
. shunit2
我会在某个阶段写一篇关于此的博客post。