やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

単体テストを行うとコスト最小化できる

表題の件は修正コスト最小化するためにどうすればいいかの答えの1つと思われる。その証明を試みる。

目次

問題提起

単体テストを先送りするとコストが増大する。最小化するにはどうすればいいか?

結論

コード結合前にテストする。単体テストを行うとコスト最小化する。

  • 単体テストをせず公開すると、単体テストよりコストが大きい「不作為コスト」を支払うことになる
  • コード結合後にテストすると、単体テストよりコストが大きい「修正コスト」を支払うことになる

コスト一覧

コスト 説明
単体テストコスト 単体テストをすることで生じるコスト
修正コスト 単体テストを先送りすることで生じるコスト
不作為コスト 単体テストを行わないことで生じるコスト

各コストの中には文脈ごとに「学習コスト」が含まれる。これについては掘り下げない。

ケース コスト
単体テストする 単体テストコスト
単体テストせず結合後に動作確認する 修正コスト
単体テストせずリリース 修正コスト + 不作為コスト
単体テストせずリリースし印象操作 修正コスト + 不作為コスト + 捏造コスト

印象操作は半ば冗談。

コスト比較

コストはいくらか

コスト種別 コスト値
単体テストコスト ?
修正コスト ?
不作為コスト ?

定量化できない。コードを解析すれば入出力やルート数から単体テストコストの定量化ができるかもしれない。修正コストと不作為コストは影響範囲が広く項目も膨大なため定量化が難しい。

影響範囲の大小関係

単体テストコスト < 修正コスト < 不作為コスト

影響範囲の大小関係が上記であるといえるかどうかについては、コストの定性の各項を参照して見比べるとイメージできる。

影響範囲が広いほうがコストが大きいと言える。以下のことが必要になるから。

  • 各文脈での現状把握
  • 各文脈での準備と実行
  • 上記2つを行うために要する各文脈固有の知識や技術
    • その学習コスト

コストの定量化

コスト種別 コスト値
単体テストコスト 1
修正コスト 2
不作為コスト 3
捏造コスト 4

コストを上記のように仮定した。根拠は影響範囲の大小関係である。上流工程であるほど影響範囲が広いためコストがかさむと考えた。影響範囲の小さいものから大きいものへ順に値を割り当てた。数値の大きさに根拠はない。単純化するために自然数のうちで小さいものから順につけただけ。

ケース コスト コスト値
単体テストする 単体テストコスト 1
単体テストせず結合後に動作確認する 修正コスト 2
単体テストせずリリース 修正コスト + 不作為コスト 5 (2+3)
単体テストせずリリースし印象操作 修正コスト + 不作為コスト + 捏造コスト 9 (2+3+4)

単体テストする」が最小コストであると言える。

コストの定性

定性とは、対象の成分や種類を調べて定めることである。各種コストの内容を分析し、何がコストになっているかを列挙してみる。

単体テストコストの定性

以下の行為に要する労力と時間。

テストケースを考えるのが最もコストが大きい。テストコードを書くためにはライブラリの学習などが必要。実行はコマンドさえ打てればすぐできる。記録はGitHubにアップロードすればOK。

「テストケースを考える」のコスト定性

  • テストケースを考える
    • 網羅
      • ルート
        • for, while
        • if
        • Exception
          • try-except
          • None Catch Exception
        • Abstract class implement pattern
      • インタフェース
        • 名前
        • アクセス修飾子
      • 入出力
        • return
        • parameter
          • 組合せ

これで網羅できるのか、それを証明できるのか不明。以下が参考になるかもしれないが情報量が多すぎる。

「テストコードを書く」のコスト定性

  • テストコードを書く
    • テスト用ライブラリの学習
    • テスト内容ごとに使い分ける

まだライブラリを学習していないから定性を洗い出せない。学習はリファレンスを見れば答えがあるため、考える必要はない。

「テストコードを実行する」のコスト定性

  • Pythonの場合
    • ターミナルを起動する
      • python3 -m unittest Test.pyコマンドを実行する

時間や労力の観点から言えば、たったこれだけ。他の観点では、実行までの待機時間、PCリソース消費、電力消費もあるが極めて軽微。

「テストコードを記録する」のコスト定性

  • ローカルPCに記録する場合:
    • コストはゼロ。余計な労力や時間はない。強いて言えば保存するときCtrl+Sキーを押下すること
  • 外部サーバに記録する場合:
    • アップロードするコスト

自動化できる可能性があるくらいには軽微。

修正コストの定性

コード結合後に不具合が発見された場合に生じる。単体テストしていれば発生しないコストである。

  • 修正箇所の特定
    • 結合後の膨大で複雑なコードから問題箇所を特定せねばならない
      • 複数ファイルの中から探して追う時間が発生する
        • コード・リーディングの時間が長くなる
  • テスト
    • 結合後の実行に必要な入力(テストデータや条件等)を準備せねばならない
    • 結合後のコードを実行するだけの実行時間とPCリソースを要する
      • テスト実行回数だけ同コストが生じる
  • ソースコード修正
    • 誤って他のファイルや箇所を修正してしまう可能性が生じる
    • 関数名などインタフェースが修正された場合
      • すべての呼出元を修正せねばならない
        • 結合後なので呼出箇所は複数あるはず
          • 文字列置換で自動化できるかもしれないが誤置換して不具合を作りこむ可能性もある
    • return内容など出力結果が修正された場合
      • 呼出元すべての実装を修正せねばならない
  • コンパイル
    • 単体でなく結合したコード全体を再コンパイルせねばならない
      • 実行時間とPCリソースを要する
  • 再テスト
    • テストコードがないため、再びテストするには手でテスト状況を用意して実行せねばならない
      • テストしたいと思うたび同コストが生じる
  • 再配布
    • 修正した回数だけ以下の作業が繰り返される
      • アップロード
      • 周知
    • バージョン管理が必要になる
      • もしくは過去版を入手不能にする

単体テストコストと比べて影響範囲が広い。1項目あたり多くの作業内容を含んでいる。どれも価値がない不毛な作業である。

不作為コストの定性

単体テストをしないことで生じるコストは以下である。単体テストしていれば発生しなかった。

  • 不具合がなかった場合:
    • 不具合の存在有無について証明する根拠がない
      • 品質を保証する根拠がない
        • 導入されにくい
          • 利用されにくい
            • 価値なきコードになりやすい
  • 不具合があるのに発見できなかった場合:
    • 利用後に不具合が発覚する
      • 利用者が開発者に問い合わせる作業が生じる
        • 開発者は利用者から再現手順を聞き取る作業が生じる
        • 開発者は修正コストを支払うことになる
    • テストケースの抜けがどれであるかテストコードとして記録できない
      • 問題も対処も記録されない
        • コストの存在に気づけずコスト発生原因を解消できない
          • 同じ過ちがくりかえされる
            • コストは知らぬ間に雪だるま式に増大
              • 開発は遅延、停滞、継続不能、縮小、消滅に追い込まれる

修正コストと比べて影響範囲が広い。致命的なのはコードの存在価値にまで波及していること。もはや作業コストの大小では済まない。不作為コストは極めて甚大で致命的である。

また、この不作為コストの定性(単体テストしないことで生じるコストの定性)は「単体テストすることで生じる価値」の存在を証明する。単体テストをすれば「不具合がないこと」を証明する証拠ができる。「価値なきコードである」という結末になることを回避しうる。

まとめ

単体テストすることでコスト最小化する。また、テストコードのおかげで品質保証ができ、コードの信頼性や価値を高める。単体テストする価値は大きい。それに比べて単体テストしない不作為コストは致命的である。そのコードの存在価値を危機にさらす。単体テストを先送りし、コード結合後に行うと修正コストを要する。コード修正範囲の増大やテスト条件の構築作業増大などが起きる。クラスや関数を作ったら、次はその単体テストをすぐ行うことでコスト最小化になる。

この証明で得たもの

私はこれまで何となく、単体テスト自体がコストのかかる大変な作業だと思っていた。しかしそれ以上に大きいコストを削減していることが判明した。「単体テストは面倒なもの」という印象が変わった。「単体テストはコードの価値を証明する作業」だった。単体テストせずしてコードに価値なし。

私はこれまで印象によって意思決定していたことも明るみになった。それが余計なコストを抱える結果になってしまいうることも判明した。だからといって「常に定量と定性をはかり証明して合理的な判断を下すべき」とは言えない。そのためのコストがあるからだ。しかし作業の規模が大きくなると、それを「するコスト < しないコスト」になる。これを何となく感じたときに見直すとコスト最小化につながるかもしれない。この証明は他の問題について考えるときの参考資料にもなりうる。

謝辞

この証明をする発端となったプログラマが知るべき97のことに感謝。