ロボットシステム学

第6回: ソフトウェアのテスト

千葉工業大学 上田 隆一

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. Creative Commons License

ロボットシステム学第6回

おさらいと今日やること

  • おさらい: これまで説明してきたシェルの役割
    • ユーザーから文字を受け取ってコマンドを実行する
    • コマンドを見つける(PATH
  • 今日やること: シェルのプログラミング言語としての側面を学習
    • コマンドを関数のように組み合わせて制御
    • ファイルにコマンドを動かす手順(シェルスクリプト)を書く
    • シェルスクリプトを使ってplus_stdinが正しいかどうかをテスト
ロボットシステム学第6回

1. シェルの変数

ロボットシステム学第6回

基本事項

  • シェルは,変数名=文字列で,文字を記憶
    ### 例 ###
    $ X=我々は宇宙人だ   # 変数のセット
    
  • ${変数名}で値に置換
    ### 例 ###
    $ echo ${X}ぜ!      # 変数を値に
    我々は宇宙人だぜ!
    $ echo $X            # {}は省略できる(こともある)
    我々は宇宙人だ
    $ echo '$X' # 値にしないときはシングルクォートで囲う(``と混同注意)
    $X
    
    ### {} が省略できない例 ###
    $ echo ${X}Z
    我々は宇宙人だZ
    $ echo $XZ
                            #変数「XZ」と解釈されるのでなにも出力されない
    
ロボットシステム学第6回

コマンドの出力を変数に

  • 変数名=$(コマンド)
  • 通常は標準出力に出るコマンドの出力を文字列として変数に格納
  • 例: lsの出力を変数A
    $ A=$(ls)
    $ echo $A
    README.md plus_stdin  # 注意: 出力は各自異なります.
    
ロボットシステム学第6回

2. コマンドの終了ステータス

ロボットシステム学第6回

コマンドによる正常終了・異常終了の伝達

  • コマンドは人に対してだけでなく,シェルにエラーの有無を伝達
  • 方法
    • 終了ステータスと呼ばれる整数値で伝達
      • シェルは?というパラメータにコマンドの終了ステータスを記録
    • 例: lsの出力
      $ ls /etc/passwd   # 存在するファイルをls
      /etc/passwd
      $ echo $?          # ?に$をつけると値に置き換わる
      0                  # ?の値はゼロ
      $ ls aaaaaaaa      # 存在しないファイルをls
      ls: 'aaaaaaaa' にアクセスできません: そのようなファイルやディレクトリはありません
      $ echo $?
      2                  # ゼロでない値が入る.
      
ロボットシステム学第6回

plus_stdinの終了ステータス

  • これまで書いてきたplus_stdinも終了ステータスをシェルに伝達
    • Pythonが裏でやっているので,任せておいて問題ない
    $ seq 3 | ./plus_stdin    # 正常な入力
    6
    $ echo $?
    0
    $ echo あ | ./plus_stdin  # ひらがなを入力してエラーを起こさせる
    (エラーの表示.省略.)
    $ echo $?
    1
    
なんのために終了ステータスがあるか?→あとで説明
ロボットシステム学第6回

3. テストコマンド

  • 以下のいずれかで,文字列を比較可能
    • その1: test 比較したい文字列 = 比較したい文字列
    • その2: [ 比較したい文字列 = 比較したい文字列 ]
    • その3: [[ 比較したい文字列 = 比較したい文字列 ]]
    • 例(その1〜3の違いには細かい話はありますが,  ここでは「その2」だけ)
      $ a=山田
      $ [ "$a" = 上田 ]       # [ はコマンドなので,引数はくっつけないようにしましょう.
      $ echo $?               #終了ステータスで確認
      1
      $ [ "$a" = 山田 ]
      $ echo $?
      0
      
ロボットシステム学第6回

4. 簡単なシェルスクリプトの記述

  • コマンドの起動手順をファイルに書いてみましょう
    • 我々がこれから書くのはBashのスクリプト
      (Bashはシェルなので,シェルスクリプトの一種)
    • ここからはおそらく一気に覚えられないので見様見真似で書いて実行
    • 読めるようにはなってください
ロボットシステム学第6回

シェルスクリプトを作ってみる

  • テストコマンドの例をファイルに保存して実行してみましょう
  • test.bash」というファイルに,次のように記述
    #!/bin/bash
      
    a=山田
    [ "$a" = 上田 ]       
    echo $?               
    [ "$a" = 山田 ]
    echo $?
    
  • chmodしてPythonのスクリプトと同様に実行
    $ chmod +x test.bash
    $ ./test.bash
    1
    0
    
ロボットシステム学第6回

5. シェルの関数

  • 基本的な関数の書き方: 名前のうしろに()をつけて,{}の中に処理を記述
  • n引数を${n}で受け取り
    • 例: ngという関数を作り、呼び出してみる(ファイル名はtest.bash
    #!/bin/bash
     
    ng () {
            echo ${1}行目が違うよ  #$1はngの1番目の引数
    }
     
    ng 123
    
    • 実行
    $ chmod +x ./test.bash
    $ ./test.bash
    123行目が違うよ
    
ロボットシステム学第6回

コマンドと関数の連携

  • [が失敗したらngを呼び出す
    • 例(ファイル名: test.bash
    #!/bin/bash
     
    ng () {
            echo ${1}行目が違うよ
            res=1                   #追加
    }
     
    res=0
    a=山田
    [ "$a" = 上田 ] || ng "$LINENO"  # LINENOは,この行の行番号の入る変数
    [ "$a" = 山田 ] || ng "$LINENO"  # ngに第一引数として$LINENOを付与
     
    exit $res     # このシェルスクリプトの終了ステータスを返して終了
    
    • ||(OR記号): 左側のコマンドが異常終了したら右側を実行
ロボットシステム学第6回

実行結果

$ ./test.bash
10行目が違うよ
$ echo $?
1
ロボットシステム学第6回

6. 初歩的なテスト

やっと本題

ロボットシステム学第6回

ここで言うテストとは

  • プログラムが意図通りに動作するかを別のプログラム(テストコード)を書いてテストすること
  • 次のようなテストコードを書いて実行
    1. テスト対象のコードに引数や標準入力からデータを入力
    2. 出力を記録
    3. 期待した出力と一致するか比較
面倒だと思いますか❓
ロボットシステム学第6回

テストがないと辛い

  • 例: 「プログラムに機能をどんどん追加していったら,最初に書いた部分がうまく動かなくなった!」
    • →こまめにテストを実行すれば防止可能
      • 転ばぬ先の杖

リグレッションテスト

  • 上記のようなテストを特に「リグレッションテスト(退行テスト)」と言う.これからそれをやる.
ロボットシステム学第6回

こういう開発スタイルに早く移行しましょう

  1. こまめにテスト
  2. テストに通ったらGitにコミット
  3. テストに失敗して,原因不明なら前回のコミットに退却
  • git restore
  1. さらに細かくテスト

予告: 最初の課題ではこのような経緯の分かるリポジトリを各自提出してもらいます!(あとから慌てても無理.コピー不可能)

ロボットシステム学第6回

コマンドに対するリグレッションテストの記述

  • 準備
    1. plus_stdinplusに改名しておくコミットを
      • 注意: テストと無関係です.
    2. さきほどのtest.bashをリポジトリ直下(plusと同じところ)に置いてコミットしてGitHubにpush
  • 次のようにtest.bashを変更(次ページ)
ロボットシステム学第6回
 1 #!/bin/bash
 2           ### 省略しますが、ここに前回学んだ著作権やライセンスの設定を!!
 3 ng () {
 4         echo ${1}行目が違うよ
 5         res=1
 6 } 
 7
 8 res=0    
 9 
10 out=$(seq 5 | ./plus)
11 [ "${out}" = 15 ] || ng "$LINENO"
12      
13 [ "${res}" = 0 ] && echo OK #通ったのが(人間に)分かるように表示
14 exit $res
  • &&(AND): 左のコマンドの終了が正常なら右側を実行
ロボットシステム学第6回

動作確認

### 実行権限をお忘れなく ###
$ ./test.bash 
OK
$ echo $?
0
  • plusなどへの入力を変えるなどの方法でecho $?1と出ることも確認
  • なんで判定に終了ステータスを使うのかは次回判明
動作確認が終わったら再度テストが通るように戻してGitHubにpush
ロボットシステム学第6回

シェルスクリプトの挙動の確認

  • -x-vでシェルスクリプトの動作を観察可能(デバッグに便利)
    #!/bin/bash -xv        <- シバンの後ろに-xvと書いて-xと-vをセット
    (以下略)
    
    • 動作確認
      ・・・省略・・・
      out=$(seq 5 | ./plus)
      ++ seq 5
      ++ ./plus
      + out=15
      [ "${out}" = 15 ] || ng "$LINENO"
      + '[' 15 = 15 ']'
      
      [ "${res}" = 0 ] && echo OK
      + '[' 0 = 0 ']'
      + echo OK
      OK
      exit $res
      + exit 0
      
ロボットシステム学第6回

テスト項目の追加

  • 誤動作を防ぐため、なるべく様々な状況をカバーしておく
    #!/bin/bash -xv
    (略)
    ### NORMAL INPUT ###
    out=$(seq 5 | ./plus)
    [ "${out}" = 15 ] || ng "$LINENO"
        
    ### STRANGE INPUT ###
    out=$(echo あ | ./plus)           #計算できない値を入力してみる
    [ "$?" = 1 ]      || ng "$LINENO" #終了ステータスが1なのを確認
    [ "${out}" = "" ] || ng "$LINENO" #この行と上の行は入れ替えるとダメです
                                             #(↑なぜかは考えてみましょう)
    out=$(echo | ./plus)              #なにも入力しない
    [ "$?" = 1 ]      || ng "$LINENO" #これも異常終了する
    [ "${out}" = "" ] || ng "$LINENO"
        
    [ "$res" = 0 ] && echo OK
    exit $res
    
ロボットシステム学第6回

まとめ

  • シェルスクリプトを記述
  • 終了ステータス,変数,テストコマンド,関数
  • リグレッションテストの記述
  • プログラムと一緒に少しずつ書いていく
  • ROSのように標準入出力を使わないもののテストについては第11回で
ロボットシステム学第6回