単体テストを有効にするために、最初からコードを設計する必要がありますか?


91

現時点では、ユニットテストを許可するようにコード設計を変更することはコードのにおいであるのか、それともコードのにおいをせずにどの程度できるのかについて、チームで議論が行われています。これは、他のすべてのソフトウェア開発会社に存在するプラクティスを導入し始めたばかりだからです。

具体的には、非常に薄いWeb APIサービスが用意されます。その主な責任は、Web要求/応答をマーシャリングし、ビジネスロジックを含む基盤となるAPIを呼び出すことです。

1つの例は、認証方法の種類を返すファクトリの作成を計画していることです。インターフェースを継承する必要はありません。具体的なタイプ以外のものになるとは思わないからです。ただし、Web APIサービスを単体テストするには、このファクトリをモックする必要があります。

これは本質的に、(コンストラクターまたはセッターを介して)DIを受け入れるようにWeb APIコントローラークラスを設計することを意味します。つまり、DIを許可するためだけにコントローラーの一部を設計し、そうでなければ必要のないインターフェースを実装することを意味しますこの方法でコントローラーを設計する必要を避けるために、Ninjectのようなサードパーティのフレームワークがありますが、インターフェイスを作成する必要があります。

チームの何人かは、テストのためだけにコードを設計することを渋っているようです。単体テストを行うには妥協が必要だと思われますが、彼らの懸念をどのように和らげるかはわかりません。

明確にするために、これはまったく新しいプロジェクトであるため、コードを変更して単体テストを有効にすることではありません。ユニットテスト可能になるように記述するコードを設計することです。


33
繰り返しますが、同僚は新しいコードの単体テストを望んでいますが、単体テストが可能な方法でコードを書くことを拒否していますが、既存のものを壊すリスクはありませんか?それが本当なら、@ KilianFothの答えを受け入れ、彼の答えの最初の文を太字で強調するように彼に頼むべきです!あなたの同僚は、自分の仕事が何かについて非常に大きな誤解を持っているようです。
Doc Brown

20
@リー:デカップリングは常に良いアイデアだと誰が言うのですか?構成インターフェイスを使用してインターフェイスファクトリから作成されたインターフェイスとしてすべてが渡されるコードベースを見たことがありますか?私は持っています; それはJavaで書かれていて、完全で、維持できない、バグの多い混乱でした。極端なデカップリングはコードの難読化です。
クリスチャンハックル

8
Michael FeathersのLegacy Codeでの効果的な作業は、この問題に非常にうまく対応しており、新しいコードベースでもテストの利点を理解するのに役立つはずです。
l0b0

8
@ l0b0これはほとんどこのための聖書です。stackexchangeでは、質問に対する答えではありませんが、RLでは、この本を(少なくとも部分的に)読むようにOPに指示します。OPは、取得レガシーコードで有効に機能し、それを読んで、少なくとも部分的に(またはそれを得るためにあなたの上司に伝えます)。このような質問に対処します。特に、テストを行わずにテストを始めた場合は、20年の経験があるかもしれませんが、今では経験のないことを行うことになります。試行錯誤によってそれらすべてを苦労して学ぶよりも、それらについて読むのは非常に簡単です。
R.シュミッツ

4
マイケル・フェザーズの本の推薦をありがとう、私は間違いなくコピーを拾います。
リー

回答:


204

テストのためにコードを変更することをためらうことは、開発者がテストの役割を理解しておらず、暗黙のうちに組織内での自分の役割を理解していないことを示しています。

ソフトウェアビジネスは、ビジネス価値を生み出すコードベースの提供を中心に展開しています。私たちは、長い苦い経験を​​通じて、テストなしではこのようなコードベースを自明でないサイズで作成できないことを発見しました。したがって、テストスイートはビジネスの不可欠な部分です。

多くのコーダーはこの原則にリップサービスを払っていますが、無意識のうちにそれを受け入れません。これがなぜなのかを理解するのは簡単です。私たち自身の精神的能力は無限ではなく、実際、現代のコードベースの巨大な複雑さに直面したときに驚くほど制限されているという認識は、歓迎されず、簡単に抑制または合理化されます。テストコードが顧客に配信されないという事実は、「必須」ビジネスコードと比較して、それがセカンドクラスの市民であり、非必須であることを容易に信じさせます。そして、ビジネスコードにテストコード追加するという考えは、多くの人にとって二重に不快感を与えるようです。

この慣行を正当化する上での問題は、ソフトウェアビジネスで価値がどのように生み出されるかの全体像は、多くの場合、企業階層の上位層によってのみ理解されるが、これらの人々は、テストを取り除くことができない理由を理解するために必要なコーディングワークフロー。したがって、テストは一般的に良いアイデアであると彼らが保証する実務家によってあまりにも頻繁になだめられますが 「私たちはそのような松葉杖を必要としないエリートプログラマー」、または「今はその時間がない」など。ビジネスの成功は数字のゲームであり、技術的な負債を回避することを保証するという事実品質などは、長期的にのみその価値を示しています。つまり、彼らはその信念に非常に誠実であることを意味します。

簡単に言えば、コードをテスト可能にすることは、開発プロセスの重要な部分であり、他の分野と同じです(多くのマイクロチップは、テストのみを目的として要素のかなりの割合で設計されています)が、それ。そのtrapに陥らないでください。


39
私はそれが変化の種類に依存すると主張します。コードをテストしやすくすることと、本番環境では決して使用すべきではないテスト固有のフックを導入することには違いがあります。私は後者について個人的に警戒しています、なぜならマーフィー...
Matthieu M.

61
ユニットテストはカプセル化を破り、テスト中のコードを他の方法で必要とされるよりも複雑にします(たとえば、追加のインターフェースタイプの導入やフラグの追加など)。ソフトウェアエンジニアリングでは常にそうであるように、すべての優れた実践とすべての優れたルールには責任があります。多くの単体テストを盲目的に作成すると、テストの作成と保守にすでに時間と労力がかかることは言うまでもなく、ビジネス価値に悪影響を与える可能性があります。私の経験では、統合テストのROIははるかに高く、妥協の少ないソフトウェアアーキテクチャを改善する傾向があります。
クリスチャンハックル

20
@Leeもちろんですが、特定の種類のテストを行うことでコードの複雑さが増すことを保証するかどうかを検討する必要があります。私の個人的な経験では、モックに対応するために基本的な設計変更が必要になるまで、単体テストは素晴らしいツールです。そこで、別の種類のテストに切り替えます。単体テストを行うという唯一の目的のために、アーキテクチャをかなり複雑にすることを犠牲にして単体テストを書くことは、へそを注視することです。
コンラッドルドルフ

21
@ChristianHacklユニットテストでカプセル化が破られるのはなぜですか?私が取り組んだコードでは、テストを有効にするために余分な機能を追加する必要があると認識されている場合、実際の問題はテストする機能がリファクタリングを必要とするため、すべての機能が同じであることを発見しました抽象化レベル(抽象化レベルの違いにより、通常、追加のコードにこの「必要性」が生じる)。下位レベルのコードは独自の(テスト可能な)関数に移動します。
Baldrickk

29
@ChristianHacklユニットテストは、カプセル化を壊してはなりません。ユニットテストからプライベート変数、保護変数、またはローカル変数にアクセスしようとすると、間違っています。機能fooをテストする場合、ローカル変数xが2番目のループの3回目の反復で入力yの平方根である場合ではなく、実際に機能するかどうかのみをテストします。一部の機能がプライベートな場合は、そうであっても、とにかく推移的にテストすることになります。本当に大きくてプライベートな場合は?これは設計上の欠陥ですが、ヘッダー実装を分離したCおよびC ++以外ではおそらく不可能です。
opa

75

思っているほど簡単ではありません。分解しましょう。

  • 単体テストを書くこと間違いなく良いことです。

しかし!

  • コードを変更すると、バグが発生する可能性があります。そのため、ビジネス上の理由なくコードを変更することはお勧めできません。

  • 「非常に薄い」webapiは、単体テストの最大のケースとは思えません。

  • コードとテストを同時に変更することは悪いことです。

次のアプローチをお勧めします。

  1. 書く統合テストを。これには、コードの変更は必要ありません。基本的なテストケースを提供し、さらにコードを変更してもバグが発生しないことを確認できます。

  2. 新しいコードがテスト可能であり、単体テストと統合テストがあることを確認してください。

  3. ビルドおよびデプロイ後に、CIチェーンがテストを実行することを確認してください。

これらの設定が完了したら、テスト容易性のためにレガシープロジェクトをリファクタリングすることから始めます。

うまくいけば、誰もがプロセスから教訓を学び、テストが最も必要な場所、テストの構造化方法、ビジネスにもたらす価値についての良いアイデアを得ることができます。

編集:私はこの答えを書いて以来、OPは既存のコードの修正ではなく、新しいコードについて話していることを示すために質問を明確にしました。私はおそらく「単体テストは良いですか?」議論は数年前に解決しました。

ユニットテストでどのようなコード変更が必要になるか想像することは困難ですが、どのような場合でも一般的な良い習慣ではありません。実際の異議を検討することはおそらく賢明でしょう。おそらく、異議を唱えられているのは単体テストのスタイルです。


12
これは受け入れられているものよりもはるかに良い答えです。投票の不均衡はがっかりします。
コンラッドルドルフ

4
@Leeユニットテストは、機能ユニットをテストする必要があります。これは、クラスに対応する場合としない場合があります。機能の単位は、そのインターフェースでテストする必要があります(この場合はAPIである可能性があります)。テストでは、デザインの匂いと、異なる/より多くのレベル化を適用する必要性が強調される場合があります。構成可能な小さなピースからシステムを構築します。それらは推論やテストが容易になります。
ウェストールマン

2
@KonradRudolph:OPが、この質問は既存のコードを変更するのではなく、新しいコードの設計に関するものであると付け加えた点を見逃したと思います。したがって、破壊するものは何もないため、この回答のほとんどは適用されません。
Doc Brown

1
ユニットテストを書くことは常に良いことだという声明には強く反対します。単体テストは、場合によってのみ有効です。ユニットテストを使用してフロントエンド(UI)コードをテストするのはばかげています。これらはビジネスロジックをテストするために作られています。また、欠落しているコンパイルチェック(Javascriptなど)を置き換えるための単体テストを作成することをお勧めします。ほとんどのフロントエンドのみのコードは、単体テストではなく、エンドツーエンドのテストのみを記述する必要があります。
サルタン

1
デザインは間違いなく「テストによる損傷」に苦しむ可能性があります。通常、テスト容易性により設計が改善されます。テストを記述すると、何かを取得することはできませんが、渡す必要があることがわかり、インターフェイスがより明確になります。ただし、テストのためだけに不快なデザインを必要とするものに出くわすことがあります。例として、たとえばシングルトンを使用する既存のサードパーティコードのために、新しいコードで必要なテスト専用コンストラクタがあります。その場合:テスト容易性という名目であなた自身の設計に損害を与える代わりに、一歩下がって統合テストのみを行います。
アンダースフォースグレン

18

本質的にテスト可能なコードを設計することは、コードの匂いではありません。それどころか、それは良いデザインのサインです。これに基づくよく知られ、広く使用されている設計パターン(Model-View-Presenterなど)がいくつかあり、簡単な(より簡単な)テストを大きな利点として提供します。

したがって、より簡単にテストするために具象クラスのインターフェースを作成する必要がある場合、それは良いことです。すでに具象クラスがある場合、ほとんどのIDEはそこからインターフェースを抽出できるため、必要な労力を最小限に抑えることができます。この2つの同期を維持するのはもう少し手間がかかりますが、インターフェースはとにかくあまり変わらないはずであり、テストから得られる利点はその余分な労力を上回る場合があります。

一方、@ MatthieuMとして。コメントで言及されていますが、テストのためだけに本番環境で使用されるべきではない特定のエントリポイントをコードに追加する場合は、問題になる可能性があります。


この問題は、静的コード分析によって解決できます-メソッドにフラグを付け(例:名前を_ForTest付ける必要があります)、非テストコードからの呼び出しについてコードベースをチェックします。
ライキング

13

単体テストを作成するには、テストするコードに少なくとも特定のプロパティが必要であることを理解するのは非常に簡単です。たとえば、コードが個別にテストできる個々のユニットで構成されていない場合、「ユニットテスト」という言葉は意味を持ちません。コードにこれらのプロパティがない場合は、最初に変更する必要があります。これは明らかです。

理論的には、最初にいくつかのテスト可能なコードユニットを記述し、すべてのSOLID原則を適用してから、元のコードをさらに変更せずにテストを記述できると言いました。残念ながら、実際に単体テストが可能なコードを書くことは必ずしも簡単ではありません。そのため、テストを作成しようとするときにのみ検出される必要な変更が必要になる可能性があります。これは、ユニットテストの概念を念頭に置いて記述されたコードにも当てはまります。また、「ユニットテスト可能性」が最初のアジェンダにない場合に記述されたコードにも、間違いなく当てはまります。

ユニットテストを最初に記述して問題を解決しようとするよく知られたアプローチがあります。これはテスト駆動開発(TDD)と呼ばれ、コードを最初からユニットテスト可能にするのに確実に役立ちます。

もちろん、後でコードを変更してテスト可能にすることをためらうのは、コードが最初に手動でテストされ、かつ/またはプロダクションで正常に動作する状況で頻繁に発生するため、変更すると実際に新しいバグが発生する可能性があります。これを軽減するための最良のアプローチは、最初に回帰テストスイート(多くの場合、コードベースへのごくわずかな変更のみで実装できます)を作成することです。また、コードレビューや新しい手動テストセッションなどの他の付随する対策も行います。それは、いくつかの内部構造を再設計しても重要なことを壊さないように十分な自信を与えるべきです。


TDDに言及するのは興味深いことです。私たちはBDD / TDDの導入を試みていますが、これにもある程度の抵抗があります。つまり、「通過する最小コード」が本当に意味するものです。
リー

2
@Lee:組織に変更を加えると、常にある程度の抵抗が生じます。また、新しいことを適応させるには常に時間が必要です。これは新しい知恵ではありません。これは人の問題です。
Doc Brown

絶対に。もっと時間が与えられたらいいのにと思います!
リー

多くの場合、この方法で時間を節約できること人々に示すことです(そして、できれば迅速に)。なぜあなたに利益をもたらさない何かをするのですか?
トールビョーン・ラウン・アンデルセン

@ThorbjørnRavnAndersen:チームは同様に、彼らのアプローチが時間を節約することをOPに示すかもしれません。知るか?しかし、ここで実際に技術的ではない問題に直面していないのではないかと思います。OPは、彼のチームが何を間違っているのか(彼の意見では)、彼が彼の大義のために同盟国を見つけようとしているかのように、私たちに言うためにここに来続けます。Stack Exchangeの見知らぬ人とではなく、チームと一緒にプロジェクトについて実際に話し合う方が有益な場合があります。
クリスチャンハックル

11

私はあなたがする(根拠のない)主張に問題を持っています:

Web APIサービスを単体テストするには、このファクトリをモックする必要があります

それは必ずしも真実ではありません。テストを書く方法はたくさんあり、モックを含まない単体テストを書く方法もあります。さらに重要なことに、機能テストや統合テストなど、他の種類のテストがあります。多くの場合、OOPプログラミング言語ではない「インターフェース」で「テストシーム」を見つけることができますinterface

より自然な代替テストシームを見つけるのに役立ついくつかの質問:

  • 別の APIを介してシンWeb APIを作成したいですか?
  • Web APIと基になるAPI間のコードの重複を減らすことはできますか?一方を他方に関して生成できますか?
  • Web APIと基盤となるAPI全体を単一の「ブラックボックス」ユニットとして扱い、全体がどのように動作するかについて有意義に主張できますか?
  • 将来、Web APIを新しい実装に置き換える必要がある場合、どうすればそれを実行できますか?
  • Web APIが将来新しい実装に置き換えられた場合、Web APIのクライアントは気付くことができますか?もしそうなら、どのように?

根拠のないもう1つの主張は、DIについてです。

DIを受け入れるようにWeb APIコントローラークラスを設計します(コンストラクターまたはセッターを介して)。つまり、DIを許可するためにコントローラーの一部を設計し、そうでなければ必要のないインターフェイスを実装するか、サードパーティを使用します。この方法でコントローラーを設計する必要を避けるために、Ninjectのようなフレームワークを使用しますが、インターフェイスを作成する必要があります。

依存性注入は、必ずしも新しいを作成することを意味しませんinterface。たとえば、認証トークンの原因:プログラムで実際の認証トークンを簡単に作成できますか?次に、テストはそのようなトークンを作成し、それらを挿入できます。トークンを検証するプロセスは、ある種の暗号シークレットに依存していますか?シークレットをハードコーディングしていないことを望みます-何らかの方法でストレージからそれを読み取ることができることを期待します。その場合、テストケースで別の(既知の)シークレットを単純に使用できます。

これは新しいものを決して作成してはならないと言うことではありませんinterface。ただし、テストを記述する方法が1つだけであるか、動作を偽造する方法が1つしかないことに固執しないでください。ボックスの外側を考えると、通常、最小限のコードのゆがみを必要とするだけでなく、必要な効果が得られるソリューションを見つけることができます。


インターフェースに関するアサーションについては指摘しましたが、それを使用しなかったとしても、オブジェクトを何らかの方法で注入する必要がありますが、これはチームの他のメンバーの懸念です。すなわち、具体的な実装をインスタンス化し、それをそのままにしておくパラメータなしのctrにチームの一部が満足するでしょう。実際、あるメンバーは、リフレクションを使用してモックを注入するというアイデアを浮かび上がらせたので、それらを受け入れるためにコードを設計する必要はありません。これは厄介なコードの匂いですimo
Lee

9

これは新しいプロジェクトなので、あなたは幸運です。テスト駆動設計は、優れたコードを書くのに非常にうまく機能することがわかりました(最初にそれを行う理由です)。

考え出すことでアップフロント現実的な入力データとコードの特定の部分を呼び出し、その後、意図したとおりに、あなたは非常に早い段階でのAPIの設計を行うされてチェックすることができ、現実的な出力データを取得し、取得のチャンスを持っていますか慣れるために書き直さなければならない既存のコードに邪魔されないため、便利な設計です。また、同僚の方が理解しやすいため、プロセスの早い段階で適切な議論を行うことができます。

上記の文の「有用」は、結果のメソッドを簡単に呼び出すことができるだけでなく、統合テストで簡単に作成でき、モックアップを作成しやすいクリーンなインターフェイスを取得する傾向があることを意味します。

考慮して下さい。特に査読付き。私の経験では、時間と労力の投資はすぐに返されます。


TDDにも問題があります。つまり、「通過する最小コード」を構成するものです。私はこのプロセスをチームに示しましたが、彼らは既に設計したものを書くだけでなく、例外を取りました。これは理解できます。「最小」は定義されていないようです。テストを作成し、明確な計画と設計がある場合、テストに合格するためにそれを作成してみませんか?
リー

@Lee「渡す最小コード」...まあ、これは少し馬鹿げているように聞こえるかもしれませんが、それは文字通りそれが言うことです。たとえば、testがある場合、テストUserCanChangeTheirPasswordでは(まだ存在しない)関数を呼び出してパスワードを変更し、パスワードが実際に変更されたことをアサートします。次に、テストを実行でき、例外がスローされず、間違ったアサートが行われないまで、関数を作成します。その時点でコードを追加する理由がある場合、その理由は別のテストに進みUserCantChangePasswordToEmptyStringます。
R.シュミッツ

@Lee最終的に、テストは最終的にコードが行うことのドキュメントになります。ただし、紙にインクを塗るだけでなく、それが満たされているかどうかをチェックするドキュメントを除きます。また、この質問と比較しくださいCalculateFactorial-120を返し、テストに合格するだけのメソッド。それ最小です。また、明らかに意図されていたではないものだが、それはちょうどあなたが何を表現するために別のテストを必要とする意味された意図します。
R.シュミッツ

1
@Lee小さなステップ。最低限必要なのは、コードが些細なレベルを超えたときに考えられる以上のことかもしれません。また、すべてを一度に実装するときに行う設計は、それを実証するテストをまだ書かずにどのように行うべきかを仮定するため、再び最適ではなくなる可能性があります。繰り返しますが、コードは最初に失敗するはずです。
トールビョーン・ラウン・アンデルセン

1
また、回帰テストは非常に重要です。チームの範囲内ですか?
ソルビョーン・ラウン・アンデルセン

8

コードを変更する必要がある場合、それはコードの匂いです。

個人的な経験から、私のコードがテストを書くのが難しい場合、それは悪いコードです。実行されたり設計どおりに動作しないため、悪いコードではありません。なぜ動作しているのかすぐに理解できないため、悪いコードです。バグに遭遇した場合、それを修正するのは長く苦痛な仕事になるだろうと思います。また、コードを再利用することは困難/不可能です。

良い(クリーンな)コードは、タスクを一目で簡単に理解できる(または少なくとも見栄えが良い)より小さなセクションに分割します。これらの小さなセクションのテストは簡単です。サブセクションについてかなり自信がある場合は、コードベースのチャンクのみを簡単にテストするテストを作成することもできます(既にテストされているので、再利用も役立ちます)。

コードをテストしやすく、リファクタリングしやすく、最初から簡単に再利用できるようにしておくと、変更が必要になるたびに自分自身を殺すことはありません。

これは、使い捨てのプロトタイプであるはずのプロジェクトをよりクリーンなコードに完全に再構築しながら入力しています。部分的に機能する何かを壊すことを恐れて何かに触れることを恐れて何時間も画面を見つめるのではなく、最初から正しいコードを取得し、不良コードをできるだけ早くリファクタリングすることをお勧めします。


3
「使い捨てのプロトタイプ」-すべてのプロジェクトがそれらの1つとして生活を開始します。私はこれを入力します..何を推測しますか?...使い捨てではないことが判明した使い捨てのプロトタイプをリファクタリングします;)
Algy Taylor

4
使い捨てのプロトタイプが確実に廃棄されるようにする場合は、プロダクションで許可されないプロトタイプ言語で作成してください。ClojureとPythonは良い選択です。
ソルビョーン・ラヴン・アンデルセン

2
@ThorbjørnRavnAndersenそれは私をくすくすさせた。それはそれらの言語を掘ることを意図していたのですか?:)
リー

@リー。いいえ、生産には受け入れられないかもしれない言語の例だけです-通常、組織内の誰もがそれらに不慣れであり、彼らの学習曲線が急勾配であるため、それらを維持できないためです。もしそうなら、そうでないものを選んでください。
トールビョーン・ラウン・アンデルセン

4

ユニットテストができないコードを書くのはコードの匂いだと私は主張します。一般に、コードを単体テストできない場合、モジュールではないため、理解、保守、または強化が困難になります。コードが統合テストの観点からのみ意味のあるグルーコードである場合は、統合テストを単体テストに置き換えることができますが、統合が失敗した場合でも問題を特定する必要があり、単体テストは優れた方法ですやれ。

あなたは言う

認証方法の種類を返すファクトリを作成する予定です。インターフェースを継承する必要はありません。具体的なタイプ以外のものになるとは思わないからです。ただし、Web APIサービスを単体テストするには、このファクトリをモックする必要があります。

私はこれに従わない。何かを作成するファクトリーを持つ理由は、ファクトリーを変更したり、ファクトリーが簡単に作成するものを変更できるようにするためです。そのため、コードの他の部分を変更する必要はありません。認証方法が変更されない場合、ファクトリは無駄なコードの膨張です。ただし、本番環境とは異なる認証方法をテストで使用する場合は、本番環境とは異なる認証方法をテストで返すファクトリを用意するのが優れたソリューションです。

これにはDIやMocksは必要ありません。さまざまな認証タイプをサポートし、何らかの方法で構成ファイルや環境変数などから構成できるようにするために必要なのは工場だけです。


2

私が考えることができるすべてのエンジニアリングの分野では、まともなまたはより高いレベルの品質を達成する方法は1つしかありません。

設計の検査/テストを考慮します。

これは、建設、チップ設計、ソフトウェア開発、および製造に当てはまります。さて、これは、テストがすべての設計を構築する必要のある柱であることを意味するのではなく、まったく設計する必要はありません。しかし、設計上のすべての決定において、設計者はテストコストへの影響について明確にし、トレードオフについて意識的に決定する必要があります。

場合によっては、単体テストよりも手動または自動(Seleniumなど)のテストのほうが便利であり、また、単体で許容できるテストカバレッジを提供します。まれに、ほとんどテストされていないものをそこに投げることも許容される場合があります。しかし、これらはケースごとに意識的に決定する必要があります。「コードのにおい」のテストを説明する設計を呼び出すことは、経験が非常に不足していることを示します。


1

ユニットテスト(および他の種類の自動化されたテスト)がコードのにおいを減らす傾向があることを発見しました。コードのにおいをもたらす単一の例を考えることはできません。単体テストでは、通常、より良いコードを書く必要があります。テスト中のメソッドを簡単に使用できない場合、コード内でメソッドを簡単にする必要があるのはなぜですか?

よく書かれた単体テストは、コードの使用方法を示します。これらは、実行可能なドキュメントの形式です。私は恐ろしく書かれた、非常に長い単体テストを理解できませんでした。それらを書かないでください!クラスを設定するために長いテストを作成する必要がある場合は、クラスのリファクタリングが必要です。

単体テストでは、コードの匂いがどこにあるかを強調します。Michael C. FeathersのWorking with Legacy Codeを読むことをお勧めします。プロジェクトは新しいものですが、ユニットテスト(または多くのテスト)がまだない場合は、コードを適切にテストするための非自明な手法が必要になる場合があります。


3
テストできるようにするために多くのインダイレクションレイヤーを導入したい場合がありますが、期待どおりに使用しないでください。
トールビョーン・ラウン・アンデルセン

1

手短に:

むしろ、あるコード-テスト可能なコードは、(通常は)保守コードでハード試験には、通常、ハード維持します。残念貧しいshmuck -テスト可能でないコードを設計する修復不可能である機械設計に似ていることでしょう(それはあなたかもしれません)最終的にそれを修復するために割り当てられます。

1つの例は、認証方法の種類を返すファクトリの作成を計画していることです。インターフェースを継承する必要はありません。具体的なタイプ以外のものになるとは思わないからです。

3年後には5種類の認証方法の種類が必要になることを知っています。要件は変更されますが、設計のオーバーエンジニアリングを回避する必要がありますが、テスト可能な設計は、設計に(あまり)痛みを伴うことなく変更できる(ちょうど)十分な縫い目があることを意味し、モジュールテストにより、あなたの変更は何も壊しません。


1

依存性注入を中心に設計するのはコードの匂いではありません-ベストプラクティスです。DIの使用は、テスト容易性のためだけではありません。DIを中心にコンポーネントを構築すると、モジュール性と再利用性が向上し、主要コンポーネント(データベースインターフェイスレイヤーなど)をより簡単に交換できます。ある程度の複雑さを追加しますが、適切に行うことで、レイヤーの分離と機能の分離が改善され、管理とナビゲートがより簡単になります。これにより、各コンポーネントの動作を適切に検証しやすくなり、バグが減少します。また、バグを追跡しやすくなります。


1
「正しく完了」が問題です。DIが間違って行われた2つのプロジェクトを維持する必要があります(ただし、「正しい」ことを目指しています)。これにより、コードはひどく恐ろしくなり、DIおよび単体テストなしのレガシープロジェクトよりもはるかに悪くなります。DIを正しくすることは簡単ではありません。
1

@Janおもしろい。彼らはどのように間違っていたのですか?
リー

1
@Lee Oneプロジェクトは、速い開始時間を必要とするサービスですが、すべてのクラスの初期化がDIフレームワーク(C#のキャッスルウィンザー)によって事前に行われるため、開始時に恐ろしく遅くなります。これらのプロジェクトで見られる別の問題は、DIを「新しい」オブジェクトの作成と混合し、DIを回避することです。それはテストを再び難しくし、いくつかの厄介な競合状態をもたらしました。
1

1

これは本質的に、(コンストラクターまたはセッターを介して)DIを受け入れるようにWeb APIコントローラークラスを設計することを意味します。つまり、DIを許可するためだけにコントローラーの一部を設計し、そうでなければ必要のないインターフェースを実装することを意味しますこの方法でコントローラーを設計する必要を避けるために、Ninjectのようなサードパーティのフレームワークがありますが、インターフェイスを作成する必要があります。

テスト可能なものの違いを見てみましょう:

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

テスト不可能なコントローラー:

public class MyController : Controller
{
}

前者のオプションには、文字通り5行のコードがあり、そのうち2行はVisual Studioで自動生成できます。依存性注入フレームワークをセットアップしてIMyDependency、実行時に具象型を置き換える-まともなDIフレームワークでは別の1行のコードである-すべてがただ機能する(ただし、モックしてコントローラーを心ゆくまでテストできる) 。

テスト可能性を可能にする6行の追加コード...そして同僚は、それが「作業が多すぎる」と主張しています。その議論は私と一緒に飛んでいないし、あなたと一緒に飛んではいけません。

また、テスト用のインターフェイスを作成および実装する必要はありません。たとえば、Moqを使用すると、単体テストの目的で具体的な型の動作をシミュレートできます。もちろん、テストしているクラスにこれらの型を挿入できない場合、それはあまり役に立ちません。

依存性注入は、一度理解すると、「これなしでどのように機能したのでしょうか?」シンプルで効果的で、意味を成します。同僚が新しいことを理解していないために、プロジェクトをテスト可能にすることを妨げないでください。


1
何として却下するのは迅速です「新しい物事の理解の欠如は、」古いものをよく理解になるかもしれません。依存性注入は確かに新しいものではありません。このアイデア、そしておそらく最も初期の実装は、数十年前のものです。そして、はい、あなたの答えはユニットテストのためにコードがより複雑になる例であり、おそらくユニットテストがカプセル化を破る例だと思います(クラスが最初にパブリックコンストラクタを持っていると言うのは誰ですか?)トレードオフのため、他の人から継承したコードベースから依存性注入を削除することがよくあります。
クリスチャンハックル

コントローラには、MVCが必要とするため、暗黙的であるかどうかにかかわらず、常にパブリックコンストラクターがあります。「複雑」-多分、コンストラクターがどのように機能するか理解していない場合。カプセル化-はい
イアンケンプ

パブリックコンストラクターについて:実際、それは使用されているフレームワークの特殊性です。フレームワークによってインスタンス化されない通常のクラスのより一般的なケースについて考えていました。複雑さが増すにつれて追加のメソッドパラメータを見ることは、コンストラクタがどのように機能するかを理解していないことに等しいと思うのはなぜですか?ただし、DIとカプセル化の間にはトレードオフが存在することをご了承いただきありがとうございます。
クリスチャンハックル

0

単体テストを書くとき、コード内で何がうまくいかないかを考え始めます。コード設計を改善し、単一責任原則(SRP)を適用するのに役立ちます。また、数か月後に同じコードを修正するために戻ったときに、既存の機能が壊れていないことを確認するのに役立ちます。

できるだけ純粋な機能を使用する傾向があります(サーバーレスアプリ)。ユニットテストは、状態を分離し、純粋な関数を記述するのに役立ちます。

具体的には、非常に薄いWeb APIサービスが用意されます。その主な責任は、Web要求/応答をマーシャリングし、ビジネスロジックを含む基盤となるAPIを呼び出すことです。

基礎となるAPIの単体テストを最初に記述し、十分な開発時間がある場合は、シンWeb APIサービスのテストも記述する必要があります。

TL; DR、ユニットテストは、コードの品質を向上させ、コードの将来の変更をリスクなしで行うのに役立ちます。また、コードの可読性も向上します。コメントの代わりにテストを使用して、あなたの意見を述べてください。


0

一番下の行、および不本意な多くとのあなたの議論であるべきことは、競合がないということです。大きな間違いは、誰かがテストを嫌う人々に「テスト用に設計する」というアイデアを生み出したことであったようです。彼らは口を閉ざすか、または「これを正しくするために時間をかけましょう」のように、それとは異なる言い方をすべきでした。

何かをテスト可能にするために「インターフェイスを実装する必要がある」という考えは間違っています。インターフェイスは既に実装されており、クラス宣言でまだ宣言されていません。既存のパブリックメソッドを認識し、そのシグネチャをインターフェイスにコピーして、クラスの宣言でそのインターフェイスを宣言するだけです。プログラミングも、既存のロジックへの変更もありません。

どうやらこれについて別の考えを持っている人がいるようです。最初にこれを修正することをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.