アサーションコードの匂いが多すぎますか?


19

私は本当に単体テストとTDDに夢中になりました-私はテストに感染しています。

ただし、ユニットテストは通常​​、パブリックメソッドに使用されます。ただし、プライベートメソッドでもいくつかの仮定(アサーション)をテストする必要がある場合があります。これは、それらの一部が "危険"であり、リファクタリングがそれ以上役に立たないためです。(フレームワークをテストすることでプライベートメソッドをテストできることはわかっています)。

したがって、プライベートメソッドの最初の行と最後の行の両方がアサーションであることが私の習慣になりました。

ただし、パブリックメソッド(およびプライベートメソッド)でアサーションを使用する傾向があることに注意してください。パブリックメソッドの前提条件は、ユニットテストフレームワークによって外部からテストされるため、これは「テストの重複」でしょうか。

誰かがあまりにも多くのアサーションをコードの匂いだと考えることはできますか?


1
これらのアサーションはリリース時にオンまたはオフになりますか?
jk。

私は主に、アサーションを手動で有効にする必要があるJavaを使用しています。
フロランツツェライ


「多すぎる」をどのように定義しますか?
ヘイレム

@haylem別のプログラマーがコードを読んで「私はこれらのアサーションにうんざりしている」と言うと、「多すぎる」アサーションがあります。
フロランツツェライ

回答:


26

これらのアサーションは、仮定をテストするのに非常に役立ちますが、別の非常に重要な目的である文書化にも役立ちます。パブリックメソッドの読者は、テストスイートを見なくても、アサートを読んで事前条件と事後条件をすばやく判断できます。このため、テストの理由ではなく、ドキュメントの理由でこれらのアサートを保持することをお勧めします。技術的には、アサーションを複製していますが、アサーションは2つの異なる目的に役立ち、両方で非常に役立ちます。

それらをアサートとして保持することは、コメントを実行するたびに積極的に前提をチェックするため、単にコメントを使用するよりも優れています。


2
非常に良い点!
フロランツツェライ

1
アサーションはドキュメントですが、それはそれらを複製することを合法にしません。それどころか、同じアサーションが複数回必要であると思われる場合、テストの品質について疑念さえも提起します。
ヤムマルコビッチ

1
@YamMarcovic「重複」した2つのコードが2つの異なる、異なる目的に役立つため、これは受け入れられると思います。重複しているにも関わらず、両方の場所にあると便利だと思います。メソッドにアサーションを含めることは文書化にとって非常に貴重であり、テストには明らかに必要です。
オレクシ

2
コードのアサーションは、ユニットテストアサーションを補完するものです。100%のテストカバレッジは得られず、コード内のアサーションにより、失敗するユニットテストをより迅速に絞り込むことができます。
セバスチャンガイガー

15

契約による設計を手作業で行おうとしているようです。

DbCを行うことは良い考えですが、少なくともそれをネイティブにサポートする言語(Eiffelなど)に切り替えることを検討するか、少なくともプラットフォーム用のコントラクトフレームワークを使用することを検討する必要があります(Microsoft Code Contracts for .NETなど)かなりいいです。おそらく最も洗練されたコントラクトフレームワークであり、エッフェルよりも強力です。そうすれば、契約の力をより活用できます。たとえば、既存のフレームワークを使用して、ドキュメントまたはIDEでコントラクトを表示できます(たとえば、.NETのコードコントラクトはIntelliSenseに表示され、VS Ultimateを使用している場合は、コンパイル時に自動定理証明によって静的にチェックすることもできます)同様に、多くのJavaコントラクトフレームワークには、JavaDoc APIドキュメントにコントラクトを自動的に抽出するJavaDocドックレットがあります)。

そして、たとえあなたの状況で手動で行うことに代わるものがなかったとしても、あなたは少なくともそれが何と呼ばれているかを知っており、それについて読むことができます。

つまり、要するに、あなたが実際にDbCをしているのであれば、それを知らなくても、それらの主張はまったく問題ありません。


4

入力パラメーターを完全に制御できない場合は、単純なエラーを事前にテストすることをお勧めします。たとえば、nullで失敗します。

これはテストの重複ではありません。不正な入力パラメーターが与えられた場合にコードが適切に失敗することをテストし、それを文書化する必要があるためです。

読者に理解してもらいたい不変式を明示的に持っている場合を除き、戻り値のパラメータをアサートする必要はないと思います。これもユニットテストの仕事です。

個人的にはassert、Java のステートメントはオフにすることができ、それは偽のセキュリティであるため、Java のステートメントは好きではありません。


1
+1「Javaのアサートステートメントはオフにすることができ、それは偽のセキュリティであるため、好きではありません。」
フロランツツェライ

3

パブリックメソッドでアサーションを使用することはさらに重要だと思います。なぜなら、そこでは入力を制御せず、仮定が破られる可能性が高いからです。

入力条件のテストは、すべてのパブリックメソッドおよび保護されたメソッドで実行する必要があります。入力がプライベートメソッドに直接渡される場合、その入力のテストは冗長になる可能性があります。

出力条件(オブジェクトの状態や戻り値!= nullなど)のテストは、内部メソッド(プライベートメソッドなど)で行う必要があります。追加の計算や内部状態の変更を行わずに、出力がプライベートメソッドからパブリックメソッドの出力に直接渡される場合、パブリックメソッドの出力条件のテストは冗長になる場合があります。

ただし、冗長性によって読みやすさが向上し、保守性も向上する可能性があることにも同意します(将来、直接の割り当てまたは返却が不要になった場合)。


4
エラー条件または無効な引数条件がパブリックインターフェイスのユーザー(呼び出し元)によって引き起こされる可能性がある場合は、これに完全な適切なエラー処理処理を与える必要があります。これは、エンドユーザーにとって意味のあるエラーメッセージをフォーマットし、エラーコード、ログ記録、およびデータの回復または正常な状態への復元を試みることを意味します。適切なエラー処理をアサーションに置き換えると、コードの堅牢性が低下し、トラブルシューティングが困難になります。
-rwong

アサーションには、例外(またはC--のエラーコード)のロギングとスローが含まれる可能性があります(多くの場合、そうすべきです)。
ダニーヴァロッド

3

アサーションと「適切な」エラー/例外処理の実装方法の詳細が答えに関係しているため、この問題について言語にとらわれないことは困難です。これは、JavaとC ++の知識に基づいた0.02ドルです。

プライベートメソッドでのアサーションは、行き過ぎてどこにでも配置しないことを前提として、良いことです。本当に単純なメソッドにそれらを置いたり、不変フィールドのようなものを繰り返しチェックしたりすると、コードが不必要に乱雑になります。

一般に、パブリックメソッドでのアサーションは避けるのが最善です。メソッドコントラクトに違反していないことを確認するなどのことを行う必要がありますが、そうであれば、意味のあるメッセージを含む適切に型指定された例外をスローし、可能な場合は状態を元に戻す必要があります(@rwongが「フル適切なエラー処理処理」)。

一般的に、アサーションを使用し開発/デバッグを支援する必要があります。APIを使用するユーザーが、コードを使用するときにアサーションを有効にすることさえ想定できません。それらはコードの文書化を支援するのにいくらかの用途がありますが、同じことを文書化するより良い方法がしばしばあります(すなわち、メソッドの文書化、例外)。


2

(主に)例外的な回答のリストに加えて、多くのアサートと重複のもう1つの理由は、そのクラスが生涯にわたってどのように、いつ、誰によって変更されるかわからないことです。1年で死ぬスローアウェイコードを書いているのであれば、心配する必要はありません。20年以上使用されるコードを作成している場合は、見た目がまったく異なります。また、あなたが行った仮定はもはや有効ではないかもしれません。その変更を行う人は、アサートに感謝します。

また、すべてのプログラマーが完璧というわけではありません。繰り返しますが、この主張は、これらの「スリップアップ」の1つがあまりにも広がらないことを意味します。

これらのアサート(および仮定に関するその他のチェック)が保守コストの削減に与える影響を過小評価しないでください。


0

アサーションが重複していること自体は間違っていませんが、アサーションを「念のために」することは最善ではありません。厳密には、各テストが正確に何を達成しようとしているかに要約されます。絶対に必要なものだけを表明してください。テストがMoqを使用してメソッドが呼び出されることを確認する場合、そのメソッドが呼び出された後に実際に何が起こるかは問題ではなく、テストはメソッドが呼び出されることを確認することのみに関係します。

1つまたは2つを除き、すべての単体テストに同じアサートセットがある場合、少しリファクタリングすると、リファクタリングの真の影響を示すのではなく、まったく同じ理由ですべてのテストが失敗する可能性があります。すべてのテストを実行すると、すべてのテストが同じアサートを持っているためにすべて失敗し、その失敗を修正し、テストを再度実行し、別の理由で失敗し、その失敗を修正するという状況になる可能性があります。完了するまで繰り返します。

また、テストプロジェクトのメンテナンスを検討してください。新しいパラメーターを追加した場合、または出力が少しずつ微調整された場合はどうなりますか?テストの束に戻ってアサートを変更するか、アサートを追加する必要がありますか?また、コードによっては、すべてのアサートが成功することを確認するためだけに、さらに多くのセットアップが必要になる場合があります。

ユニットテストに重複するアサートを含めることの魅力は理解できますが、実際にはやり過ぎです。最初のテストプロジェクトでも同じ道を歩みました。メンテナンスだけで気が狂ったので、私はそこから離れました。


0

ただし、パブリックメソッドにはユニットテストが使用されます。

テストフレームワークでアクセサが許可されている場合は、プライベートメソッドの単体テストもお勧めです(IMO)。

誰かがあまりにも多くのアサーションをコードの匂いだと考えることはできますか?

私がやります。アサーションは大丈夫ですが、最初の目標はシナリオを作成できないようにすることです。それが起こらないというだけではありません。


-2

それは悪い習慣です。

パブリック/プライベートメソッドをテストしないでください。クラス全体をテストする必要があります。カバレッジが良いことを確認してください。

経験則として各メソッドを個別にテストする(プリミティブ)方法を使用すると、将来クラスをリファクタリングすることは非常に困難になります。そして、それがTDDでできることの1つです。

その上、それ複製です。さらに、コードの作成者は自分が何をしていたのか実際には知らなかったことを示唆しています。そして面白いのは、あなたがすることです

一部の人々は、本番コードよりも慎重にテストを処理します。彼らはすべきではありません。どちらかといえば、私の経験では、テストをさらに慎重に扱うことを示唆しています。彼らはそれだけの価値があります。


-1クラス全体をユニットテストすることは、テストごとに複数のテストを行うことになり、危険な場合があります。これは本当に脆いテストになります。一度に1つの動作をテストする必要があります。これは、多くの場合、テストごとに1つのメソッドのみをテストすることに対応します。
オレクシ

また、「コードの作者は、彼が何をしていたのかを本当に知らなかったことを示唆しています」。将来の開発者は、特定のメソッドが何をするのか明確ではないかもしれません。この場合、アサートの形式で事前条件と事後条件を持つことは、コードの一部を理解する上で非常に貴重です。
オレクシ

2
@Oleksi優れたテストとは、有効な使用シナリオに関するものであり、関係のない最小の詳細をすべて主張することではありません。冗長な主張は、あいまいな意図のために実際にコードの匂いであり、防御的なプログラミングの別の悪い例です。
ヤムマルコビッチ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.