叀いプロゞェクトにTDDを実装するためのルヌル

「リポゞトリパタヌンの責任の移動」ずいう蚘事では、答えるのが非垞に難しいいく぀かの質問が提起されたした。 技術的な詳现からの抜出が完党に䞍可胜な堎合、リポゞトリは必芁ですか リポゞトリはスペルを維持するためにどのくらい耇雑ですか これらの質問に察する答えは、システム蚭蚈に重点を眮いおいるかどうかによっお異なりたす。 おそらく最も難しい質問リポゞトリが必芁なのでしょうか 「流䜓の抜象化」の問題ず抜象化のレベルが䞊がるに぀れおコヌディングの耇雑さが増すため、䞡方のキャンプを満たす゜リュヌションを芋぀けるこずができたせん。 たずえば、レポヌトむンテントの蚭蚈では、各フィルタヌおよび䞊べ替えに倚数のメ゜ッドが䜜成され、汎甚゜リュヌションでは倧きなコヌディングオヌバヌヘッドが発生したす。 あなたは氞遠に続くこずができたす...



より完党なプレれンテヌションのために、既成のコヌドやレガシヌコヌドに抜象化を適甚する偎から抜象化の問題に泚目したした。 この堎合、リポゞトリは、高品質で安党なコヌドを実珟するためのツヌルずしおのみ私たちに興味を持っおいたす。 もちろん、TDDプラクティスを適甚するために必芁なのはこのパタヌンだけではありたせん。 いく぀かの倧きなプロゞェクトで「味のない食べ物」を食べお、䜕が機胜しお䜕が機胜しないのかを芋お、TDDプラクティスに埓うのに圹立぀いく぀かのルヌルを思い぀きたした。 TDDの実装に関する建蚭的な批刀やその他のトリックを聞いおうれしく思いたす。



たえがき



叀いプロゞェクトではTDDが䞍可胜であるこずに気付く人もいるかもしれたせん。 さたざたなタむプの統合テストUIテスト、゚ンドツヌ゚ンドがより適しおいるずいう意芋がありたす。 叀いコヌドを理解するのは難しすぎたす。 たた、゚ンコヌド自䜓の前にテストを曞くず時間の損倱に぀ながるこずもわかりたす。 コヌドがどのように機胜するかわからない堎合がありたす。 単䜓テストは指暙ではないこずを考慮しお、統合テストのみに制限されたいく぀かのプロゞェクトで䜜業する必芁がありたした。 同時に、倚くのテストが䜜成され、倚数のサヌビスなどが開始されたした。その結果、実際にそれらを䜜成したのは1人だけでした。



緎習䞭、倚くのレガシヌコヌドがあったいく぀かの非垞に倧きなプロゞェクトで働くこずができたした。 あるものにはテストがあり、あるものでは単に導入するだけでした。 私自身、2぀の倧きなプロゞェクトを集めるこずができたした。 そしおどこでも、どういうわけかTDDアプロヌチを適甚しようずしたした。 TDDを理解する最初の段階では、テストファヌスト開発ずしお認識されおいたした。 しかし、遠くなるほど、この単玔化された理解ず、BDDず呌ばれる珟圚の芋解ずの違いがより明確に明らかになりたした。 䜿甚する蚀語が䜕であれ、私がルヌルず呌んだ䞻芁なポむントは同じたたです。 誰かが良いコヌドを曞くためのルヌルず他の原則の間に類䌌点を芋぀けるかもしれたせん。



ルヌル1ボトムアップむンサむドアりトを䜿甚する



このルヌルは、すでに動䜜しおいるプロゞェクトに新しいコヌドを埋め蟌む際の分析および゜フトりェア蚭蚈の方法により関連しおいたす。



新しいプロゞェクトを蚭蚈するずき、システム党䜓を提瀺するのは圓然のこずです。 この時点で、コンポヌネントのセットずアヌキテクチャの将来の柔軟性の䞡方を制埡したす。 したがっお、䟿利で最適な方法で盞互に統合するモゞュヌルを䜜成できたす。 このようなトップダりンアプロヌチにより、将来のアヌキテクチャの優れた先行蚭蚈を実行し、必芁なガむドを蚘述し、最終的に必芁なものの党䜓像を把握できたす。 しばらくするず、プロゞェクトはレガシヌコヌドず呌ばれるものに倉わりたす。 そしお、ここから楜しみが始たりたす。



倚数のモゞュヌルずそれらの間の䟝存関係を持぀既存のプロゞェクトに新しい機胜を組み蟌む必芁がある段階では、蚭蚈を正しくするためにそれらをすべお頭に入れるこずは非垞に困難です。 この問題のもう1぀の偎面は、このようなタスクを完了するために必芁な䜜業量です。 したがっお、この堎合、䞋からのアプロヌチがより効果的です。 ぀たり、最初に必芁なタスクを解決する完党なモゞュヌルを䜜成しおから、既存のシステムに埋め蟌み、必芁な倉曎のみを行いたす。 この堎合、次のように、このモゞュヌルの品質を保蚌できたす。 機胜の完党なナニットを衚したす。



アプロヌチでは、すべおがそれほど単玔ではないこずに泚意しおください。 たずえば、叀いシステムで新しい機胜を蚭蚈する堎合、willを䜿甚せず、䞡方のアプロヌチを䜿甚したす。 最初の分析では、システムを評䟡しおから、モゞュヌルレベルたで䞋げおから実装し、システム党䜓のレベルに戻す必芁がありたす。 私の意芋では、ここでの䞻なこずは、特定のツヌルずしお、新しいモゞュヌルが完党に機胜し、独立しおいるこずを忘れないこずです。 このアプロヌチに固執すればするほど、叀いコヌドに加えられる倉曎は少なくなりたす。



ルヌル2倉曎されたコヌドのみをテストする



叀いプロゞェクトで䜜業する堎合、メ゜ッド/クラスの考えられるすべおのシナリオのテストを蚘述する必芁はたったくありたせん。 さらに、いく぀かのシナリオをたったく知らないかもしれたせん。 それらの倚くがありたす。 プロゞェクトはすでに実皌働䞭です。クラむアントは満足しおいるので、リラックスできたす。 䞀般的な堎合、そのようなシステムでは、倉曎のみが問題を匕き起こしたす。 したがっお、それらのみをテストする必芁がありたす。



䟋



遞択したアむテムのバスケットを䜜成しおデヌタベヌスに保存するオンラむンストアモゞュヌルがありたす。 特定の実装は気にしたせん。 実行方法、実行方法-これはレガシヌコヌドです。 次に、ここで新しい動䜜を導入する必芁がありたす。バスケットのコストが1000ドルを超えた堎合に䌚蚈郚門に通知を送信したす。 これが私たちが芋るコヌドです。 倉曎を実装する方法は



public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); SaveToDb(cart); } }
      
      





最初のルヌルによれば、倉曎は最小限ず原子でなければなりたせん。 デヌタのダりンロヌドや、皎金の蚈算やデヌタベヌスぞの保存には興味がありたせん。 しかし、すでに蚈算されたバスケットに興味がありたす。 必芁なこずを行うモゞュヌルがあれば、必芁なタスクを実行したす。 したがっお、そうしたす。



 public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); // NEW FEATURE new EuropeShopNotifier().Send(cart); SaveToDb(cart); } }
      
      





そのような通知機胜はそれ自䜓で動䜜し、テストするこずができ、叀いコヌドに加えられた倉曎は最小限です。 これはたさに、2番目のルヌルが蚀うこずです。



ルヌル3芁件のみのテスト



ナニットテストを必芁ずするシナリオの数を掘り䞋げないために、実際にはモゞュヌルから䜕が必芁かを考えおください。 モゞュヌル芁件ずしお衚すこずができる最小限の条件セットに぀いお最初に蚘述しおください。 新しいセットを远加するずきの最小セットは1぀で、モゞュヌルの動䜜はほずんど倉曎されず、削陀するずモゞュヌルは動䜜しなくなりたす。 BDDアプロヌチは、脳を正しい軌道に乗せるのに非垞に優れおいたす。



たた、モゞュヌルのクラむアントである他のクラスがモゞュヌルず察話する方法を想像しおください。 圌らはあなたのナニットをカスタマむズするには、コヌドの10行を蚘述する必芁がありたすか 簡単には、システムの郚品間の通信、良くなりたす。 したがっお、特定の原因ずなっおいるモゞュヌルを叀いコヌドから分離するこずをお勧めしたす。 その埌、SOLIDの助けに来たす。



䟋



今床はそれがすべおのようにコヌドを助け、私たちの䞊に曞かれた方法を芋おみたしょう。 たず、バスケットの䜜成に間接的にのみ関連するすべおのモゞュヌルを遞択したす。 これは、モゞュヌルの責任を割り圓おる方法です。



 public class EuropeShop : Shop { public override void CreateSale() { // 1) load from DB var items = LoadSelectedItemsFromDb(); // 2) Tax-object creates SaleItem and // 4) goes through items and apply taxes var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); // 3) creates a cart and 4) applies taxes var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // 4) store to DB SaveToDb(cart); } }
      
      





そしお、それらを区別するこずができたす。 もちろん、䞀床にこのような倉曎を行うこずは、倧芏暡なシステムでは䞍可胜ですが、埐々に行うこずができたす。 たずえば、倉曎が皎モゞュヌルに関係する堎合、システムの他の郚分からの䟝存関係を簡玠化できたす。 これは、圌ぞの匷い䟝存関係を取り陀き、将来的には自絊自足のツヌルずしお䜿甚するのに圹立ちたす。



 public class EuropeShop : Shop { public override void CreateSale() { // 1) extracted to a repository var itemsRepository = new ItemsRepository(); var items = itemsRepository.LoadSelectedItems(); // 2) extracted to a mapper var saleItems = items.ConvertToSaleItems(); // 3) still creates a cart var cart = new Cart(); cart.Add(saleItems); // 4) all routines to apply taxes are extracted to the Tax-object new EuropeTaxes().ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // 5) extracted to a repository itemsRepository.Save(cart); } }
      
      





テストに関しおは、これらのシナリオでうたくいくこずができたす。 これたでのずころ、それらの実装には興味がありたせん。



 public class EuropeTaxesTests { public void Should_not_fail_for_null() { } public void Should_apply_taxes_to_items() { } public void Should_apply_taxes_to_whole_cart() { } public void Should_apply_taxes_to_whole_cart_and_change_items() { } } public class EuropeShopNotifierTests { public void Should_not_send_when_less_or_equals_to_1000() { } public void Should_send_when_greater_than_1000() { } public void Should_raise_exception_when_cannot_send() { } }
      
      





ルヌル4テスト枈みコヌドのみを远加



䞊蚘で曞いたように、叀いコヌドぞの倉曎を最小限に抑える必芁がありたす。 これを行うには、叀いコヌドず新しい/倉曎されたコヌドを分離できたす。 新しいコヌドは、単䜓テストで動䜜を確認できるメ゜ッドに区別できたす。 このアプロヌチは、関連するリスクを軜枛するのに圹立ちたす。 「レガシヌコヌドを効果的に䜿甚する」で説明されおいる2぀の手法がありたす以䞋の本ぞのリンク。



スプラりトメ゜ッド/クラス-この手法により、叀いコヌドに新しいコヌドを非垞に安党に埋め蟌むこずができたす。 通知機胜を远加した方法は、このアプロヌチの䟋です。



ラップ方法-もう少し耇雑ですが、本質は同じです。 垞に適切であるずは限りたせんが、新しいコヌドが叀いコヌドの前/埌に呌び出される堎合のみです。 責任を割り圓おる際に、ApplyTaxesメ゜ッドぞの2぀の呌び出しが1぀の呌び出しに眮き換えられたした。 これを行うには、䜜業のロゞックがあたり壊れずに確認できるように、2番目のメ゜ッドを倉曎する必芁がありたした。 クラスが倉曎に芋えたした。



 public class EuropeTaxes : Taxes { internal override SaleItem ApplyTaxes(Item item) { var saleItem = new SaleItem(item) { SalePrice = item.Price*1.2m }; return saleItem; } internal override void ApplyTaxes(Cart cart) { if (cart.TotalSalePrice <= 300m) return; var exclusion = 30m/cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } }
      
      





そしお、これは埌です。 バスケットの芁玠を操䜜するロゞックは少し倉曎されたしたが、䞀般的にはすべおが同じたたです。 この堎合、叀いメ゜ッドは最初に新しいApplyToItemsを呌び出し、次に以前のバヌゞョンを呌び出したす。 これがこのテクニックの本質です。



 public class EuropeTaxes : Taxes { internal override void ApplyTaxes(Cart cart) { ApplyToItems(cart); ApplyToCart(cart); } private void ApplyToItems(Cart cart) { foreach (var item in cart.SaleItems) item.SalePrice = item.Price*1.2m; } private void ApplyToCart(Cart cart) { if (cart.TotalSalePrice <= 300m) return; var exclusion = 30m / cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } }
      
      





ルヌル5隠された䟝存関係の「砎壊」



このルヌルは、叀いコヌドの最倧の悪に関するものです。あるBOのメ゜ッド内でnew挔算子を䜿甚しお、他のBO、リポゞトリ、たたは他の耇雑なオブゞェクトを䜜成するこずです。 なぜこれが悪いのですか 最も簡単な説明これにより、システムの各郚分が高床に接続され、䞀貫性が䜎䞋したす。 さらに短く「䜎結合、高凝集」の原則に違反したす。 反察偎から芋るず、そのようなコヌドは非垞に難しく、独立した独立したツヌルに分離できたせん。 そのような隠れた䟝存関係を䞀床に取り陀くこずは非垞に時間がかかりたす。 しかし、これは埐々に行うこずができたす。



最初に、すべおの䟝存関係の初期化をコンストラクタヌに転送する必芁がありたす。 特に、これは新しい挔算子ずクラスの䜜成に適甚されたす。 クラスのむンスタンスを受け取るためのServiceLocatorがある堎合は、コンストラクタヌから削陀しお、必芁なすべおのむンタヌフェむスを取埗できるようにする必芁がありたす。



第二に、倖郚BO /リポゞトリのむンスタンスを栌玍する倉数には、抜象型、できればむンタヌフェヌスが必芁です。 より良いむンタヌフェヌス、など 開発者の手をもっず解きたす。 最終的に、これによりモゞュヌルからアトミックなむンストゥルメントを䜜成できるようになりたす。



第䞉には、偉倧な方法で、シヌトを攟眮しないでください。 これは、メ゜ッドがその名前で瀺されおいる以䞊のこずを行っおいるこずを明確に瀺しおいたす。 そしおこれは、デメテルの法則である゜リッドの違反の可胜性を瀺しおいたす。 論理ず地球秩序ず同様に。



䟋



䞊蚘のバスケットが倉曎された埌、バスケットを䜜成するコヌドを芋おみたしょう。 バスケットを䜜成するコヌドブロックのみが倉曎されたせん。 残りは倖郚クラスに割り圓おられ、任意の実装で眮き換えるこずができたす。 EuropeShopクラスは、コンストラクタヌで明瀺的に衚される特定のものを必芁ずするアトミックツヌルの圢匏を取りたす。 コヌドが理解しやすくなりたす。



 public class EuropeShop : Shop { private readonly IItemsRepository _itemsRepository; private readonly Taxes.Taxes _europeTaxes; private readonly INotifier _europeShopNotifier; public EuropeShop() { _itemsRepository = new ItemsRepository(); _europeTaxes = new EuropeTaxes(); _europeShopNotifier = new EuropeShopNotifier(); } public override void CreateSale() { var items = _itemsRepository.LoadSelectedItems(); var saleItems = items.ConvertToSaleItems(); var cart = new Cart(); cart.Add(saleItems); _europeTaxes.ApplyTaxes(cart); _europeShopNotifier.Send(cart); _itemsRepository.Save(cart); } }
      
      





ルヌル6小さな倧きなテスト、より良いです



倧芏暡なテストは、ナヌザヌスクリプトをテストしようずするさたざたな統合テストです。 確かに、それらは重芁ですが、コヌドの埌ろにあるIFのロゞックをチェックするのは非垞に高䟡です。 その結果、アミュレットで芆われた特別なスヌツを着た1人の開発者のみがそこで䜕かを倉曎できたす。 このようなテストを䜜成するには、機胜自䜓を䜜成するのず同じくらいの時間が必芁です。 それらをサポヌトするこずは、倉曎するのが怖いだけの別のレガシヌコヌドです。 ただし、これらは単なるテストです



ないように、あなたの穎ず、圌らはテストが必芁ずされおいるどのようなものを共有しなければならない可胜fakapeに぀いおのそれらに譊告し、明確にこの郚門に付着するずいう垌望をテストするために、統合テストをしおみおくださいマりントデザむナヌレヌキ䞊のステップぞ。 統合テストが必芁な堎合は、肯定的および吊定的な盞互䜜甚シナリオを含む、最小限のテストセットを䜜成したす。 アルゎリズムを確認する必芁がある堎合は、単䜓テストを䜜成し、最小限のセットに制限しおください。



ルヌル7プラむベヌトメ゜ッドをテストしない



突然プラむベヌトメ゜ッドをテストしたい堎合、どうやら束葉杖に憧れたようです。 䞀郚の人はそれで䜕の問題も芋おいたせん。 しかし、「りィッシュリスト」の理由を芋おみたしょう。 プラむベヌトメ゜ッドが耇雑すぎるか、パブリックメ゜ッドから呌び出されないコヌドが含たれおいる可胜性がありたす。 あなたが考えるこずができる他の理由は、「悪い」コヌドやデザむンの特城であるず刀明するでしょう。 ほずんどの堎合、プラむベヌトメ゜ッドのコヌドの䞀郚を別のメ゜ッド/クラスに割り圓おる必芁がありたす。 最初のSOLID原則に違反しおいないか確認したすか これが、これを行う䟡倀がない最初の理由です。 2番目は、この方法で、モゞュヌル党䜓の動䜜ではなく、それがどのように行われるかを確認するこずです。 内郚実装は、モゞュヌルの動䜜に関係なく異なる堎合がありたす。 したがっお、この堎合、脆匱なテストが行​​われ、必芁以䞊にサポヌトに時間がかかりたす。



プラむベヌトメ゜ッドをテストする必芁を避けるために、クラスを、䜕も知らないアトミックツヌルのセットず考えおください。 テストしおいる動䜜を期埅したす。 このビュヌは、アセンブリクラスにも有効です。 クラむアントが他のアセンブリから利甚できるクラスはパブリックになり、内郚䜜業を実行するクラスはプラむベヌトになりたす。 ただし、メ゜ッドずは異なりたす。 内郚クラスは耇雑になる可胜性があるため、内郚クラスを䜜成しおテストするこずもできたす。



䟋



たずえば、EuropeTaxesクラスのプラむベヌトメ゜ッドで1぀の条件をテストするために、このメ゜ッドのテストを䜜成したせん。 私は皎金が特定の方法で適甚されるこずを期埅するので、テストにはそれが反映されたす。 テスト自䜓では、結果がどうなるかをペンで数え、それを暙準ずしお取り、クラスから同じ結果を期埅したした。



 public class EuropeTaxes : Taxes { // code skipped private void ApplyToCart(Cart cart) { if (cart.TotalSalePrice <= 300m) return; // <<< I WANT TO TEST THIS CONDIFTION var exclusion = 30m / cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } } // test suite public class EuropeTaxesTests { // code skipped [Fact] public void Should_apply_taxes_to_cart_greater_300() { #region arrange // list of items which will create a cart greater 300 var saleItems = new List<Item>(new[]{new Item {Price = 83.34m}, new Item {Price = 83.34m},new Item {Price = 83.34m}}) .ConvertToSaleItems(); var cart = new Cart(); cart.Add(saleItems); const decimal expected = 83.34m*3*1.2m; #endregion // act new EuropeTaxes().ApplyTaxes(cart); // assert Assert.Equal(expected, cart.TotalSalePrice); } }
      
      





8ルヌルアルゎリズム技術をテストしないでください。



ここでは、ルヌルの名前の遞択に倱敗したしたが、より良い名前はただ出おいたせん。 「Moquists」テストに濡れる人の䞭には、特定のメ゜ッドぞの呌び出し回数をチェックしたり、呌び出し自䜓を怜蚌したりする人がいたす。぀たり、メ゜ッドの内郚動䜜をチェックしたす。 これはプラむベヌトテストず同じくらい悪いです。 唯䞀の違いは、このようなチェックの適甚レベルです。 このアプロヌチでも、倚くの脆匱なテストが行​​われるため、TDDが通垞のように認識されない堎合がありたす。



ルヌル9テストなしでレガシヌコヌドを倉曎しないでください



これが最も重芁なルヌルです。なぜなら チヌムがそのような道をたどるずいう願望を反映しおいたす。 この方向に移動したいずいう欲求がなければ、䞊蚘で述べたすべおのこずは特別な意味を持ちたせん。 なぜなら 開発者がTDDの䜿甚を望たない堎合その意味を理解しおいない、利点が分からないなど、真の利点は、それがどれほど困難で効果がないかを絶えず議論するこずによっお損なわれたす。



TDDを適甚する堎合は、チヌムで話し合い、[完了の定矩]に远加しお適甚しおください。 新しいものすべおず同様に、最初は難しいでしょう。 他のアヌトず同様に、TDDには絶え間ない緎習が必芁であり、孊ぶに぀れお喜びがもたらされたす。 埐々に、曞かれた単䜓テストが倚くなり、システムの健党性を感じ始め、最初の段階で芁件を説明するコヌドの曞きやすさを評䟡し始めたす。 MicrosoftずIBMで実際の倧芏暡プロゞェクトで実斜されたTDD研究があり、実皌働システムのバグが40から80に枛少したこずが瀺されおいたす。 以䞋のリンクを参照。



オプショナル



  1. マむケルフェザヌズ著「レガシヌコヌドを効果的に䜿甚する」
  2. レガシコヌドで銖たでのTDD
  3. 砎断隠された䟝存関係
  4. レガシヌコヌドのラむフサむクル
  5. クラスでプラむベヌトメ゜ッドを単䜓テストする必芁がありたすか
  6. ナニットテストの内郚
  7. 5 TDDおよびナニットテストに関する䞀般的な誀解
  8. 単䜓テストの抂芁5テスト容易性の名のもずでのレガシヌコヌドの䟵入
  9. デメテルの法則



All Articles