私たちはゲッターとセッターのテストを書くことになっていますか、それともやりすぎですか?
私たちはゲッターとセッターのテストを書くことになっていますか、それともやりすぎですか?
回答:
私はノーと言うでしょう。
@ 100%コードカバレッジを目指すべきだと言いますが、私の意見では危険な注意散漫です。カバレッジが100%の単体テストを作成できますが、まったく何もテストしません。
ユニットテストは、表現力豊かで意味のある方法でコードの動作をテストするためのものであり、ゲッター/セッターは目的を達成するための手段にすぎません。テストでゲッター/セッターを使用して「実際の」機能をテストするという目標を達成すれば、それで十分です。
一方、ゲッターとセッターが単に取得して設定するだけではない場合(つまり、適切に複雑なメソッドである場合)、はい、テストする必要があります。ただし、ゲッターを設定するためだけにユニットテストケースを作成しないでください。これは時間の無駄です。
Roy Osheroveは、著書「The Art Of Unit Testing」で次のように述べています。
プロパティ(Javaのゲッター/セッター)は、通常はロジックを含まず、テストを必要としないコードの良い例です。ただし、注意してください。プロパティ内にチェックを追加したら、ロジックがテストされていることを確認する必要があります。
注:この回答は、悪いアドバイスになる可能性はありますが、賛成票を獲得し続けます。その理由を理解するには、以下の妹をご覧ください。
議論の余地はありますが、この質問に「いいえ」と答えた人は誰でもTDDの基本的な概念を欠いていると私は主張します。
私にとって、TDDをフォローしていれば、答えはイエスです。そうでない場合は、もっともらしい答えはありません。
TDDは、主な利点があるとよく言われます。
プログラマーとして、属性を重要なものと考え、ゲッターとセッターをある種のオーバーヘッドと考えるのはひどく魅力的です。
ただし、属性は実装の詳細であり、セッターおよびゲッターは実際にプログラムを機能させる契約上のインターフェースです。
オブジェクトが次のことを行うことを綴ることははるかに重要です。
クライアントが状態を変更できるようにする
そして
クライアントがその状態を照会できるようにする
次に、この状態が実際に格納される方法(属性が最も一般的ですが、唯一の方法ではありません)。
次のようなテスト
(The Painter class) should store the provided colour
TDD のドキュメント部分にとって重要です。
最終的な実装が取るに足らないこと(属性)であり、防御上の利点がないことは、テストを作成するときにはわかりません。
システム開発の世界で重要な問題の一つは、の欠如である ラウンドトリップエンジニアリング1 -システムの開発プロセスは(ドキュメント、コード)はしばしば矛盾しているアーティファクトそのとりとめのないサブプロセスに細分化されます。
1ブロディ、マイケルL.「ジョンミロポーロス:概念モデリングの種を縫う」。概念モデリング:基礎とアプリケーション。スプリンガーベルリンハイデルベルク、2009年。1-9。
システムとそのコードの仕様が常に一貫していることを保証するのは、TDD のドキュメント部分です。
TDD内では、まず不合格の受け入れテストを記述し、次に合格するコードを記述します。
上位レベルのBDD内では、まずシナリオを記述してから、それを通過させます。
セッターとゲッターを除外する必要があるのはなぜですか?
理論的には、TDD内で1人がテストを記述し、もう1人が合格するコードを実装することは完全に可能です。
だから自問してください:
クラスのテストを書いている人がゲッターとセッターに言及すべきかどうか。
ゲッターとセッターはクラスへのパブリックインターフェイスであるため、答えは明らかにyesです。そうでない場合、オブジェクトの状態を設定または照会する方法はありません。ただし、これを行う方法は必ずしも各メソッドを個別にテストすることではありません。詳細については、他の回答を参照してください。
明らかに、最初にコードを書く場合、答えはそれほど明確ではないかもしれません。
tl; dr: はい、そうすべきです。OpenPojoを使用すると、それは簡単です。
ゲッターとセッターで検証を行う必要があるので、それをテストする必要があります。たとえばsetMom(Person p)
、自分より若い人を母親にすることはできません。
現時点でそれを実行していない場合でも、将来的にそうなる可能性は高いので、これは回帰分析に適しています。あなたが母親をnull
あなたに設定することを許可したい場合は、誰かが後でそれを変更した場合のテストをする必要があります、これはあなたの仮定を強化します。
一般的なバグはvoid setFoo( Object foo ){ foo = foo; }
、どこにあるべきかvoid setFoo( Object foo ){ this.foo = foo; }
です。(最初の場合foo
に書き込まれていることであるパラメータ ではないfoo
上フィールドオブジェクト)。
配列またはコレクションを返す場合、ゲッターが戻る前にセッターに渡されたデータの防御コピーを実行するかどうかをテストする必要があります。
そうでなければ、もしあなたが最も基本的なセッター/ゲッターを持っているなら、それらを単体テストすると、オブジェクトごとにせいぜい約10分を追加することになるので、何が損失でしょうか?動作を追加すると、すでにスケルトンテストがあり、この回帰テストは無料で受けられます。Javaを使用している場合、OpenPojoがあるので言い訳はありません。既存のルールセットを有効にしてから、プロジェクト全体をスキャンして、コード内で一貫して適用されることを確認できます。
彼らの例から:
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
詳しく説明します:
レガシーコード1を効果的に使用することから:
ユニットテストという用語には、ソフトウェア開発において長い歴史があります。単体テストのほとんどの概念に共通するのは、それらがソフトウェアの個々のコンポーネントを分離したテストであるという考えです。コンポーネントとは何ですか?定義はさまざまですが、単体テストでは通常、システムの最もアトミックな動作ユニットに関心があります。手続き型コードでは、単位は多くの場合関数です。オブジェクト指向コードでは、単位はクラスです。
ゲッターとセッターを見つけるOOPでは、ユニットはクラスであり、必ずしも個々のメソッドではないことに注意してください。
すべての要件とテストは、Hoareロジックの形式に従います。
{P} C {Q}
どこ:
{P}
前提条件は、(されて与えられました)C
トリガー条件です(いつ){Q}
事後条件です(then)次に格言が来る:
実装ではなくテスト動作
つまりC
、事後条件がどのように達成されるかをテストするのではなく、それ{Q}
がの結果であることを確認する必要がありC
ます。
OOPに関してC
は、クラスです。したがって、内部効果はテストせず、外部効果のみをテストしてください。
ゲッターとセッターにはいくつかのロジックが含まれる場合がありますが、このロジックが外部に影響しない限り、Beanアクセサーになります2)のテストではオブジェクトの内部を調べる必要があり、それによってカプセル化に違反するだけでなく、実装のテストも行われます。
そのため、Beanのゲッターとセッターを分離してテストしないでください。これは悪いです:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
setVAT
例外がスローされる場合でも、外部効果があるため、対応するテストが適切です。
オブジェクトの内部状態を変更しても外部に影響がない場合、そのような影響が後で発生しても、事実上、オブジェクトの内部状態を変更しても意味がありません。
したがって、setterおよびgetterのテストは、内部メソッドではなく、これらのメソッドの外部効果に関係する必要があります。
例えば:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
あなたは自分で考えるかもしれません:
少し待ってください。
getGross()
ここではテストしていませんsetVAT()
。
しかし、setVAT()
誤動作した場合、そのテストはすべて同じように失敗します。
1 Feathers、M.、2004。レガシーコードを効果的に使用。プレンティスホールプロフェッショナル。
2マーティン、RC、2009年。クリーンなコード:アジャイルソフトウェアの職人技のハンドブック。ピアソン教育。
プロパティには正当な理由がありますが、プロパティを介してメンバーの状態を公開することは悪いデザインであるという一般的なオブジェクト指向の設計信念があります。オープンクローズドプリンシプルに関するRobert Martinの記事 Propertiesがカップリングを促進し、したがって、変更からクラスを閉じる機能を制限することを述べてこれを拡張します。プロパティを変更すると、クラスのすべてのコンシューマーも変更する必要があります。メンバー変数を公開することは必ずしも悪いデザインではなく、単に悪いスタイルかもしれないと彼は認めています。ただし、プロパティが読み取り専用の場合は、悪用や副作用の可能性が低くなります。
単体テストに提供できる最善のアプローチ(これは奇妙に思えるかもしれません)は、できるだけ多くのプロパティを保護または内部にすることです。これにより、ゲッターとセッターの愚かなテストを書くことを妨げながら、結合を防ぎます。
入力フィールドにバインドされているViewModelプロパティなど、読み取り/書き込みプロパティを使用する明確な理由があります。
より実際的には、単体テストはパブリックメソッドを介して機能を駆動する必要があります。テストしているコードがたまたまこれらのプロパティを使用する場合、無料でコードカバレッジを取得できます。これらのプロパティがコードカバレッジによって強調表示されないことが判明した場合、次のような非常に強力な可能性があります。
ゲッターとセッターのテストを作成すると、誤ったカバレッジの感覚が得られ、プロパティが実際に機能動作で使用されているかどうかを判断できなくなります。
これは実際、私のチームと私との間の最近のトピックです。80%のコードカバレッジを目指します。私のチームは、getterとsetterは自動実装されており、コンパイラーが舞台裏でいくつかの基本的なコードを生成していると主張しています。この場合、生成されたコードが邪魔にならないことを考えると、コンパイラーが作成したコードをテストすることは実際には意味がありません。非同期メソッドについてもこのディスカッションを行いました。その場合、コンパイラはバックグラウンドで大量のコードを生成します。これは別のケースであり、テストを行います。長い答えは短く、あなたのチームと一緒に持ってきて、テストする価値があるかどうか自分で決めてください。
また、私たちのようにコードカバレッジレポートを使用している場合は、[ExcludeFromCodeCoverage]属性を追加することができます。私たちの解決策は、ゲッターとセッターを使用するプロパティを持つモデル、またはプロパティ自体にこれを使用することです。これにより、コードカバレッジパーセンテージの計算に使用していると想定して、コードカバレッジレポートの実行時に合計コードカバレッジ%に影響を与えません。ハッピーテスト!
私の意見では、コードカバレッジは、カバーする必要のある機能を見逃したかどうかを確認するための良い方法です。
カバレッジをきれいに着色して手動で検査すると、プレーンゲッターとセッターはテストする必要がないと主張できます(私は常にテストします)。
プロジェクトのコードカバレッジパーセンテージのみをチェックする場合、80%などのテストカバレッジパーセンテージは意味がありません。すべての論理的でない部分をテストして、いくつかの重要な部分を忘れることができます。この場合、100%だけが、すべての重要なコード(およびすべての非論理コードも同様)をテストしたことを意味します。99.9%になるとすぐに、何かを忘れたことがわかります。
ちなみに、コードカバレッジは、クラスを完全に(ユニット)テストしたかどうかを確認する最終チェックです。ただし、100%のコードカバレッジは、必ずしもクラスのすべての機能を実際にテストしたことを意味するわけではありません。したがって、ユニットテストは常にクラスのロジックに従って実装する必要があります。最後に、カバレッジを実行して、何かを忘れたかどうかを確認します。あなたが正しくそれをしたとき、あなたは100%最初にヒットしました。
もう1つ:最近オランダの大手銀行で働いているときに、ソナーが100%コードカバレッジを示していることに気付きました。しかし、私は何かが欠けているのを知っていました。ファイルごとのコードカバレッジパーセンテージを調べると、ファイルのパーセンテージが低いことがわかりました。コードベース全体のパーセンテージは、1つのファイルではパーセンテージが99.9%と表示されないほど大きかった。だからあなたはこれに気をつけたいかもしれません...
JUnitコード自体で達成されたカバレッジを少し分析しました。
カバーされていないコードのカテゴリの1つは、「テストが簡単すぎる」です。これには、JUnitの開発者が行わない単純なゲッターとセッターが含まれますテストしてます。
一方、JUnitには、テストでカバーされていない3行を超える(非推奨ではない)メソッドはありません。
私は言うでしょう:ゲッター/セッターメソッドでのエラーエラーは静かにこっそり侵入し、いくつかの醜いバグを引き起こす可能性があります。
私はこれと他のいくつかのテストを簡単にするためにlibを書きました。JUnitテストで記述しなければならないのはこれだけです。
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
はい、特に取得するアイテムが抽象クラスからサブクラス化されたクラスのオブジェクトである場合。IDEは、特定のプロパティが初期化されていないことを警告する場合と警告しない場合があります。
そして、明らかに無関係ないくつかのテストが NullPointerException
で、最初にgettableプロパティが実際に存在しないことを理解するにはしばらく時間がかかります。
それでも、本番環境で問題を発見するほど悪いことはありません。
すべての抽象クラスにコンストラクタがあることを確認することをお勧めします。そうでない場合、ゲッターのテストはそこでの問題を警告するかもしれません。
プリミティブのゲッターとセッターに関しては、問題は次のようになるでしょう:私は自分のプログラムをテストしていますか、それともJVMまたはCLRをテストしていますか?一般的に言って、JVMをテストする必要はありません。