26


7

TDDを使用して開発されたプロジェクトは、包括的な単体テストのセットを生成するため、リファクタリングがより簡単であると私は聞いたことがあります。 ただし、これまでに説明した例はすべて、リファクタリングの実装、たとえばアルゴリズムをより効率的なものに変更する方法を扱っています。

設計がまだ行われている初期段階では、リファクタリングアーキテクチャがより一般的であることがわかりました。 インターフェースが変更され、新しいクラスが追加されました しかし、各テストケースがこれらの不安定なクラスと密接に関連している場合、デザインを変更するたびにテストケースを常に書き直す必要があるでしょうか。

TDDのどのような状況下でテストケースを変更したり削除しても大丈夫ですか? テストケースを変更してもテストケースが破損しないことをどのように確認できますか? さらに、包括的なテストスイートを絶えず変更されるコードと同期させる必要があるのは面倒なことです。 ソフトウェアが構築され、安定して機能するようになれば、単体テストスイートはメンテナンス中に非常に役立ちますが、TDDが早い段階でも役立つことになっているので、それはゲームの後半です。

最後に、TDDやリファクタリングに関する優れた本は、この種の問題に対処するでしょうか。 もしそうなら、どちらをお勧めですか?

8 Answer


10


注意する必要があることの1つは、TDDは主にテスト戦略ではなく、設計戦略ではないということです。 あなたが最初にテストを書く、それはあなたがより良い分離されたデザインを思いつくのを助けるので。 また、より優れた分離設計はリファクタリングも容易です。

クラスやメソッドの機能を変更すると、テストも変更する必要があります。 実際、TDDに従うことは、もちろんテストを最初に変更することを意味します。 1つの機能だけを変更するために多くのテストを変更する必要がある場合は、通常、ほとんどのテストが動作を過剰に指定していることを意味します。 もう1つの問題は、責任が生産コードにうまくカプセル化されていないことです。

それが何であれ、小さな変更のために失敗する多くのテストを経験するとき、あなたはそれが将来再び起こらないようにあなたのコードをリファクタリングするべきです。 その方法は常に明らかではありませんが、いつでも可能です。

より大きなデザイン変更で、物事はもう少し複雑になることができます。 はい、新しいテストを書いて古いテストを破棄する方が簡単なことがあります。 時々、あなたは少なくともリファクタリングされる部分全体をテストするいくつかの統合テストを書くことができます。 そしてあなたはまだ大体影響を受けていないあなたの受け入れテストのスイートを持っていることを望みます。

まだ読んでいませんが、「XUnitテストパターン - テストコードのリファクタリング」という本について良いことを聞いたことがあります。


8


_ さらに、包括的なテストスイートを絶えず変更されるコードと同期させる必要があるのは面倒なことです。 ソフトウェアが構築され、安定して機能するようになれば、単体テストスイートはメンテナンス中に非常に役立ちますが、TDDが早い段階でも役立つことになっているので、それはゲームの後半です。 _

アーキテクチャーの大きな変更が行われているときに、ユニットテストスイートを導入することによるオーバーヘッドがこれらの初期の変更で生じる可能性があることには同意しますが、ユニットテストを行うことのメリットはこの欠点をはるかに上回ります。 私たちの単体テストはコードベースの第二級の市民であると考える傾向があるので、私たちはそれらを台無しにしなければならないことに憤慨します。 しかし時間が経つにつれて、私はそれらに頼り、それらの有用性を認めるようになり、コードベースの他の部分としてそれらを重要性が低く、保守に値するものではないと考えるようになりました。

主要な建築上の「変化」は、本当にリファクタリングだけなのか? あなたがリファクタリングをしているだけで、劇的にテストが失敗し始めたなら、それはあなたがどこかに誤って機能を変更したことをあなたに伝えるかもしれません。 これは単体テストがあなたを捕らえるのを助けることになっているものです。 機能とアーキテクチャを同時に大幅に変更する場合は、速度を落としてその赤/緑/リファクタリングの溝に入ることを検討してください。追加のテストなしで新しい(または変更された)機能はありません。リファクタリング中の機能(およびテストの中断)

更新(コメントに基づく):

@Cybisは、リファクタリングによって動作が変わるべきではないので、リファクタリングによってテストが中断されるべきではないとの私の主張に興味深い異議を提起しました。 彼の異議は、リファクタリングすることでAPIが変更されるため、 "break"がテストされることです。

第一に、私は誰でもリファクタリングに関する標準的なリファレンスを訪問することを勧めます:http://www.martinfowler.com/bliki/refactoring.html[Martin Fowler’s bliki]。 ちょうど今、私はそれをレビューしました、そして、いくつかの事が私に飛び出します:

  • http://www.martinfowler.com/bliki/IsChangingInterfacesRefactoring.html [ インターフェイスリファクタリングの変更?] Martinは、リファクタリングを「動作を維持する」変更と呼びます。つまり、インターフェイス/ APIが変更されると、そのインターフェイス/ APIのすべての呼び出し元も変更する必要があります。 テストを含めて、私は言います。

  • それは_行動_が変わったという意味ではありません。 再び、ファウラー リファクタリングの彼の定義は、変更が振る舞いを維持することであると強調しています。

この点を考慮すると、リファクタリング中にテストを変更しなければならないとしたら、私はこれをテストを「壊す」とは思いません。 コードベース全体の動作を維持するという、リファクタリングの一部に過ぎません。 テストを変更しなければならないことと、コードベースの他の部分をリファクタリングの一部として変更しなければならないこととの間に違いはありません。 (これは、テストをコードベースの第一級の市民と見なすことについて前に述べたことに戻ります。)

さらに、リファクタリングが完了したら、テスト、修正したテストでさえも合格することを期待しています。 そのテストがテストであったとしても(おそらくそのテスト内のassertは)、リファクタリングが行われた後も有効であるはずです。 さもなければ、それは振る舞いが何らかの形でリファクタリングの間に変化/後退したという赤い旗です。

その主張は無意味に思えるかもしれませんが、それについて考えてみてください。プロダクションコードベースでコードブロックを移動して、新しいコンテキスト(新しいクラス、新しいメソッドシグネチャなど)で機能し続けることを期待することについては何も考えません。 私はテストについても同じように感じます。おそらく、リファクタリングはテストが呼び出さなければならないAPI、あるいはテストが使わなければならないクラスを変更しますが、結局、テストのポイントはリファクタリングのために変わらないはずです。

(私が考えることができる唯一の例外は、LinkedListをArrayListなどに置き換えるなど、リファクタリング中に変更したいと思う可能性がある低レベルの実装の詳細をテストするテストです。 しかしその場合、テストはテストをやり過ぎて、硬直しすぎて脆弱すぎると主張するかもしれません。)


6


リファクタリングにもたらされたTDDの主な利点は、開発者が彼らのコードを変更するためにより多くの勇気を持っているということです。 単体テストの準備が整ったので、開発者はあえてコードを変更してから実行するだけです。 xUnitバーがまだ緑色であれば、先に進む自信があります。

個人的には、私はTDDが好きですが、オーバーTDDを奨励しません。 つまり、あまりにも多くのユニットテストケースを書かないでください。 単体テストで十分なはずです。 単体テストをやり過ぎているのであれば、アーキテクチャーを変更したいときにジレンマに陥っていると感じるかもしれません。 製品コードの大きな変更の1つは、多くのユニットテストケースの変更をもたらします。 そのため、単体テストを十分に行ってください。


4


TDDは最初に「失敗テスト」を書くと言っています。 テストは、開発者がユースケース/ストーリー/シナリオ/プロセスが達成することになっていることを理解していることを示すために書かれています。

それからテストを満たすためにコードを書きます。

要件が変更された、または誤解された場合は、まずテストを編集または書き直してください。

赤バー、緑バー、そうでしょ?

Fowlerの_Refactoring_はリファクタリングのための参照ですが、不思議なことに十分です。

Scott Amblerによる一連の記事(Dr)。 Dobb’s( 'The Agile Edge ??')は、実際にはTDDの優れたチュートリアルです。


3


_ たとえば、アルゴリズムをより効率的なアルゴリズムに変更します。 _

これはリファクタリングではありません、これはパフォーマンス最適化です。 リファクタリングとは、既存のコードの設計を改良することです。 つまり、開発者のニーズに合わせて形を変えることです。 外部から見える動作に影響を与えることを意図してコードを変更することはリファクタリングではなく、効率のための変更も含まれます。

TDDの価値の一部はあなたのテストがあなたがその結果を作り出す方法を変える間あなたが目に見える行動を一定に保つのを助けるということです。


2


私はお勧めします(他の人がそうであるように):


1


ケントベックのTDD本。

最初にテストしてください。 必要でなければ、以下のS.O.L.I.D OOPの原則と優れたリファクタリングツールの使用が不可欠です。


1


_ TDDのどのような状況下でテストケースを変更したり削除しても大丈夫ですか? テストケースを変更してもテストケースが破損しないことをどのように確認できますか? さらに、包括的なテストスイートを絶えず変更されるコードと同期させる必要があるのは面倒なことです。 _

テストと仕様のポイントは、システムの正しい動作を定義することです。 だから、非常に簡単に:

if definition of correctness changes
  change tests/specs
end

if definition of correctness does not change
  # no need to change tests/specs
  # though you still can for other reasons if you want/need
end

そのため、アプリケーション/システムの仕様や目的の動作が変更された場合は、テストを変更する必要があります。 そのような状況でコードだけを変更し、テストを変更しないことは明らかに壊れた方法論です。 あなたはそれを「苦痛」と見なすかもしれませんが、テストスイートがないのはもっと苦痛です。 :) As others have mentioned, having that freedom to "dare" to change コードは実に非常に力を与え、解放しています。 :)