テスト目的でコードを厳密に変更するのは悪い習慣ですか?


77

私はプログラマーの同僚と、テスト可能なものにするためだけに作業中のコードを変更するのが良いか悪いかについて議論しています(例えば、単体テストを介して)。

私の意見では、オブジェクト指向とソフトウェアエンジニアリングの優れた実践を維持することの範囲内で(「すべてを公開する」などではなく)OKです。

私の同僚の意見は、テスト目的でのみコードを修正するのは間違っているというものです。

単純な例として、一部のコンポーネント(C#で記述された)で使用される次のコードを考えてください。

public void DoSomethingOnAllTypes()
{
    var types = Assembly.GetExecutingAssembly().GetTypes();

    foreach (var currentType in types)
    {
        // do something with this type (e.g: read it's attributes, process, etc).
    }
}

私は、このコードを修正して、実際の作業を行う別のメソッドを呼び出すことを提案しました。

public void DoSomething(Assembly asm)
{
    // not relying on Assembly.GetExecutingAssembly() anymore...
}

このメソッドは、作業するAssemblyオブジェクトを取り込んで、テストを実行するために独自のAssemblyを渡すことを可能にします。私の同僚は、これが良い習慣だとは思いませんでした。

良い一般的な慣行とは何ですか?


5
変更されたメソッドはType、パラメータではなくを使用する必要がありますAssembly
ロバートハーヴェイ

あなたは正しい-タイプまたはアセンブリ、ポイントは、コードが...パラメータとして、この機能のみをテストに使用するためにそこにあるように見えるかもしれないがことを供給できるようにすべきであるということだった
liortal

5
あなたの車には「テスト」のためのODBIポートがあります-物事が完璧であればそれは必要ありません。私の推測では、あなたの車は彼のソフトウェアよりも信頼性が高いでしょう。
マッテンツ

24
彼はコードが「機能する」ことをどのように保証しますか?
クレイジュ

3
もちろん-そうします。テストされたコードは、長期にわたって安定しています。しかし、あなたは、彼らはいくつかの機能の改善ではなく、テスト容易化のための修正に付属している場合は、新たに導入されたバグを説明するためにはるかに簡単に時間を持つことになります
ペターNordlander

回答:


135

コードを変更してテストしやすくすることには、テスト容易性を超える利点があります。一般的に、よりテストしやすいコード

  • 保守が簡単で、
  • 推論するのは簡単ですが、
  • より疎結合であり、
  • アーキテクチャ的に全体的な設計が改善されています。

3
私は答えでこれらのことを言及しませんでしたが、私は完全に同意します。よりテストしやすいようにコードを修正すると、いくつかの幸せな副作用が生じる傾向があります。
ジェイソンスウェット

2
+1:一般に同意します。コードをテスト可能にすると、これらの他の有益な目標に悪影響を与える明確なケースが確かにあり、それらのケースではテスト可能性が最も低い優先順位にあります。しかし、一般的に、コードがテストするのに十分な柔軟性/拡張性がない場合、使用したり、適切にエージングするのに十分な柔軟性/拡張性がありません。
テラスティン

6
テスト可能なコードは優れたコードです。ただし、テストを行わないコードを変更することには常に固有のリスクがあり、非常に短期間でそのリスクを軽減する最も簡単で安価な方法は、コードをそのままにしておくことです。実際に必要になるまで...コードを変更します。行う必要があるのは、単体テストのメリットを同僚に売り込むことです。全員が単体テストに参加している場合、コードがテスト可能である必要があるという議論はありません。全員が単体テストに参加しているわけではない場合、意味がありません。
guysherman

3
まさに。私は自分のコードの約1万ソース行の単体テストを書いていたことを覚えています。コードが完全に機能していることは知っていましたが、テストによって状況を再考する必要がありました。「この方法は正確に何をしているのですか?」と自問しなければなりませんでした。新しい視点から見ただけで、作業中のコードにいくつかのバグが見つかりました。
サルタン

2
[上]ボタンをクリックし続けますが、ポイントを1つだけ与えることができます。私の現在の雇用主は近視眼的すぎて、実際に単体テストを実装することはできず、テスト駆動型で作業しているように意識してコードを書くことで、まだ恩恵を受けています。
AmericanUmlaut

59

プレイ中の(一見)対立する勢力がいます。

  • 一方では、カプセル化を実施したい
  • 一方、ソフトウェアをテストできるようにしたい

すべての「実装の詳細」を非公開にする提案者は、通常、カプセル化を維持したいという願望に動機付けられています。ただし、すべてをロックして利用できない状態に保つことは、カプセル化に対する誤解されたアプローチです。すべてを利用できないようにすることが最終的な目標であった場合、カプセル化されたコードはこれだけです。

static void Main(string[] args)

あなたの同僚は、これをコード内で唯一のアクセスポイントにすることを提案していますか?他のすべてのコードは、外部の呼び出し元からアクセスできないようにする必要がありますか?

ほとんどない。次に、いくつかのメソッドを公開しても大丈夫ですか?最終的には、主観的な設計決定ではありませんか?

そうでもない。無意識のレベルであっても、プログラマを導く傾向があるのは、カプセル化の概念です。パブリックメソッドが不変式を適切に保護すると、パブリックメソッドを安全に公開できます

私はその不変量を保護しないプライベートメソッドを公開したくないだろうが、多くの場合、あなたはそれがそのようにそれを修正することができないその不変量を保護し、その後、 TDDで、もちろん(公共の場所に放置し、あなたはそれを行います他の方法で)。

テスト容易化のためのAPIを開くことはある良いこと何をしているので、実際にやっては開放/閉鎖原則を適用しています

APIの呼び出し元が1人だけの場合、APIが実際にどれほど柔軟かはわかりません。チャンスは、それは非常に柔軟性に欠けています。テストは2番目のクライアントとして機能し、APIの柔軟性に関する貴重なフィードバックを提供します

したがって、テストでAPIを開く必要があることが示唆された場合は、APIを開きます。ただし、複雑さを隠すのではなく、フェイルセーフな方法で複雑さを明らかにすることで、カプセル化を維持します。


3
+1 パブリックメソッドが不変式を適切に保護している場合、パブリックメソッドを安全に公開できます。
シャンブレーター

1
あなたの答えが大好きです!:)何回聞いた:「カプセル化は、いくつかのメソッドとプロパティをプライベートにするプロセスです」。これは、オブジェクト指向でプログラミングするときに言っているようなものです。これは、オブジェクトを使用してプログラミングするためです。:(私はとてもきれいな答えは、依存性注入のマスターから来ていることに驚いていないよ、私は確かに私の貧しい古いレガシーコードについて笑顔にするためにあなたの答えを数回を読み込みます。。
サミュエル・

あなたの答えにいくつかの調整を加えました。余談として、私はEncapsulation of Propertiesの投稿を読みました。自動プロパティを使用することについて聞いた唯一の説得力のある理由は、パブリック変数をパブリックプロパティに変更するとバイナリ互換性が損なわれることです。最初から自動プロパティとして公開すると、クライアントコードを壊すことなく、検証やその他の内部機能を後で追加できます。
ロバートハーベイ

21

あなたは依存性注入について話しているように見えます。それは本当に一般的で、テスト容易性のためにかなり必要なIMOです。

テスト可能にするためだけにコードを変更することをお勧めしますか?というより広範な質問に対処するには、次のように考えてください:コードには、a)実行、b)人間が読むこと、c)を含む複数の責任がありますテストされる。3つすべてが重要であり、コードが3つすべての責任を果たしていない場合は、あまり良いコードではないと思います。だから変更してください!


DIは大きな問題ではありません(私は例を挙げようとしていました)、ポイントは、テストのためだけに、本来作成することを意図していない別のメソッドを提供してもよいかどうかでした。
liortal

4
知っている。私は2番目の段落であなたの主な点を「はい」で述べたと思いました。
ジェイソンスウェット

13

それは少し鶏と卵の問題です。

コードのテストカバレッジが優れている最大の理由の1つは、大胆にリファクタリングできることです。しかし、良いテストカバレッジを得るためにコードをリファクタリングする必要がある状況にあります!そして、あなたの同僚は恐ろしいです。

あなたの同僚の見方がわかります。(おそらく)動作するコードがあり、何らかの理由でリファクタリングを行った場合、それを破るリスクがあります。

しかし、これが継続的なメンテナンスと変更が予想されるコードである場合、その上で作業を行うたびにそのリスクを実行することになります。そしてリファクタリングといくつかのテストカバレッジを取得し、今あなたが制御された条件下で、そのリスクを取る、そして将来の変更のためのより良い形にコードを取得できるようになります。

したがって、この特定のコードベースがかなり静的であり、将来重要な作業が行われるとは思われない場合を除き、やりたいことは優れた技術的実践であると言えます。

もちろん、それが良いビジネス慣行であるかどうかは、まったく別のワームの缶です。


重要な懸念。通常、私はIDEがリファクタリングを自由にサポートしているのは、そこにあるものを壊す可能性が非常に低いからです。リファクタリングがより複雑な場合は、とにかくコードを変更する必要がある場合にのみ行います。物事を変えるには、リスクを減らすためのテストが必要なので、ビジネス上の価値さえ得られます。
ハンスピーターシュトルー

ポイントは、現在のアセンブリを照会する責任をオブジェクトに与えたいのか、いくつかのパブリックプロパティを追加したりメソッドシグネチャを変更したくないので、古いコードを保持したいのでしょうか?このコードはSRPに違反しているため、同僚がどれほど恐れていても、コードをリファクタリングする必要があります。確かにこれが多くのユーザーによって使用されるパブリックAPIである場合、ファサードの実装などの戦略や、変更をあまり多く行わないようにするインターフェイスを古いコードに付与するのに役立つ何かについて考える必要があります。
サミュエル

7

これは、他の回答との強調の違いにすぎないかもしれませが、テスト容易性を改善するために、コードを厳密にリファクタリングするべきではないと思います。保守にはテスト容易性が非常に重要ですが、テスト容易性自体が目的ではありません。そのため、ビジネスエンドをさらに進めるために、このコードがメンテナンスを必要とすることが予測できるまで、このようなリファクタリングを延期します。

あなたは、このコードはいくつかのメンテナンスを必要とすることを決定時点で、それは、テスト容易化のためのリファクタリングのに良い時期でしょう。あなたのビジネスケースによっては、すべてのコードが最終的に何らかのメンテナンスを必要とすることは有効な仮定かもしれません。その場合、ここで他の回答(例えば Jason Swettの回答)との区別はなくなります。

要約すると、テスト可能性だけでは(IMO)コードベースをリファクタリングするのに十分な理由ではありません。テスト容易性は、コードベースでのメンテナンスを可能にする上で重要な役割を果たしますが、リファクタリングを促進するコードの機能を変更することはビジネス要件です。そのようなビジネス要件がない場合は、おそらく顧客が気にする何かに取り組む方が良いでしょう。

(もちろん、新しいコードは積極的に保守されているので、テスト可能なように記述する必要があります。)


2

あなたの同僚は間違っていると思います。

他の人は、これがすでに良いことである理由をすでに述べていますが、あなたがこれを行うための前進を与えられている限り、あなたは大丈夫です。

この警告の理由は、コードに変更を加えると、コードの再テストが必要になるためです。何をするかに応じて、このテスト作業は実際にはそれ自体で大きな努力になる場合があります。

あなたの会社/顧客に利益をもたらす新機能に取り組むのではなく、リファクタリングを決定するのは必ずしもあなたの場所ではありません。


2

コードを通るすべてのパスが実行されているかどうかを確認する単体テストの一環として、コードカバレッジツールを使用しました。私自身はかなり優れたコーダー/テスターとして、コードパスの80〜90%をカバーしています。

発見されたパスを調べて、それらのいくつかのために努力するとき、それは「決して起こらない」エラーケースのようなバグを発見するときです。そのため、はい、コードを変更してテストカバレッジをチェックすると、コードの品質が向上します。


2

ここでの問題は、テストツールがくだらないことです。このオブジェクトをモックアウトし、変更せずにテストメソッドを呼び出すことができるはずです。この単純な例は本当に簡単で修正が簡単ですが、もっと複雑なものがあるとどうなるかということです。

多くの人がコードを変更して、IoC、DI、およびインターフェイスベースのクラスを導入し、これらのコード変更を必要とするモックツールとユニットテストツールを使用したユニットテストを可能にしました。私はそれらが健全なものだとは思っていません。あなたが非常に簡単で単純なコードが、すべてのクラスメソッドを他のものから完全に切り離す必要性によって完全に駆動される複雑な相互作用の悪夢の混乱に変わるときではありません。そして、傷害にin辱を加えるために、私たちはプライベートメソッドをユニットテストすべきかどうかについて多くの議論を持っています!(もちろん、彼らは何をすべきか

もちろん、問題はテストツールの性質にあります。

現在、これらの設計変更を永久にベッドに置くことができるより良いツールが利用可能です。Microsoftには、静的オブジェクトを含む具体的なオブジェクトをスタブ化できるFakes(旧姓Moles)があるため、ツールに合わせてコードを変更する必要はありません。あなたの場合、Fakesを使用した場合、GetTypes呼び出しを有効なテストデータと無効なテストデータを返す独自の呼び出しに置き換えます。これは非常に重要です。

答えます:あなたの同僚は正しいですが、おそらく間違った理由のためです。テストするコードを変更したり、テストツールを変更したりしないでください(または、このようなきめ細かいテストの代わりに、より統合スタイルの単体テストを行うためのテスト戦略全体を変更してください)。

マーティン・ファウラーは、彼の記事「モックはスタブではない」でこの分野に関する議論をしています


1

良い一般的な方法は、ユニットテストとデバッグログを使用することです。単体テストでは、プログラムにさらに変更を加えても、古い機能が損なわれないことを確認します。デバッグログは、実行時にプログラムをトレースするのに役立ちます。
時には、それを超えてさえ、テスト目的のためだけのものが必要になることがあります。このためにコードを変更することは珍しいことではありません。ただし、このために生産コードが影響を受けないように注意する必要があります。C ++およびCでは、これはコンパイル時エンティティであるMACROを使用して実現されます。その場合、本番環境ではテストコードがまったく見えなくなります。そのような規定がC#にあるかどうかはわかりません。
また、プログラムにテストコードを追加すると、それがはっきりと見えるはずです。コードのこの部分は、何らかのテスト目的のために追加されます。または、コードを理解しようとする開発者は、単にコードのその部分に汗をかきます。


1

あなたの例の間にはいくつかの重大な違いがあります。の場合、現在のアセンブリの型に適用できるDoSomethingOnAllTypes()意味がdo somethingあります。ただし、任意のアセンブリをDoSomething(Assembly asm)渡すことができることを明示的に示しています。

私がこれを指摘する理由は、テスト専用依存性注入の多くのステップが元のオブジェクトの境界の外側にあるためです。すべてを公開する」」とは言わなかったが、それはそのモデルの最大のエラーの1つであり、これに続いて、オブジェクトのメソッドを意図されていない用途に開放する。


0

あなたの質問はあなたの同僚が主張した文脈をあまり与えなかったので、憶測の余地がある

「悪い習慣」かどうかは、に依存する方法するとき変更が行われました。

私の意見でDoSomething(types)は、メソッドを抽出する例は大丈夫です。

しかし、私はこのような大丈夫ではないコードを見てきました:

public void DoSomethingOnAllTypes()
{
  var types = (!testmode) 
      ? Assembly.GetExecutingAssembly().GetTypes() 
      : getTypesFromTestgenerator();

  foreach (var currentType in types)
  {
     if (!testmode)
     {
        // do something with this type that made the unittest fail and should be skipped.
     }
     // do something with this type (e.g: read it's attributes, process, etc).
  }
}

これらの変更により、考えられるコードパスの数が増えたため、コードの理解がより困難になりました。

私は何を意味する方法する場合

動作する実装があり、「テスト機能の実装」のために変更を行った場合、DoSomething()メソッドが壊れている可能性があるため、アプリケーションを再テストする必要があります。

if (!testmode)理解し、抽出された方法よりもテストするために、より多くのdifficuliltです。

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