回答:
重複したコードは、他のコードと同様に単体テストコードのにおいです。テストでコードを複製した場合は、更新するテストの数が極端に増えるため、実装コードのリファクタリングが難しくなります。テストは、テスト対象のコードでの作業を妨げる大きな負担になるのではなく、自信を持ってリファクタリングするのに役立ちます。
複製がフィクスチャのセットアップにある場合は、setUp
メソッドをさらに活用するか、より多くの(またはより柔軟な)作成メソッドを提供することを検討してください。
SUTを操作するコードに重複がある場合は、いわゆる「単体」テストがまったく同じ機能を実行している理由を自問してください。
重複がアサーションにある場合、おそらくいくつかのカスタムアサーションが必要です。たとえば、複数のテストに次のような一連のアサーションがある場合:
assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())
次に、おそらく単一のassertPersonEqual
メソッドが必要になるため、を記述できますassertPersonEqual(Person('Joe', 'Bloggs', 23), person)
。(または、単に等価演算子をにオーバーロードする必要があるだけかもしれませんPerson
。)
あなたが言うように、テストコードが読みやすいことが重要です。特に、テストの意図が明確であることが重要です。多くのテストがほとんど同じように見える場合(たとえば、4分の3の線が同じまたは実質的に同じ)、注意深く読んで比較しなければ、大きな違いを見つけて認識することは困難です。したがって、重複を削除するためのリファクタリングは読みやすさに役立ちます。すべてのテストメソッドのすべての行がテストの目的に直接関連しているためです。これは、直接関連する行と単なる定型文である行のランダムな組み合わせよりも、読者にとってはるかに役立ちます。
とはいえ、テストでは、類似しているものの大幅に異なる複雑な状況を実行している場合があり、重複を減らすための適切な方法を見つけるのは困難です。常識を使用してください。テストが読みやすく、意図が明確で、テストによって呼び出されたコードをリファクタリングするときに、理論的に最小限の数以上のテストを更新する必要がある場合は、問題を受け入れて移動します。より生産的なものに移ります。いつでも戻って、後からテストをリファクタリングして、インスピレーションが得られます。
読みやすさはテストにとってより重要です。テストが失敗した場合は、問題を明確にする必要があります。何が失敗したのかを正確に判断するために、開発者は多くの厳密に因数分解されたテストコードをたどる必要はありません。ユニットテストテストを作成する必要があるほどテストコードが複雑になりたくない。
ただし、何も不明瞭にならない限り、通常は重複を排除することをお勧めします。テストで重複を排除すると、APIが向上する可能性があります。収益が減少するポイントを超えないようにしてください。
実装コードとテストは異なる動物であり、ファクタリングルールはそれらに異なる方法で適用されます。
重複したコードまたは構造は、常に実装コードのにおいです。実装にボイラープレートを導入し始めたら、抽象化を修正する必要があります。
一方、テストコードは重複のレベルを維持する必要があります。テストコードの複製により、次の2つの目標が達成されます。
各テストメソッドが約20行未満である限り、私はテストコードのささいな重複を無視する傾向があります。セットアップ、実行、検証のリズムがテストメソッドで明らかになるのが好きです。
テストの「検証」部分で複製が忍び寄る場合、カスタムアサーションメソッドを定義すると効果的です。もちろん、これらのメソッドは、メソッド名で明らかにできる明確に識別された関係をテストする必要があります:assertPegFitsInHole
->良い、assertPegIsGood
->悪い。
テスト方法が長くて繰り返しの多いものになると、いくつかのパラメーターを取る空欄埋めテストテンプレートを定義すると便利なことがあります。次に、実際のテストメソッドは、適切なパラメーターを使用したテンプレートメソッドの呼び出しに削減されます。
プログラミングとテストに関する多くのことに関しては、明確な答えはありません。あなたは味を発達させる必要があり、そうするための最良の方法は間違いを犯すことです。
テストユーティリティメソッドのいくつかの異なるフレーバーを使用して、繰り返しを減らすことができます。
私は本番コードよりもテストコードの繰り返しに寛容ですが、時々それに不満を感じています。クラスのデザインを変更し、戻ってすべてが同じセットアップ手順を実行する10の異なるテストメソッドを微調整する必要がある場合、イライラします。
同意する。トレードオフは存在しますが、場所によって異なります。
状態を設定するために、重複したコードをリファクタリングする可能性が高くなります。しかし、実際にコードを実行するテストの部分をリファクタリングする可能性は低くなります。とは言っても、コードの実行に常に数行のコードが必要な場合は、それをにおいだと考えて、テスト中の実際のコードをリファクタリングするかもしれません。これにより、コードとテストの両方の可読性と保守性が向上します。
このため、rspecが大好きです。
役立つ2つのこと-
一般的な動作をテストするためのサンプルグループを共有しました。
テストのセットを定義し、そのセットを実際のテストに「含める」ことができます。
ネストされたコンテキスト。
基本的に、クラスのすべてのテストだけでなく、テストの特定のサブセットに対して「セットアップ」および「ティアダウン」メソッドを使用できます。
.NET / Java /その他のテストフレームワークがこれらのメソッドを採用するのが早ければ早いほどよい(または、IronRubyまたはJRubyを使用してテストを作成することができます。
テストコードには、通常は量産コードに適用されるのと同じレベルのエンジニアリングが必要だと思います。読みやすさを優先して行われた議論は確かにあり得、私はそれが重要であることに同意します。
しかし、私の経験では、よく因数分解されたテストの方が読みやすく、理解しやすいことがわかりました。変更された1つの変数と最後のアサーションを除いて、それぞれが同じように見える5つのテストがある場合、その単一の異なる項目が何であるかを見つけるのは非常に困難です。同様に、変化する変数とアサーションのみが表示されるように因数分解されている場合、テストがすぐに何を行っているかを理解するのは簡単です。
テストが困難な場合に適切な抽象化レベルを見つけることは難しいので、実行する価値はあると思います。
「より乾燥するようにそれらをリファクタリングしました-各テストの意図はもはや明確ではありませんでした」
リファクタリングの実行に問題があったようです。私は推測しているだけですが、それが明らかに不明確になったとしても、完全に明確である適度にエレガントなテストを行うために、まだやらなければならないことがまだないのですか?
テストがUnitTestのサブクラスであるのはそのためです。そのため、正確で、検証とクリアが容易な優れたテストスイートを設計できます。
昔は、さまざまなプログラミング言語を使用するテストツールがありました。テストを使用して快適で作業しやすいように設計することは困難(または不可能)でした。
あなたは-Python、Java、C#-を使っているどんな言語の能力も持っているので、その言語を上手に使ってください。明確で冗長すぎない見栄えの良いテストコードを実現できます。トレードオフはありません。