🎸

シフトレフトと同値分割:早期テストで発見する要件・設計の不備

2024/05/06に公開

シフトレフトとテストの重要性

https://youtu.be/mL5_c6uJzgw

なぜC1カバレッジを採用するのか?:ホワイトボックスがブラックボックスを照らすの執筆を通じて、ブラックボックステストのテストケースを可能な限り早期に作成することの重要性を再認識しました。これは「シフトレフト」というアプローチの一環です。
※シフトレフトについては、後日別の記事で詳しく取り上げたいと考えています。

要件が明確化された段階で、直ちに受け入れ条件(テスト条件)を定義すべきです。もし条件を定義できなければ、要件のさらなる明確化を求めるべきです。このようなコミュニケーションが非常に重要です。

設計書が完成した段階で、テストケースの作成を開始することができます。テストケースを作成できない場合は、設計の詳細を再確認し、必要な情報を明確にすることが求められます。このプロセスを通じて、不明瞭な要件や設計に対してフィードバックを提供し、ドキュメントの精度を向上させることが可能です。

以下は、なぜC1カバレッジを採用するのか?:ホワイトボックスがブラックボックスを照らすを執筆する過程で得た洞察を共有します。

初期の仕様定義

想定される機能は、年齢を入力する画面での処理を示しています。具体的な仕様は以下の通りです。

  • 数値を入力したとき、18歳以上であれば"OK"を返します。
  • 数値を入力したとき、18歳未満であれば"NG"を返します。

初期の段階では「数値以外の入力」を考慮に入れていませんでした。

初期のソースコードとテストコード

初期のソースコードは単純で、年齢のチェックのみを行っていましたが、「数値以外の入力値」に対する対応は含まれていませんでした。このため、型不一致によるエラーの可能性がありました。

def user_message(age):
    if age >= 18:
        return "OK"
    else:
        return "NG"

初期のテストコードは以下の通りです。このコードは正しい数値入力に対する反応のみをテストしており、数値以外のケースは考慮されていませんでした。

def test_user_message():
    assert user_message(17) == "NG", "18歳未満は'NG'を返すべき"
    assert user_message(18) == "OK", "18歳以上は'OK'を返すべき"

ブラックボックスとホワイトボックステストの統合を目指して同値分割を実施

ブラックボックステストとホワイトボックステストの相互関連を探るため、同値分割法を使用して、数値と非数値のデータタイプでパーティションを分けて考えました。このテストプロセスを通じて気づいたのは、「数値以外の入力」を考慮していないことでした。これは、型エラーを引き起こす可能性があることを意味し、プログラムが予期しない挙動を示す可能性があります。

有効な数値入力(年齢)

  • 18歳以上(例: 18歳、30歳)
  • 18歳未満(例: 17歳、0歳)

無効な入力

  • 数値以外(例: 文字列 "eighteen"、空白、特殊文字 "@")

このテストプロセスを通じて気づいたのは、「数値以外の入力」を考慮していないことでした。この初期のソースコードは文字列入力に対応しておらず、以下のコード行で型エラーを引き起こす可能性があります。これにより、"OK" または "NG" といった予期された出力が得られず、プログラムが予期しない挙動を示す可能性があります。

if age >= 18:

正しいエラーハンドリングが行われていないため、予期しないエラーが発生しやすくなります。この点を改善することで、より堅牢なソフトウェアを構築できるようになります。

アップデートされたテストコード

以下のテストコードを実行すると、型エラーにより予期せぬ結果を防ぐことができます。期待される出力は"Error"ですが、適切な型チェックが行われていない場合は、型エラーが発生する可能性があります。

def test_user_message():
    assert user_message("eighteen") == "Error", "非数値入力は'Error'を返すべき"
    assert user_message(17) == "NG", "18歳未満は'NG'を返すべき"
    assert user_message(18) == "OK", "18歳以上は'OK'を返すべき"

アップデートされたソースコード

数値以外の入力に対応するため、型チェックを導入しました。これにより、数値以外の入力が関数に渡された場合、明示的に"Error"を返すようになります。これにより、関数のロバスト性が向上し、予期しないエラーから保護されます。

def user_message(age):
    if not isinstance(age, (int)):
        return "Error"
    if age >= 18:
        return "OK"
    else:
        return "NG"

アップデートされた仕様

仕様をアップデートして、「数値以外の入力でエラーを返す」という条件を追加しました。これにより、関数の挙動がより明確になり、ユーザーからの無効な入力に対しても適切に対応できるようになります。

  • 入力は数値のみ、数値でなければ"Error"を返します。
  • 数値を入力したとき、18歳以上であれば"OK"を返します。
  • 数値を入力したとき、18歳未満であれば"NG"を返します。

テスト技法・同値分割によって要件・設計不備を発見する

今回の例で明らかになったのは、テストコードを書いた後、同値分割法を用いてテストケースを考える過程で不明確な要件に気づいたことです。もしソースコードを書く前に同値分割法や境界値分析を行っていたならば、その時点で不明瞭な要件を特定し修正できた可能性が高いです。

このアプローチを取っていれば、最初から型チェックを考慮したソースコードを書くことができ、ソースコードやテストコードの後からの修正が不要になるため、開発の効率化にも寄与します。

Discussion