web-dev-qa-db-ja.com

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

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

もちろん、オブジェクト指向とソフトウェアエンジニアリングの優れた実践を維持する(「すべてを公開する」などではない)範囲内で問題はない、と私は考えています。

私の同僚の意見では、テスト目的でのみ機能するコード(変更可能)の変更は間違っています。

単純な例として、いくつかのコンポーネント(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オブジェクトを取り込んで、独自のアセンブリを渡してテストを実行できるようにします。私の同僚は、これは良い習慣だとは思っていませんでした。

何が良い一般的な習慣と考えられていますか?

79
liortal

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

  • メンテナンスが簡単です
  • 推論するのは簡単ですが、
  • より疎結合であり、
  • 全体的にデザインが優れています。
136
Robert Harvey

(一見)反対勢力が働いている。

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

すべての「実装の詳細」を非公開に保つことの支持者は、通常、カプセル化を維持したいという欲求に動機付けられています。ただし、すべてをロックダウンして使用不可にすることは、カプセル化への 誤解 アプローチです。すべてを使用不可に保つことが最終的な目標である場合、唯一の真のカプセル化されたコードは次のとおりです。

static void Main(string[] args)

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

ほとんどありません。次に、いくつかのメソッドを公開してもよいのはなぜですか?結局のところ、主観的な設計上の決定ではないでしょうか。

結構です。プログラマを無意識のレベルでさえ導く傾向があるのは、やはりカプセル化の概念です。 適切にその不変条件を保護する の場合、パブリックメソッドを安全に公開できます。

その不変条件を保護しないプライベートメソッドを公開したくありませんが、多くの場合、 does その不変条件を保護し、になるように変更できます。その後、それを一般に公開します(もちろん、TDDでは、逆の方法で行います)。

テストしやすいようにAPIを開くことは、良いことです。なぜなら、あなたがしていることは 実際に行っていることは、開閉原理を適用することです

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

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

59
Mark Seemann

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

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

21
Jason Swett

少し鶏と卵の問題です。

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

私はあなたの同僚の視点を理解しています。あなたは(おそらく)動作するコードを持っています、そしてあなたがそれを行ってそれをリファクタリングすると-何らかの理由で-あなたがそれを壊す危険があります。

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

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

もちろん、それが良いかどうかビジネス実践は​​、まったく別のワームです。

13
Carson63000

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

このコードにいくつかのメンテナンスが必要であると判断した時点で、thatは、テスト容易性のためにリファクタリングする良い機会です。あなたのビジネスケースによっては、allコードは最終的に何らかのメンテナンスが必要になるという妥当な仮定である場合があります。その場合、ここで他の回答と区別する(ここでは(例えば. Jason Swettの答え )が消えます。

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

(もちろん、新しいコードは積極的にメンテナンスされているので、テストできるように書く必要があります。)

7
Aidan Cully

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

多くの人がコードを変更して、IoC、DI、およびインターフェイスベースのクラスを導入し、これらのコード変更を必要とするモッキングツールとユニットテストツールを使用したユニットテストを可能にしています。非常に単純で単純なコードが、すべてのクラスメソッドを他のすべてのメソッドから完全に切り離す必要性によって完全に駆動される複雑な相互作用の悪夢のような混乱に変わるのではなく、それらが健康であることを薄くしません。そして、傷害に侮辱を加えるために、私たちはプライベートメソッドがユニットテストされるべきかどうかについて多くの議論があります! (もちろん、そうする必要があります。システムの一部のみをテストする場合の単体テストのポイントは何ですか)しかし、これらの引数は、既存のツールを使用してこれらのメソッドをテストするのが難しいという観点からより推進されます-テストツールが可能かどうか想像してくださいパブリックメソッドと同じくらい簡単にプライベートメソッドに対してテストを実行します-誰もが不満なくテストするでしょう。

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

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

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

マーティン・ファウラーは彼の記事でこの領域について議論しています モックはスタブではありません

2
gbjbaanb

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

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

2
Sam Gamoran

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

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

この警告の理由は、コードに変更を加えると、コードを再テストしなければならないという犠牲を払うためです。あなたが何をするかにもよりますが、このテスト作業は実際にはそれ自体が大きな労力となる場合があります。

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

2
ozz

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

これを指摘する理由は、多くのdependency-injection-for-testing-onlyステップが元のオブジェクトの境界の外側にあるためです。 「「すべてを公開すること」ではない」と言ったのは知っていますが、これはそのモデルの最大のエラーの1つであり、これに続いて:それらが意図されていない使用までのオブジェクトのメソッド。

1
Ross Patterson

単体テストとデバッグログを使用することをお勧めします。単体テストは、プログラムにさらに変更を加えても、古い機能が壊れていないことを確認します。デバッグログは、実行時にプログラムを追跡するのに役立ちます。
たまに、それを超えてさえ、テスト目的のためだけに何かが必要になることがあります。このためにコードを変更することは珍しいことではありません。ただし、このために製品コードに影響が及ばないように注意する必要があります。 C++およびCでは、これは、コンパイル時エンティティである[〜#〜] macro [〜#〜]を使用して実現されます。そうすると、テストコードは実稼働環境ではまったく見えなくなります。このような規定がC#にあるかどうかはわかりません。
また、プログラムにテストコードを追加するときは、コードのこの部分がテスト目的で追加されていることを明確に見えるにする必要があります。あるいは、コードを理解しようとする開発者は、単にコードのその部分に汗を流すだけです。

1
Manoj R

あなたの質問はあなたの同僚が主張した多くの文脈を与えなかったので推測の余地があります

「悪い習慣」かどうかはhowwhenに依存し、変更が行われます。

私の意見では、メソッドDoSomething(types)を抽出する例は問題ありません。

しかし、私はnot okであるコードをこのように見ました:

_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).
  }
}
_

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

howおよびwhenの意味:

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

if (!testmode)は、抽出されたメソッドよりも理解とテストが困難です。

0
k3b