web-dev-qa-db-ja.com

OOPでホワイトボックステストが推奨されないのはなぜですか?

ユニットテストクラスの一般的なコンセンサスは、パブリックインターフェイスのみを介してオブジェクトをテストすることです。したがって、LinkedListクラスでremoveElementメソッドをテストする場合は、addElement、次にremoveElement、最後にcontainsElementを呼び出して、要素が削除されました。

addElementまたはcontainsElementが壊れた場合、removeElementの実装が正しい場合でもテストは失敗するため、これは脆弱です。

スタンドアロンプ​​ロシージャをテストするときは、それらを個別に呼び出そうとします。 removeElementプロシージャをテストする場合、テストでパラメーターの状態を直接構築し、呼び出し後の状態が正しいことをアサートします。

メソッドとプロシージャの唯一の違いは、メソッドにパラメーターとしてオブジェクトが暗黙的に与えられることです。 list.removeElement(el)removeElement(list, el)は機能的に同じなので、同じ方法でテストしてみませんか?例えばテストでは、LinkedListクラスのインスタンスを作成し、その「プライベート」フィールドをセットアップし、removeElementを呼び出し、変更後のフィールドを正しく変更したことをアサートします。

これは、単一の機能ユニットの入力を取り、出力をアサートすることに関する理想的なユニットテストです。メソッドGをテストするためだけにパブリックメソッドA、B、C、D、E、およびFを呼び出さなければならないことは、境界線の統合テストであり、誤検出(データ自体が検証されないため)を引き起こす可能性があり、メンテナンス中のテストはより困難です。

事例として、ブラックボックステストは開発者を不必要なパブリックメソッドを追加してテストを「より簡単」にするように誘惑しますが、長期的にはメンテナンスが増加します。

だから私の質問は、手続きと機能の世界で常識のように思われるのに、なぜOO世界でホワイトボックステストが推奨されないのか?

[〜#〜] edit [〜#〜]:OOグリップを処理する方法はありますか?私の投稿で、特に後半で概要を説明しましたがしない新しいパブリックメソッドを追加し、テストされているもの以外のパブリックメソッドを呼び出さないようにしますか?私の comment で「前の」ノードポインターをアサートするジレンマを検討してください。

EDIT#2:どうやら、私のクラスの概念は他のものとは異なる場合があります。私にとってのクラスは、(非常に古い)設計パターンです。「構築」、「消費」(たとえば、呼び出しメソッド)、および「破壊」は、fopenfread, fwrite fseek、およびCのfclose。暗黙のパラメーターが関係しているかどうかに関係なく、名前空間の背後に物事が詰め込まれている、またはプライベート、パブリック、または保護と呼ばれているものはすべて、結局のところデータとデータの変換にすぎません。関数そのものである実際のユニットの設計パターンまたは「コンテナ」に似ていると、クラスをユニットとして把握するのに問題があります。

2
Zippers

あなたは2つの関連しているが、それでも異なったものを混ぜています:

  • ホワイトボックステスト

  • プライベートメソッドを使用した単体テスト

パブリックメソッドのみを使用して単体テストを作成する理由または作成しない理由は、このサイトで以前に何度も説明されています(例: here または here )。これらの議論を繰り返すことは意味がないと思います。それがあなたの質問である場合、おそらくそれらのリンクをたどって答えを見つけるでしょう。

ただし、ホワイトボックステストは、プライベートメンバーを使用してテストをセットアップすることを意味するものではありません。これは、テストするクラスまたはコンポーネントの内部に関する特定の知識を使用してテストを設計することを意味します。たとえば、完全なコードカバレッジやブランチカバレッジを実現するためのテストを作成することで、これは通常パブリックメンバーだけを使用して行われます。したがって、ホワイトボックステストでは、クラスの内部を知る必要がありますが、内部へのアクセスを直接利用しません。これにより、コンポーネントの設計者は、テストについてあまり気にすることなく実装の詳細を変更できる状況でもコンポーネントを作成できます。

この種のテストはOOPではお勧めできません。まったく逆です。よく知られているテスト駆動開発(OOPおよび非OOPで人気があります)は、実際にこれらの種類のテストにつながるテストの形式です。新しいテストを追加したいときはいつでも機能、コンポーネントのクラスへの機能。最初に「赤」のテストを記述し、新しいコードを追加するか、既存のコードを変更して機能を追加します。新しいテストが「緑」になるので、追加または変更されたことは明らかです。コードはテストでカバーされている必要があります。

あなたの例として:removeElementが「リスト」モジュールのパブリックメソッドであり、クラスのメンバーではない場合でも、あたかもそのモジュールのパブリックインターフェイスのみを使用してテストする方法をお勧めしますそれはクラスでした。壊れたaddElementまたはcontainsElementの例は不自然です(そして、「テストされているメソッド以外のパブリックメソッドを呼び出さないようにする」という考えは、問題ありません-誤解しています)。実際には、そのようなテストを

  • 新しいリストを作成する
  • リストに要素Xが含まれていないことをアサートする
  • 要素Xをリストに追加する
  • リストに要素Xが含まれていることをアサートする
  • リストから要素Xを削除する
  • リストに要素Xが含まれていないことをアサートする

これはすべてパブリックメソッドを使用して可能です。

addElementまたはcontainsElementが壊れていた場合、上記のテストシーケンスは、テストでこれが明らかになることを確認します(removeElementの誤検知は発生しません)。

もちろん、パブリックインターフェイスのみを使用することが完全なテストシナリオを作成するための最良のアプローチではない可能性があるクラスや複雑なコンポーネントの場合があり、たとえば「」を追加することによって、カプセル化をある程度緩めることが役立つ場合があります。メンテナンスハッチ」をコードに挿入します。しかし、私はそのようなケースは例外的なケースだと思います。良いテストとコンポーネントの設計はこれらの状況を避けようとするべきです。リンクされたリストのような単純なコンポーネントはそのような手段を必要とすべきではありません。

14
Doc Brown

ここで何が起こっているのかは、見方の違いだと思います。クラスを最小単位または関数と見なしていますか? classesを最小単位と見なす人の視点から質問を分解します。

[理想的な単体テスト]は、単一の機能ユニットの入力を取り、出力をアサートすることです。メソッドGをテストするためだけにパブリックメソッドA、B、C、D、E、およびFを呼び出す必要があるのは、境界線の統合テストです。

クラスisが最小のユニットであるため、これは意味がありません。したがって、ユニット全体をテストするため、複数のメソッドを呼び出すことは問題になりません。

[データ]は誤検出を引き起こす可能性があります(データ自体は検証されないため)。

データの信頼性についてではなく、インターフェースについて、およびそれがであるかどうかが正しく動作するかどうかを示します。

メンテナンス中のテストの失敗を特定することはより困難です。

テストが失敗した場合は、ユニット全体が壊れているため、どのメソッドが失敗を引き起こしたかは問題ではありません。結果がユニット全体を修正するため、根本的な原因を見つけるのは困難です。

LinkedListクラスでremoveElementメソッドをテストする場合は、addElement、次にremoveElement、最後にcontainsElementを呼び出して要素をアサートする必要があります。取り除かれた。 addElementまたはcontainsElementが壊れた場合、removeElementの実装が正しい場合でもテストは失敗するため、これは脆弱です。

メソッドが失敗することではなく、クラス全体が失敗することです。特定のメソッドを単独でテストしても意味がありません。ワークフローまたは要件テストを通じてクラス全体をテストするのが最善です。

次に、誰かがfunctionsを最小単位として表示する視点を説明します。私は編集2でそれを最もよく説明したと思います(表現を少し変更しました)。

クラスは設計パターンです。Cのfopenfread/fwrite/fseek、およびfcloseと同じである「構築」、「消費」、および「破壊」です。暗黙のselfパラメータがあり、定義は名前空間に配置されます。または、プライベート、パブリック、または保護と呼ばれますすべてが1日の終わりにデータとデータ変換です

この観点からは、テスト中に直接データ入力を確立することに問題はありません。そうしないと、Nudgeへの関数呼び出しシーケンスを使用してゲームをプレイすることになります(これは、あなたが知る必要がなかった)データに間接的に確認できる状態。これは、直接のデータの信頼性とテストの明快さを犠牲にします。

エンジニアがデータおよびデータの変換または抽象化の観点から考える必要があるかどうか、および開発とテスト中にどのレベルで考えるかについては、格差があるようです。これにより、なぜこの格差が存在するのかという疑問が生じます。エンジニアがソフトウェアを表示、設計、およびテストする方法に波及効果があるため、これは重要な質問です。質問する価値があると思うので、別の質問を投稿したくなります。

0
Zippers