トップ 差分 一覧 Farm ソース 検索 ヘルプ PDF RSS ログイン

UnitTestPatterns

BulkDataStressTestPattern.jpg CodePathPettern.jpg ConstraintDataPattern.jpg DataTransformationTestPattern.jpg LoadingTestPattern.jpg ModelStateTest Pattern.jpg ParameterRangePattern.jpg PresentationLayerPatterns.jpg ResourceStressTestPattern.jpg RollbackPattern.jpg SimpleDataIOPattern.jpg SimpleTestDataPattern.jpg SimpleTestPettern.jpg ViewStateTestPattern.jpg vts.jpg

UnitTestPatterns

最終更新時間:2008年04月26日 14時11分54秒

This Page is translated from follow URL.

http://www.marcclifton.com/tabid/87/Default.aspx

(c) 2005 Marc Clifton All Rights Reserved.

This page is translated by Kuniaki IGARASHI, Yasuhiro SUGINO.

mail : igarashikuniaki@gmail.com

このページは上記のwebページを日本語訳したものです。

翻訳のおかしい部分、こなれていない部分はご指摘頂ければありがたいです。

現在鋭意翻訳中です。

Introduction

ユニットテストは常に人々に強い反応を引き起こすようです。ユニットテストを導入する際には満場一致で、良いユニットテストは書くのが難しいことと、書いたユニットテストがどれだけの価値があるのか?とわめき散らします。また、ユニットテスト(特に「テストをパスしたコードは良いコード」)を一笑に付すコミュニティもあります。いつか流行は過ぎ去り、ユニットテストは埃をかぶった"yet another programmer tool"になってしまうのかもしれません。しかし、もしもこの運命を変えることができれば、ユニットテストはどのコミュニティにも、またツール開発者にも受け入れられることでしょう。次のバージョンのVisualStudioには自動リファクタリング機能が追加されます。自動ユニットテスト生成ツールにより、メンテナンスコストを考えている人を救うだけでなく、より多くの人々にユニットテストを普及させることになると私は確信しています。

しかし、これを成し遂げるために、ユニットテストは形式化されねばならないはずです。つまり、ユニットテストはプログラマがその場しのぎで作成したおぼつかないものではなくて、プログラマの規律にすべきです。結論から言えば、ユニットテストはプログラマが書いたものをテストするもののはずです。最初にプログラマが悪いコードを書いていた場合、テストの力でより高品質のコードを思いつくことができますか?これがユニットテストをコードを書くより前に書くべき理由です。これはすなわち、プログラマはコードが何をすべきかだけでなく、どのように設計するかある程度は考えなくてはならないということです。コードの目的と設計からインターフェースが形づくられます。これが多くの人がテストファーストの概念に尻込みする理由です。事前設計をするということが心地良くないようです。

すなわち我々は相刃の剣で斬りつけられようとしているのです。一方は、プログラマへ指針を提供するコミュニティから公式のユニットテスト規律が提示されず、プログラマが書くユニットテストのレベルが保証されないことです。他方は、設計はあらゆるテストを書く前に形式化されなくてはならないという前提条件が多くのプログラマに難しさを感じさせるということです。なぜなら、彼らは形式ばった設計の経験はないし、単純に事前設計が嫌いなのです。この状況をさらに悪くするのは、事前設計作業はリファクタリングにより変更できるという考え方です。

この剣をナマクラにするために、2つ必要なものがあります。ユニットテストパターンを樹立することでユニットテストを形式化すること。ユニットテストを必要とするアプリケーション開発のために、オブジェクト指向デザインパターンを早期採択すること。本稿はこの2つの解法を大きな筆で一気に描きます。目的は、あなたがユニットテストを熱望し、オブジェクト指向デザイン、デザインパターン、リファクタリングらと同様に、より格式高いユニットテストエンジニアリングプロセスを導きだせるようにすることです。

本稿の読者であるあなたは、1つのゴールはユニットテスト自動生成できるツール群であることを心にとめておいてください。それは開発後と開発前に使用きでるもので、開発前にテスト可能なスタブを自動生成できなければなりません。つまり、ユニットテストの利点の1つは、テストの元で期待される構造と振る舞いをドキュメントとして開発者に提供することです。

尚、本稿は一般化のためにサンプルコードを付記しません。

Patterns

私はパターンをおおまかに以下のように分類しました。

  • pass/fail patterns(pass/failパターン)
  • collection management patterns(コレクションマネジメントパターン)
  • data driven patterns(データ駆動パターン)
  • performance patterns(パフォーマンスパターン)
  • process patterns(プロセスパターン)
  • simulation patterns(シミュレーションパターン)
  • multithreading patterns(マルチスレッディングパターン)
  • stress test patterns(負荷テストパターン)

また、これらは太い筆の一描きであることを強調させてください。私の研究により、これらは新しい領域だと見えてくるでしょう。

pass/fail patterns(pass/failパターン)

これらのパターンは良いコード品質を保証する最初の防御壁(または展望に基づく攻撃台)です。しかし注意しなければならないことは、これらはあなたのコードに関しては何も教えてはくれないことです。

The Simple-Test Pattern(シンプルテストパターン)

pass/failテストパターンは最もシンプルなテストパターンであり、このパターンは私にユニットテストの有効性を考えさせてくれるものです。ユニットテストがこのパターンを通過した時、テスト下のコードはまったく同じ入力をいれた場合に動作することだけを教えてくれます。エラーをトラップする練習のユニットテストと同様です。ユニットテストとまったく同じ状況で、コードは正しくエラーをトラップする、ということだけを教えてくれます。どちらの場合も、あらゆる条件下でコードが正しく動作する保証も、他のエラー状況下で正しくエラーをトラップする保証もありません。これは本当に基本のロジックです。しかし、これを礎にすることで、ユニットテストツリーをグリーンバーに変えた人々の「通過!」の大合唱を聞くことができるのです。

The Code-Path Pattern(コードパスパターン)

先ほどのシンプルテストパターンは「ブラックボックステスト」の典型です。コードの内情を知らない場合はそれがあなたのできる全てです。テスト下で通過すると推測されうる成功ケースと失敗ケースの両方について考えながらユニットテストを書いてください。良いテストは最低でも全てのコードパスが実行されることを保証しているものです。これはホワイトボックステスト(被テストコードの内部を知っているもの)の一部です。ここで優先すべきは、通過/失敗のテストを作ることではなくて、コードパスのテストを作ることです。従って、結果は与えられたコードパスに対する出力と比較することになります。しかしここで1つ問題があります。まだコードがないのに、どうやってホワイトボックス(コードパス)テストを書けばいいのでしょう?ここで我々は早くも「コーディング前デザイン」に直面します。この規律はここでは、ユニットテストのテスト駆動デザインの御利益、開発者が通常考えないかもしれないコードパスを、ユニットテストはテストできるということです。さらに、ユニットテストはコードパスを正確に表すドキュメントになります。逆に言えば、規律は開発中ずっと必要とされるのです。ユニットテストを書いている間には気づけなかったコードパスを発見したら、それはユニットテストを直す時です!

The Parameter-Range Pattern(パラメータ範囲パターン)

SimpleTestPetternを手直ししている間、各種の成功/失敗条件をこなすだけでは私は納得できません。コードは条件範囲を使ってテストした方がいいはずです。Parameter Range Patternは1つ以上のパラメータをセットしたCode-Path Pattern で行われます。これで、テストされたコードは実際に様々な条件下で動作すると自信を持ち始めることができるのです。

Data Driven Test Patterns(データ駆動テストパターン)

Parameter-Range Paettern のテストを作れば特定の種類のテストを行うことができます。でも、それではユニットテストが入り組んだコードの羅列になってしまい、非効率で煩雑です。Data Driven Test Patterns はテストデータをテストから切り離しことにより複雑さを解消します。テストデータを生成し(これは時間がかかる作業かもしれません)、テストと独立に変更することができます。

The Simple-Test-Data Pattern(シンプルテストデータパターン)

最も単純なケースでは、1組のデータはコードをテストして、明確な結果(成功/失敗)を予測できます。結果はユニットテスト自身で計算するか、またはデータセットを与えることで可能です。答えは一意で、相違は許されません。このテストパターンの例としては、チェックサム計算、数学のアルゴリズム、シンプルなビジネスの数式計算などが挙げられます。複雑な例としては、暗号化アルゴリズム、ロスレスエンコーディング、圧縮アルゴリズムが挙げれられます。

The Data-Transformation-Test Pattern(データ変換テストパターン)

このパターンは想定される結果のデータ群を使用して行います。Lossy(不可逆)アルゴリズムを使った変換などで使用されます。この場合、例えば、ユニットテストで行いたいことは、圧縮率とデータ損失の組を測定することです。許容誤差の範囲で復元可能かも確かめたいところです。他の適用例としては、(四捨五入などの)丸めアルゴリズムなどがあります。(買う側よりも売る側の方が有利になっているものです。)別の例としては精度があります。精度は業務系システムの場合にはしばしば要求されます。税、利子、パーセンテージなどの計算です。計算全体で正しく精度を追求しないと、最終的にペンス、ドルの桁まで丸められた際に大きな差が生まれてしまいます。

Data Transaction Patterns (データトランザクションパターン)

これはデータの永続性と関連に関する問題を含む場合のスタート地点です。より深い議論はシミュレーションパターンで行います。また、これらのパターンでは負荷(サーバの負荷など)の問題を考えません。負荷に関しては負荷テストパターンで議論します。

The Simple-Data-I/O Pattern(シンプルデータI/Oパターン)

これは単純なデータトランザクションパターンです。サービスのread/write機能だけを確認します。データ群をサービスに渡して、読むことができるか確認します。これはシンプルテストデータパターンと一緒になり、トランザクションテストを少し強化するかもしれません。

The Constraint-Data Pattern(制約データパターン)

このパターンはサービスが受け入れるべきルールなどを加え、シンプルデータI/Oパターンを強化します。典型的な制約は以下を含みます。

  • NULL可
  • 一意制約
  • デフォルト値
  • 外部キーとの関係
  • cascade on update
  • cascade on delete

図示した通り、これらの制約はデータベースでよく使われるものです。このユニットテストはサービス自身の要件を確かめることから生まれます。DBスキーマ、ウェブサービス、または他のモデルでデータの正当性を高めるために制約を使用します。

The Rollback Pattern(ロールバックパターン)

このパターンは他のトランザクションテストパターンの付随物です。ユニットテストが順序を考慮しないで実行されると、DBやストレージを使用している場合に問題を引き起こします。あるユニットテストは終了後、状態を不正にし、他のユニットテストを失敗させることがあります。トランザクションテストの多くは、データセットを想定通りの状態へ戻す処理をいれるべきです。ユニットテスト前に想定したデータセットを設定することも必要です。パフォーマンスの面からも、(サービスがロールバックの機能を提供可能であれば)それぞれのテストでテスト開始前の状態へロールバックできる機能を提供した方がいいでしょう。

.....

The Deadlock-Resolution Pattern(デッドロック解消パターン)

このテストはデッドロックが解消されることを検証します。このテストを確立させるとなると、おそらく大変複雑になることでしょう。その理由は動くスレッドそれぞれを徹底的に理解している必要があるからです。

The Stress-Test Patterns(ストレステストパターン)

大多数のアプリケーションは理想的な環境でテストされます。プログラマーは高速なマシンを使用し、ネットワークのトラフィックは少なく、使うデータセットは小さいものです。実際の環境は全く異なります。アプリケーションは完全に破綻してしまう前に劣化にさいなまれ、ユーザに対しては、ほとんど応答しないかエラーを伴って応答する可能性があります。

The Bulk-Data-Stress-Test Pattern(バルクデータストレステストパターン)

このテストの設計は大きなデータセットで動作するときのデータ操作のパフォーマンスの確認にあります。これらのテストは非効率な挿入、アクセス、削除のプロセスを明らかにすることが多く、たいていは索引、制約、データモデルの構造のレビューを、コードがクライアントとサーバのどちらで動作するべきかも含めて行うことで修正できます。

The Resource-Stress-Test Pattern(リソースストレステストパターン)

負荷によるリソース消費のテストはOSの機能に依存するため、モックオブジェクトを利用すると改善できるでしょう。OSがメモリが少ない状態や低いディスク残量やその他のリソースのシミュレートをサポートしていれば、シンプルなテストができます。そうでない場合は、モックオブジェクトを利用してリソースが低い状態でのOSの挙動をシミュレートするしかありません。

The Loading-Test Pattern(ローディングテストパターン)

このテストは別のマシンまたはアプリケーションまたはスレッドが「システム」をロードする際のコードの振る舞いをテストします。例えば、高いCPU使用率や高いネットワークトラフィックがあります。このテストはシミュレーションするだけであり(モックオブジェクトも利用しません)、それゆえ、価値があるものかはっきりしません。理想的には、ユニットテストが多量のネットワークトラフィックをシミュレートするように意図されたものであれば、スレッドが一つ作成されて、ネットワークにパケットを注入するだけです。

Prerentation Layer Patterns(プレゼンテーションレイヤパターン)

ユニットテストのもっとも挑戦的な面の一つとして、プレゼンテーション層自身でユーザに正しく情報を伝えること、つまり、アプリケーション内部の動作によってプレゼンテーション層に状態を正しく設定されているかを検証することがあります。プレゼンテーション層はビジネスオブジェクト、データオブジェクト、制御ロジックが絡み合うことがよくあります。プレゼンテーション層のユニットテストを計画する際には、明確な関心事の分離が必須になるということに気づかねばなりません。解決のための重要な要素として、適切なMVCアーキテクチャの開発があります。MVCアーキテクチャによって、プレゼンテーション層と上手く協調動作する設計プラクティスを開発することができます。しかし、それは安易に乱用することになります。多数の規律が必要になれば、早い話が、自分たちがMVCアーキテクチャそのものを実装していると納得するだけになります。

Sun MicrosystemsのWebページは、MVCアーキテクチャを表わす絶対的な福音書だと言えます。http://java.sun.com/blueprints/patterns/MVC-detailed.html

MVCパターンを要約すると以下のようなものです。

重要なのは、モデルが変更の通知することとユーザ操作を伝えるのにイベントを利用するということです。ボタンクリックが例として挙げられます。もしイベントを利用しなければ、プレゼンテーション層特有の要求に合わせてビューを変更することが簡単にはできなくなり、モデルは破綻してしまいます。さらに言うと、イベント無しにはオブジェクト同士が複雑に絡み合ってしまいます。また、イベントプールに管理されているような類のイベントは、計測とデバッグを可能にします。私が実践する中でわかった、このモデルに対する唯一の例外は、モデル内の状態の変更がビューよりもむしろコントローラ内のイベントによって捕らえられることが時々あるということです。モデルの変更によっては、ユーザ認証のように、ビューを持たないが、コントローラの他の側面に影響を与えることになるものもあります。

The View-State Test Pattern(ビューステートテストパターン)

このテストは、モデルの状態が変化するとビューが状態を適切に変化することを検証します。このテストはMVCパターンの半分だけを実践します。モデルのイベントがビューに通知されると、ビューがそれらのイベントを、プレゼンテーション層の内容と状態に作用するという観点で管理します。コントローラはこのテストの要素ではありません。

The Model-State Test Pattern(モデルステートテストパターン)

いったんView-Stateパターンについてアプリケーションの動作を検証していくと、もう一歩複雑なパターンへ進むことができます。

このパターンでは、ユニットテストがユーザの操作をシミュレートするには、プレゼンテーション層に状態と中身(content)を直接設定する方法があります。また、必要があれば、状態を変更するイベントを呼び出す方法があります。例えはKeyUp、Click等です。上で図解したように、このユニットテストは、モデルの状態が明らかに変化して期待されたイベントが起こっている場合を検証できます。

加えて、ビューが持つイベントはフックすることができ、検証でシミュレートされたユーザ操作によってビューのイベントを選択的に引き起こすことができます。このテストはモデル自身に幾分の設定が必要となる可能性があり、コントローラはブラックボックスとして扱われます。しかし、モデルの状態を点検することで、コントローラがモデルの状態を適切に管理しているかどうかを見極めることができます。