2008-01-25

シェルスクリプト - テスト支援

今、シェルの開発をやっているんだけれども、テストが面倒くさい。で、楽できるようにテスト支援シェルみたいなのを作った。以外に役に立ったし気に入ったので、たまにメンテナンスしつつ使っていきたいなあとか思っていたけど、お馴染みのセキュリティ云々で持ち出すことが出来ない。せっかくだから何か残しておきたいと思うので、作るときのノウハウ的なものをメモ。ちなみにブラックボックス。

  1. テスト時の準備オペレーションを関数化
  2. 異常系のコードを走らせるために、シェルの実行環境とかをいじる必要があったりする。 そういう場合のオペレーションを全部関数化しておく。例えば以下のようなものとか。

    • 設定ファイルなどをリネームする。
    • DBに対してSQLを発行する。
    • DBに対して時間差でSQLを発行する。
    • 任意のディレクトリにごみファイルを作る。
    • 設定ファイルなどの内容を書き換える。
    • 共通系モジュールなどのリターンコードを任意の値に変更する。
    • ファイル・ディレクトリのパーミッションを設定する。
    • などなど。

    あと、これらの関数で変更した内容を元に戻すための仕組みも用意しておく。例えば、リネームだと

    UNIQUE_ID=`date +%Y%m%d%H%M%S`${$}  # どこかでユニークなIDを用意しておく。
    function rename {
      _ORIGINAL=${1}
      mv ${_ORIGINAL} ${_ORIGINAL}${UNIQUE_ID}
      RECOVER="${RECOVER}:mv ${_ORIGINAL}${UNIQUE_ID} ${_ORIGINAL}"
    }
    
    function recover {
      IFS=":"
      for COMMAND in ${RECOVER}; do
        eval ${COMMAND}
      done
      RECOVER=""
    }
    

    とか。(変更内容を戻したいときは、${RECOVER}に登録されたコマンドを実行する。)

    とにかく、準備オペレーションをうまいこと関数化しておき、簡単にあるケースに特化した試験環境を作れるようにしておく。ここでどれだけナイスな関数を作っておくかで試験の効率化具合が決まる。

  3. シェルの実行を関数でラップ
  4. ラッパーの中で、以下の機能を実現する。

    • 戻り値のチェック。
    • 準備オペレーションの内容を元に戻す。
    • テストケース毎に一時停止する。

    こんなかんじ。

    function exec_command {
      _COMMAND=${1}  # 実行コマンド
      _EXPECT=${2}   # 期待する戻り値
      echo ${_COMMAND}
      eval ${_COMMAND}
      _RC=${?}
      if [ ${_RC} -ne ${_EXPECT} ]; then
        echo "No good. Return code => ${_RC}"
        exit 1
      else
        echo "Good. Return code => ${_RC}"
      fi
      read
      recover
      clear
    }
    

    個人的に一番重要なのは、「テストケース毎に一時停止する。」。「テストシェルに通ったのでOKです!」で通じる相手なんてまれで、「シェルをコマンドラインから投入した実行結果のハードコピーが欲しい。」なんてことが多いので、準備オペレーションの内容を元に戻す前で一時停止させる。この仕組みを作っておけば、2個ターミナルを上げて、片方のターミナルでテストシェルを流し、もう片方でハードコピー取得用にコマンドラインからシェルを実行する。ということが楽に出来る。

で、最後に使い方のイメージをのせておくと、
#!/bin/bash

UNIQUE_ID=${$}`date +%Y%m%d`

#----------------------------------
# 試験環境準備オペレーション用関数
#----------------------------------
function rename {
  _ORIGINAL=${1}
  mv ${_ORIGINAL} ${_ORIGINAL}${UNIQUE_ID}
  RECOVER="${RECOVER}:mv ${_ORIGINAL}${UNIQUE_ID} ${_ORIGINAL}"
}

function recover {
  IFS=":"
  for COMMAND in ${RECOVER}; do
    eval ${COMMAND}
  done
  RECOVER=""
}

#------------------
# シェル実行用関数
#------------------
function exec_command {
  _COMMAND=${1}  # 実行コマンド
  _EXPECT=${2}   # 期待する戻り値
  echo ${_COMMAND}
  eval ${_COMMAND}
  _RC=${?}
  if [ ${_RC} -ne ${_EXPECT} ]; then
    echo "No good. Return code => ${_RC}"
    exit 1
  else
    echo "Good. Return code => ${_RC}"
  fi
  read
  recover
  clear
}

trap recover EXIT

# こっからテストを書く。
rename foo
exec_command "/home/foo/bin/test.sh -c hoge" 0

rename hoge
rename foo
exec_command "/home/foo/bin/test.sh -c hoge" 0

試験環境準備オペレーション用関数(ながい。)を別ファイルにまとめ、読み込んでから使うようにしてもいいし、他にも色々工夫できそう。どっかで使う機会があれば、使ってみよう。

Posted at 01:56 in | WriteBacks (2) | Edit