広く使用されているテスト技法として、1 つのクラスが取りうる異なる抽象状態を分析する方法があります。 オブジェクトの状態は、通常はそのオブジェクトの属性値に関する制約として定義されます。 オブジェクトの状態によって、特定のメソッドに対する呼び出しが有効または無効になる場合、あるいは、メソッドの振る舞いが変わる場合があります。
一般的に、状態ベースのテスト技法の使用には以下のプロセスが含まれます。
これがどのように機能するかを確認するために、JUnit の MoneyBag クラスについて考察してみましょう。
class MoneyBag implements IMoney { private Vector fMonies= new Vector(5); public IMoney add(IMoney m) { return m.addMoneyBag(this); } public IMoney addMoney(Money m) { return MoneyBag.create(m, this); } public IMoney addMoneyBag(MoneyBag s) { return MoneyBag.create(s, this); } void appendMoney(Money aMoney) { if (aMoney.isZero()) return; IMoney old= findMoney(aMoney.currency()); if (old == null) { fMonies.addElement(aMoney); return; } fMonies.removeElement(old); IMoney sum= old.add(aMoney); if (sum.isZero()) return; fMonies.addElement(sum); } private Money findMoney(String currency) { for (Enumeration e=fMonies.elements();e.hasMoreElements();) { Money m= (Money) e.nextElement(); if (m.currency().equals(currency)) return m; } return null; } private boolean contains(Money m) { Money found= findMoney(m.currency()); if (found == null) return false; return found.amount() == m.amount(); } }
状態ベースのテスト技法を使用する場合の最初のステップは状態の定義です。 コードの 2 行目を見ると、MoneyBag クラスは 0 から 5 個の Money オブジェクトを保持できることが分かります。
private Vector fMonies= new Vector(5);
この分析から、次のような状態を持つ状態モデルを 作成できます。
この例では、以下の表に表示されているように、fMonies 属性に関して次のように制約を定義できます。
状態 | 制約 |
---|---|
EmptyBag | fMonies.size()==0 |
PartiallyFullBag | (fMonies.size()>0) && (fMonies.size()<5) |
FullBag | fMonies.size()==5 |
これらの状態の正式な定義は必須ではありませんが、このようにしておくと、テスト・データの定義の際に、または特定のシナリオの実行中にオブジェクト状態を検査する必要が生じた際に、役立つことがあります。
次のステップは、状態間で起こりうる遷移の定義、および、ある状態から別の状態への遷移を起動するトリガーの決定です。 通常は、クラスをテストする場合、遷移はメソッド呼び出しにより起動されます。 たとえば、EmptyBag 状態から PartiallyFullBag 状態への遷移は、appendMoney の呼び出しにより起動されます。
したがって、起こりうる遷移は次のように定義できます。
要約すると、識別した状態ごとに以下のものをリストする必要があるということです。
一般的に、テストは状態マシン内の特定のパスに沿ってオブジェクトを使用するシナリオから構成されます。 通常は、状態マシン内の可能なパス数は無限であるため、可能なパスをすべてテストするのは現実的ではありません。 その代わりとして、以下の作業を必ず行ってください。
シナリオの全体を通じて、可能な場合はいつでも、テスト中のオブジェクトの状態を検査して、定義済みの理論上の状態モデルと、テスト中のクラスによって実装される状態モデルが実質的に同一であることを確認してください。 遷移を対象とする作業を済ませたら、次には頑強性をテストできます。これを行うには、メソッドをランダム・シーケンスで呼び出して、クラスの不変性への違反がないことを確認します。 例えば、MoneyBag クラスは常に、それぞれに通貨が必ず異なる Money オブジェクトのセットでなければなりません。
テスト・シナリオの作成には、製品に付属のシナリオ・ベースのテスト・パターンを使用できます。
最後に、個々の状態ごとにテスト値を選択する必要があります。 固有のテスト値を選択するようにし、他のテストのコンテキストで使用したことのある値を再利用しないでください。 この戦略に従えば、テスト・スイートの多様性がさらに増し、バグを検出できる可能性が高まります。 適切なテスト値の定義方法についての詳細は、『テスト・データの定義』を参照してください。