moritetuのIT関連技術メモ

shUnit2

xUnit系テストツールのシェル版である。

インストール

特別な操作は不要。
ソースをダウンロードして、任意の場所に設置するだけである。
非常に簡単である。

テスト

特別な設定は不要であり、典型的な実行方法はテストプログラムの中でshunit2プログラムをインクルードするだけである。

sample.sh

#! /bin/sh

testEquality() {
  assertEquals 1 1
}
# shunit2テストをインクルード、テストが実行される
. /path/to/shunit2

テストプログラム

補足

テストの実行

shunit2から実行

テストは、テストプログラム内からshunit2をロードする、または、shunit2にテスト対象ファイル名を渡すことで実行される。

$ shunit2 <a test file> [<func> [<func>...]]

sample_test.sh

#! /bin/sh

testEquality() {
    assertEquals 1 1
}

testTrue() {
    assertTrue "[ 1 -eq 1 ]"
}

実行

ファイルのテスト関数をすべて実行する。

$ shunit2 sample_test.sh
testEquality
testTrue

Ran 2 tests.

OK

ファイルの特定のテスト関数を実行する。

$ shunit2 sample_test.sh testTrue
testTrue

Ran 1 test.

OK

shunit2をインクルードして実行

hoge_test.sh

# hoge_test.sh
testFunc() {
  :
}

. /path/to/shunit2
# この場合、$0がテスト対象ファイルと見なされる(つまり、hoge_test.sh)

実行

$ bash hoge_test.sh

テストスイートの実行

test_runner

テスト実行のためのヘルパーである。
suffixが、_test.shであるテストファイル見つけてまとめて実行してくれる(テストスイートの実行である)。
特定のシェル環境で実行したり、指定がしなければデフォルトで複数のシェル環境でテストを実行してくれる。

$ ./test_runner -h
usage: test_runner [-e key=val ...] [-s shell(s)] [-t test(s)]

デフォルトのシェル環境は以下のとおり。

/bin/sh ash /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/zsh

サンプル test_runnerの実行例

shunit2の配下にあるテストでない場合は、shunit2のlibディレクトリの場所を指定すると流れる。
テスト対象は、テスト実行ディレクトリ($PWD)にある_test.shのファイルである。

$ tree ../shunit2
../shunit2
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── doc
│   ├── CHANGES-2.1.md
│   ├── RELEASE_NOTES-2.1.0.txt
│   ├── RELEASE_NOTES-2.1.1.txt
│   ├── RELEASE_NOTES-2.1.2.txt
│   ├── RELEASE_NOTES-2.1.3.txt
│   ├── RELEASE_NOTES-2.1.4.txt
│   ├── RELEASE_NOTES-2.1.5.txt
│   ├── RELEASE_NOTES-2.1.6.txt
│   ├── RELEASE_NOTES-2.1.7.md
│   ├── TODO.txt
│   ├── contributors.md
│   └── design_doc.txt
├── examples
│   ├── equality_test.sh
│   ├── lineno_test.sh
│   ├── math.inc
│   ├── math_test.sh
│   ├── mkdir_test.sh
│   ├── mock_file.sh
│   ├── mock_file_test.sh
│   ├── party_test.sh
│   └── suite_test.sh
├── lib
│   ├── shflags
│   └── versions
├── shunit2
├── shunit2_args_test.sh
├── shunit2_asserts_test.sh
├── shunit2_failures_test.sh
├── shunit2_macros_test.sh
├── shunit2_misc_test.sh
├── shunit2_standalone_test.sh
├── shunit2_test_helpers
└── test_runner

3 directories, 35 files
$ tree
.
└── hoge_test.sh

0 directories, 1 file
$ LIB_DIR=../shunit2/lib ../shunit2/test_runner
#------------------------------------------------------------------------------
# System data.
#

$ uname -mprsv
Linux 3.10.0-1062.9.1.el7.x86_64 #1 SMP Fri Dec 6 15:49:49 UTC 2019 x86_64 x86_64

OS Name: Linux
OS Version: CentOS Linux 7 (Core)

### Test run info.
shells: /bin/sh ash /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/zsh
tests: hoge_test.sh


#------------------------------------------------------------------------------
# Running the test suite with /bin/sh.
#
shell name: sh
shell version: GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)

--- Executing the 'hoge' test suite. ---
testEquality

Ran 1 test.

OK


#------------------------------------------------------------------------------
# Running the test suite with ash.
#
runner:WARN unable to run tests with the ash shell


#------------------------------------------------------------------------------
# Running the test suite with /bin/bash.
#
shell name: bash
shell version: GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)

--- Executing the 'hoge' test suite. ---
testEquality

Ran 1 test.

OK


#------------------------------------------------------------------------------
# Running the test suite with /bin/dash.
#
runner:WARN unable to run tests with the dash shell


#------------------------------------------------------------------------------
# Running the test suite with /bin/ksh.
#
runner:WARN unable to run tests with the ksh shell


#------------------------------------------------------------------------------
# Running the test suite with /bin/pdksh.
#
runner:WARN unable to run tests with the pdksh shell


#------------------------------------------------------------------------------
# Running the test suite with /bin/zsh.
#
runner:WARN unable to run tests with the zsh shell


実験テストプログラムがshunit2と別のディレクトリにある場合

test_runnerのラッパーを作成して実行してみる(run.sh)。

ディレクトリ構成

ディレクトリ構成は以下のとおり。
テストは、shunit2とは別のディレクトリにあるとする。

/home/guest/shunit2
 |- shunit2
 |- lib
 |- test_runner
 ...

/home/guest/mytests
  |- my_test.sh
  |- run.sh

/home/guest/mytests/run.sh

test_runnerを実行するためだけのラッパー。

#!/usr/bin/env bash

export SHUNIT2_ROOT=${SHUNIT2_ROOT:-/home/guest/shunit2}
export LIB_DIR="$SHUNIT2_ROOT/lib"
export SHUNIT_INC="$SHUNIT2_ROOT/shunit2"

"$SHUNIT2_ROOT"/test_runner "$@"

/home/guest/mytests/my_test.sh

テストプログラムは、shunit2を環境変数で変えられるように作成しておく。

#! /bin/sh

. "$SHUNIT2_ROOT"/shunit2_test_helpers

testEquality() {
  assertEquals 1 1
  assertEquals "$HOGE" "foo"
}

. "${TH_SHUNIT}"

以下では、シェルは/bin/sh、テスト対象はmy_test.sh、環境変数としてHOGE=fooを定義して実行している。

$ cd /home/guest/mytests
$ bash run.sh -s /bin/sh -t my_test.sh -e HOGE=foo

#------------------------------------------------------------------------------
# System data.
#

$ uname -mprsv
Linux 3.10.0-1062.9.1.el7.x86_64 #1 SMP Fri Dec 6 15:49:49 UTC 2019 x86_64 x86_64

OS Name: Linux
OS Version: CentOS Linux 7 (Core)

### Test run info.
shells: /bin/sh
tests: my_test.sh
HOGE=foo


#------------------------------------------------------------------------------
# Running the test suite with /bin/sh.
#
shell name: sh
shell version: GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)

--- Executing the 'my' test suite. ---
testEquality

Ran 1 test.

OK

サンプル filetest_runner_sample.zip

suite(独自にテスト関数を指定して実行)

suiteを用いるとsuite_addTestで任意の関数をテスト対象に指定することができる。
しかし、ソースコメントを見る限り2.1.0の時点でdeprecatedとなっている。

suiteで独自のテスト関数を実行する

suite_test.sh

#! /bin/sh

my_test1() {
    assertEquals 1 1
}

my_test2() {
    assertTrue "[ 1 -eq 1 ]"
}

suite() {
    suite_addTest "my_test1"
    suite_addTest "my_test2"
}

. ../shunit2/shunit2

実行。

$ bash suite_test.sh
my_test1
my_test2

Ran 2 tests.

OK

注意

suite_addTestが設定されると、デフォルトの "test" を接頭辞にもつ関数の自動的なテストは行われないことに注意。
これは、テスト対象が存在しない場合にのみtestを接頭辞にもつテスト関数を探索するようになっているためである。

参考

特徴・機能

詳細はドキュメントを参照のこと。
動作で留意しておくべき点のみピックアップする。

oneTimeSetUp、oneTimeTearDown

oneTimeSetUpで失敗した場合

oneTimeSetUp() {
    echo "oneTimeSetUp"
    return 1
}

oneTimeTearDown() {
    echo "oneTimeTearDown"
}

setUp() {
    echo "setUp"
}

tearDown() {
    echo "tearDown"
}

testMy() {
    echo "testMy"
    assertEquals 1 1
}

testMy2() {
    echo "testMy2"
    assertEquals 1 1
}

. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash study_test.sh
oneTimeSetUp
shunit2:FATAL oneTimeSetUp() returned non-zero return code.
tearDown
oneTimeTearDown
ASSERT:unknown failure encountered running a test

Ran 0 tests.

FAILED (failures=1)

oneTimeTearDown で失敗した場合

当然ながらテストは実行された後のcleanupで失敗となる。

oneTimeSetUp() {
    echo "=> oneTimeSetUp"
}

oneTimeTearDown() {
    echo "=> oneTimeTearDown"
    return 1
}

setUp() {
    echo "=> setUp"
}

tearDown() {
    echo "=> tearDown"
}

testMy() {
    echo "=> testMy"
    assertEquals 1 1
}

testMy2() {
    echo "=> testMy2"
    assertEquals 1 1
}

. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash study4_test.sh
=> oneTimeSetUp
=> setUp
testMy
=> testMy
=> tearDown
=> setUp
testMy2
=> testMy2
=> tearDown
=> oneTimeTearDown
shunit2:FATAL oneTimeTearDown() returned non-zero return code.
=> tearDown
=> oneTimeTearDown
shunit2:WARN oneTimeTearDown() returned non-zero return code.
ASSERT:unknown failure encountered running a test

Ran 2 tests.

FAILED (failures=1)

setUp、tearDown

setUp関数がエラーの場合

oneTimeSetUp() {
    echo "oneTimeSetUp"
}

oneTimeTearDown() {
    echo "oneTimeTearDown"
}

setUp() {
    echo "setUp"
    return 1
}

tearDown() {
    echo "tearDown"
}

testMy() {
    echo "testMy"
    assertEquals 1 1
}

testMy2() {
    echo "testMy2"
    assertEquals 1 1
}

. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash study2_test.sh
oneTimeSetUp
setUp
shunit2:FATAL setup() returned non-zero return code.
tearDown
oneTimeTearDown
ASSERT:unknown failure encountered running a test

Ran 2 tests.

FAILED (failures=1)

tearDown関数がエラーの場合

なお、以下ではtestMyは失敗するようにしている。

oneTimeSetUp() {
    echo "oneTimeSetUp"
}

oneTimeTearDown() {
    echo "oneTimeTearDown"
}

setUp() {
    echo "setUp"
}

tearDown() {
    echo "tearDown"
    return 1
}

testMy() {
    echo "Run testMy"
    assertEquals 1 2
}

testMy2() {
    echo "Run testMy2"
    assertEquals 1 1
}

. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash study3_test.sh
oneTimeSetUp
setUp
testMy
Run testMy
ASSERT:expected:<1> but was:<2>
shunit2:ERROR testMy() returned non-zero return code.
tearDown
shunit2:FATAL tearDown() returned non-zero return code.
tearDown
shunit2:WARN tearDown() returned non-zero return code.
oneTimeTearDown
ASSERT:unknown failure encountered running a test

Ran 2 tests.

FAILED (failures=3)

Skipping

assertのfailの実行と記録を行わない。
startSkippingとendSkippingでスキップ対象範囲を囲む。

メモ
これらの関数は内部的にスキップフラグをON/OFFにしているだけあり、assertやfail系関数の実行時にこのフラグが参照され、assertやfailを実行するか否かを判断している。

スキップの例

testSkip() {
#    startSkipping
    echo "This message will be printed"
    assertEquals 1 1
#    endSkipping
    assertEquals 1 1
}


. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash skip_test.sh
testSkip
This message will be printed

Ran 1 test.

OK

Skipを有効にすると以下のとおり。

$ bash skip_test.sh
testSkip
This message will be printed

Ran 1 test.

OK (skipped=1)

レポート

レポートで出力される内容は、以下のとおりである。
数値が何を意味しているのかは少し注意が必要(failures)である。

表示意味
Ran n testsnは、テスト対象となったテスト関数の数。テスト開始時に決まる。
skipped=assertやfailがスキップされた数
failures=失敗した実行数(assert、fail、テスト)

Ran n testsのnは、テスト対象関数として定義された時点でのテスト数である。
実行されていなくとも、setUp関数で失敗してもRan n testsと表示されるのはそのためである。

実験 テスト実行前にテスト終了させる場合のレポート

通常はこのような事をしないので、あくまで実験である。

my_test1() {
    assertEquals 1 1
}

my_test2() {
    assertTrue "[ 1 -eq 1 ]"
}

suite() {
    suite_addTest "my_test1"
    suite_addTest "my_test2"
    exit # ここで停止してもRan 2 testsとなるはず
}

. ../shunit2/shunit2

実行すると以下のように Ran 2 testsとなる。

bash suite_test.sh
ASSERT:unknown failure encountered running a test

Ran 2 tests.

FAILED (failures=1)

メモ テストの実行プロセス

テストの実行プロセスは大まかに以下のとおり。

  1. テスト対象ファイルの認識
  2. oneTimeSetUpの実行
  3. suiteがあればsuiteを実行。もしくは、<file> 関数名...が指定されて実行された場合は、shunit2が面倒を見てくれる。
    (suiteは実行されない)
  4. 登録済みのテスト対象関数がなければ、shunit2がtestの接頭辞のテスト対象関数を抽出
  5. テストスイートの実行
  6. oneTimeTearDownの実行
  7. テストレポートの出力


補足 詳細なテストレポート

assertの数など少し詳細な情報が欲しいかもしれない。
内部的には統計情報を持っているが、レポート生成関数では参照されていないようであった。

以下のようにすれば内部の情報も見ることができた。
(他に正当なやり方があるかもしれないが調べる限り見つからなかった)

test_test1() {
    startSkipping
    fail "This is not counted"
    assertEquals 1 1
    endSkipping
}

test_test2() {
    assertTrue "[ 1 -eq 1 ]"
    fail "hoge"
    assertTrue "[ 1 -eq 2 ]"
}


_DETAIL_REPORTED=0

oneTimeTearDown() {
    if [ $_DETAIL_REPORTED -ne 1 ]; then
        echo "=== Detail Report ==="
        echo "testsTotal=${__shunit_testsTotal}"    # Run n tests の n
        echo "testsPassed=${__shunit_testsPassed}"
        echo "testsFailed=${__shunit_testsFailed}"

        echo "assertsTotal=${__shunit_assertsTotal}"
        echo "assertsPassed=${__shunit_assertsPassed}"
        echo "assertsFailed=${__shunit_assertsFailed}"    # failures=の値
        echo "assertsSkipped=${__shunit_assertsSkipped}"  # skipped=の値
        echo "======"
        _DETAIL_REPORTED=1
    fi
}

. ../shunit2/shunit2

実行結果は以下のとおり。

$ bash suite2_test.sh
test_test1
test_test2
ASSERT:hoge
ASSERT:
shunit2:ERROR test_test2() returned non-zero return code.
=== Detail Report ===
testsTotal=2
testsPassed=1
testsFailed=1
assertsTotal=6
assertsPassed=1
assertsFailed=3
assertsSkipped=2
======

Ran 2 tests.

FAILED (failures=3,skipped=2)

各表示の意味は以下のとおり。

表示意味
testsTotalテスト数、suite_addTestされたテスト関数の数
testsPassedテストにパスした数、テスト関数単位
testsFailedテストに失敗した数、テスト関数単位
assertsTotalskipを含む、assert、failの数
assertsPassedassertにパスした数
assertsFailedassert失敗、fail、テストに失敗した数
assertsSkippedスキップされたassert、failの数

assertsFailedの数が見た目とずれている気がするかもしれない。
これは、assertやfailの失敗以外もfailedとしてカウントされているためである。

https://github.com/kward/shunit2/issues/117

test_test2() {
    assertTrue "[ 1 -eq 1 ]"
    fail "hoge"
    assertTrue "[ 1 -eq 2 ]"
    # 上記のassertで失敗するため、テスト関数もエラー終了となり、assertFailedもインクリメントされる。
}

以下のようにすれば期待する結果となる。

test_test2() {
    assertTrue "[ 1 -eq 1 ]"
    fail "hoge"
    assertTrue "[ 1 -eq 2 ]"
    :
    # return 0 でもOK
}


実験 suiteの途中で不意に終了した例

もう1つassertFailedに関して実験してみる。
assertFailedがfailuresに含まれる例である。

#! /bin/sh

my_test1() {
    assertEquals 1 1
}

suite() {
    suite_addTest "my_test1"
    exit
}

_DETAIL_REPORTED=0

oneTimeTearDown() {
    if [ $_DETAIL_REPORTED -ne 1 ]; then
        echo "=== Detail Report ==="
        echo "testsTotal=${__shunit_testsTotal}"
        echo "testsPassed=${__shunit_testsPassed}"
        echo "testsFailed=${__shunit_testsFailed}"

        echo "assertsTotal=${__shunit_assertsTotal}"
        echo "assertsPassed=${__shunit_assertsPassed}"
        echo "assertsFailed=${__shunit_assertsFailed}"
        echo "assertsSkipped=${__shunit_assertsSkipped}"
        echo "======"
        _DETAIL_REPORTED=1
    fi
}
. ../shunit2/shunit2

残念ながらこの結果からは、__shunit_assertsFailed1にはならない。
oneTimeTearDownが実行される段階では、__shunit_assertsFailedがインクリメントされていないからである。

$ bash suite4_test.sh
=== Detail Report ===
testsTotal=1
testsPassed=0
testsFailed=0
assertsTotal=0
assertsPassed=0
assertsFailed=0
assertsSkipped=0
======
ASSERT:unknown failure encountered running a test

Ran 1 test.

FAILED (failures=1)

実行コンテキスト

shUnit2のテスト実行コンテキストは、テストファイル内の各テストで共通である。
つまり、各テストはサンドボックス内で実行されていないため、他のテストに影響しうるということである。

例を見てみよう。
極端な例ではあるが、イメージが掴めると思う。

実験 shunit2のテスト実行コンテキスト

~/bin/ls

$ cat ~/bin/ls
#!/usr/bin/env bash
echo "myls"

sandbox_test.sh

test_no1() {
    export PATH="~/bin":"$PATH"
    result=$(ls)
    assertEquals "$result" "myls"
}

test_no2() {
    touch "test.txt"
    result=$(ls "test.txt")
    assertEquals "$result" "test.txt"
}

source ../shunit2/shunit2

実行すると以下のようになる。

$ bash sandbox_test.sh
test_no1
test_no2
ASSERT:expected:<myls> but was:<test.txt>
shunit2:ERROR test_no2() returned non-zero return code.

Ran 2 tests.

FAILED (failures=2)

2番目のテストtest_no2で失敗していることが分かる。
これは、test_no1で実行したexport PATHtest_no2でも生きているということである。
テスト実行のコンテキストで、evalを使ってテスト関数を実行しているためである。
あるテストで行なった他に影響し得る変更は、テストの最後でリセットするようにすればよい。

例えば、Bashで書かれたテストフレームワークであるbatsでは、各テストがサンドボックス内(サブシェル)で実行されるため、あるテストで定義した変数や環境変数等の定義は、他のテストには影響しない。

参考リンク


添付ファイル: filetest_runner_sample.zip 59件 [詳細]

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP