テスト可胜なコヌドの曞き方

画像


あなたがプログラマヌたたはより悪いアヌキテクトである堎合、このような簡単な質問に答えるこずができたすテスト䞍可胜なコヌドを曞く方法は あなたはそれに぀いお考えたしたか テストされおいないコヌドを実珟するための少なくずも3぀の方法にほずんど名前を付けられない堎合は、この蚘事が圹立ちたす。

倚くの人が蚀うでしょうなぜ私はテストされおいないコヌドを曞くかを知る必芁があるのですか、あなたは私に悪いこずを教えたいですか 私は答えたすテストされおいないコヌドの兞型的なパタヌンを知っおいれば、そうであれば、プロゞェクトでそれらを簡単に芋るこずができたす。 そしお、ご存知のように、問題の認識はすでに治療ぞの道の半分です。 この蚘事は、そのような治療が実際にどのように行われるかに぀いおも答えおいたす。 猫をお願いしたす。

この蚘事では特定のプログラミング蚀語に焊点を圓おるこずはせず、以䞋はすべおの手続き蚀語に関連しおいたす。 しかし、私にずっおは、この蚘事は動的なむンタヌプリタヌ蚀語でプログラムする人にずっお特に有甚であり、型付きコンパむル蚀語の開発に本栌的な経隓はなかったようです。 このカテゎリの開発者のコ​​ヌドで、以䞋に説明するパタヌンに最も頻繁に気づきたした。 䟋は、Java、C、およびTypeScriptに䌌た擬䌌コヌドになりたす。

この蚘事では、テストの曞き方の問題に぀いおは觊れおいたせん。 このテヌマに関する倚くの蚘事がありたす。 コヌドを簡単か぀矎しくテストできるようにするためのコヌドの曞き方の問題を怜蚎し、結果ずしお埗られるテストはシンプルで保守可胜になりたす。 さらに、テストずいう甚語は矎しく、クリヌンな単䜓テストを意味し、さたざたなハックなしで、䟝存関係をオンザフラむで倉曎するための远加の「マゞック」ラむブラリ、およびテストの読み取りずサポヌトを困難にするその他のこずなく曞かれおいたす。 それは、蚀葉の最高の意味でのテストです。

ちょっずした哲孊。 テストを曞くべきですか 私の意芋開発するプロゞェクトを䜜成する堎合は、テストだけが必芁です。 テストが盎接的な機胜を実行するずいう事実コヌドが芁件に準拠しおいるかどうかを確認できるに加えお、副䜜甚ずしおクラス蚭蚈を「たっすぐに」したす。 これは、「曲がった」デザむンのクラスではテストを蚘述できないためです。したがっお、テスト容易性を実珟するには、クラスをリファクタリングする必芁がありたす。 たたは、コヌドの前にテストが蚘述されおいる堎合、クラス蚭蚈はすぐに生たれたす。 テスト察象のコヌドは再利甚可胜です。テストで再利甚できるためです。 たた、再利甚性は適切なクラス蚭蚈の重芁な基準です。 たた、テストは、必ずしもではありたせんが、おそらく、アプリケヌション党䜓のアヌキテクチャを改善したす。 誰かがよく指摘したように、クラス蚭蚈は、このクラスを単䜓テストでテストできる皋床にしか優れおいたせん。

テストされたコヌドの䞻なキラヌのリスト




知識マむニング


知識の抜出は、メ゜ッドが1぀の匕数セットを必芁ずし、それらを盎接䜿甚せず、他のオブゞェクトの怜玢でこれらの匕数を「遞択」し始めるずきに発生したす。 兞型的なシナリオ

メ゜ッドたたはコンストラクタヌには、実際に動䜜するために必芁なものが必芁です。 圌は、あらゆる皮類の仕事をしながら、これらすべおを取埗/蚈算する方法を気にする必芁はありたせん。 䜜業には、最䜎限必芁な匕数のセットが必芁です。

人生の䟋店で賌入代金を支払うように求められたら、䜕をしたすか財垃を枡し、レゞ係自身がそこからお金を受け取るようにしたすか、それずもお金を䞎えたすか 類掚は明らかだず思いたす-レゞ係は、蚈算方法を入力するために、りォレットのクラスではなく、クラスのお金を必芁ずするべきです。
いく぀かの䟋を芋おみたしょう。

前テストされおいないコヌド
class DiscountCard { DiscountCard(UserContext userContext) { this.user = userContext.getUser(); this.level = userContext.getLevel(); this.order = userContext.getOrder(); } // ... } //  UserContext userContext = new UserContext(); userContext.setUser(new User("Ivan")); PlanLevel level = new PlanLevel(143, "yearly"); userContext.setLevel(level); Order order = new Order("SuperDeluxe", 100, true); userContext.setOrder(order); DiscountCard discountCard = new DiscountCard(userContext); //   


DiscountCardはuserContextを盎接䜿甚したせん。 userContextには初期化のために倚数のオブゞェクトを含めるこずができるため、テストを䜜成する人は、DiscountCardクラスのデバむスを調べお、そこで本圓に必芁なオブゞェクトを理解する必芁がありたす。 そしお、これは䜕か間違ったこずをする時間ずリスクであり、その結果、テストが䞍正確になる可胜性がありたす。 本圓に必芁なものをDiscountCardに芁求させたしょう

埌テストコヌド
 class DiscountCard { DiscountCard(User user, PlanLevel level, Order order) { this.user = user; this.level = level; this.order = order; } // ... } //  User user = new User("Ivan"); PlanLevel level = new PlanLevel(143, "yearly"); Order order = new Order("SuperDeluxe", 100, true); DiscountCard discountCard = new DiscountCard(user, level, order); //   


この䟋は、アプリケヌションのすべおの䟝存関係を保存する有名なServiceLocatorパタヌンも意図的に瀺しおいたす。 そのため、このようなパタヌンはアンチパタヌンず芋なされたす。 それを避けるようにしおください。

知識抜出の別の䟋を考えおみたしょう。

前テストされおいないコヌド
 class SalesTaxCalculator { TaxTable taxTable; SalesTaxCalculator(TaxTable taxTable) { this.taxTable = taxTable; } float computeSalesTax(User user, Invoice invoice) { //  "user"    Address address = user.getAddress(); float amount = invoice.getSubTotal(); return amount * taxTable.getTaxRate(address); } } //  SalesTaxCalculator calc = new SalesTaxCalculator(new TaxTable()); Address address = new Address(" 23 ..."); //     "user" User user = new User(address, ...); Invoice invoice = new Invoice(1, new ProductX(95.00)); assertEquals(calc.computeSalesTax(user, invoice), 100); 


テストでは、Userクラスを䜜成する必芁がありたすが、そこからはアドレスのみが必芁です。 Invoiceクラスに぀いおも同じこずが蚀えたす。 繰り返したすが、テストを曞いた人から、SalesTaxCalculatorコヌドを「スキャン」しお、そこで本圓に必芁なものを芋぀け出す必芁がありたす。 面倒な堎所。

たた、SalesTaxCalculatorは、UserクラスずInvoiceクラスを持たない別のプロゞェクトで再利甚できたせん。

埌テストコヌド
 class SalesTaxCalculator { TaxTable taxTable; SalesTaxCalculator(TaxTable taxTable) { this.taxTable = taxTable; } float computeSalesTax(Address address, float amount) { return amount * taxTable.getTaxRate(address); } } //  SalesTaxCalculator calc = new SalesTaxCalculator(new TaxTable()); Address address = new Address(" 23 ..."); assertEquals(calc.computeSalesTax(address, 95.00), 100); 


これで、クラスは必芁なものだけを必芁ずし、テストは透過的になりたす。

ビゞネスコヌドの新しいオペレヌタヌ


おそらく、このパタヌンには、テストされおいないコヌドを䜜成するための金メダルを䞎えるこずができたす。
簡単な䟋から解析を始めたしょう

前テストされおいないコヌド
 class House { Kitchen kitchen; Bedroom bedroom = new Bedroom(); House() { this.kitchen = new Kitchen(new Refrigerator()); } // ... } //  House house = new House(); // ,    //    kitchen  bedroom 


このコヌドのすべおが悪いです。 Houseメ゜ッドを呌び出すず、キッチンやベッドルヌムの呌び出しが発生し、それらを制埡しないため、テストできたせん。 デヌタベヌスず通信するか、ネットワヌクにリク゚ストを送信するず、テストは終了したす。 ポリモヌフィズムの助けを借りお、キッチンやベッドルヌムを亀換するこずは䞍可胜であり、私たちの家は特定のクラスに厳密に結び぀いおいたす。 コヌドをテスト可胜にする方法は

埌テストコヌド
 class House { Kitchen kitchen; Bedroom bedroom; House(Kitchen kitchen, Bedroom bedroom) { this.kitchen = kitchen; this.bedroom = bedroom; } // ... } //  Kitchen kitchen = new DummyKitchen(null); Bedroom bedroom = new DummyBedroom(); House house = new House (kitchen, bedroom); // ,      


珟圚、Houseクラスでは、デザむナヌが仕事に必芁なものをすべお必芁ずしおいたす。 そしお、圌はそれがどこから来たか気にしたせん。 すべおのビゞネスクラスは、そのコンストラクタで䟝存関係の既補のむンスタンスを必芁ずする必芁がありたす。これが䞻な原則です。

しかし、読者の䞭には、「ちょっず埅っおください。 コンストラクタヌですべおの䟝存関係を芁求する必芁がある堎合、「深い」子クラスアプリケヌション䟝存関係グラフの深さにあるクラスを䜜成するために、このクラスのすべおの䟝存関係を芪クラスを通じお転送する必芁がありたす。 そしお、これは、「䞊䜍」クラス䟝存関係グラフの先頭に近いクラスの蚭蚈者が、あらゆるもののクラスタヌに倉わるずいう事実に぀ながりたす。 䟋ずしお、これは、 新しいHouseを䜜成するクラスがキッチンずベッドルヌムのコンストラクタヌ自䜓を芁求する必芁があるこずを意味したす。 しかし、なぜ圌はそれらに぀いお知る必芁がありたすか これはナンセンスです。」

この掚論にのみ1぀の間違いがありたす。 䞊蚘の原則に埓っお、Houseを䜜成するクラスはたったく䜜成すべきではありたせん。 結局のずころ、原則によれば、圌自身が自分に応じお䞋院を芁求するべきです。 しかし、最埌に、Houseクラスはどこから来るのでしょうか アプリケヌションの開始時に䜜成されたすハりスのラむフタむムがアプリケヌションのラむフタむムず䞀臎する堎合たたは工堎によっお䜜成されたすハりスのラむフタむムがアプリケヌションのラむフタむムより短い堎合

クラスハりスの䜜成
 class HouseFactory { House build() { Kitchen kitchen = new Kitchen(new Refrigerator()); Bedroom bedroom = new Bedroom(); return new House (kitchen, bedroom); } } 


しかし、再び反察する人もいたす。「では、クラスごずにファクトリを䜜成し、コヌド量を2倍に増やしたすか これはナンセンスです。」 実際、1぀のファクトリヌが同じラむフタむムを持぀クラスを䜜成しお関連付けたす。 たた、実際のアプリケヌションでは、存続期間の異なる倧量のコヌドはありたせん。 たずえば、さたざたなラむフタむムで分類されたオブゞェクトのタむプがWebアプリケヌションに存圚するこずを考えおみたしょう。

䞀般的なWebアプリケヌションには4぀のファクトリヌが十分にあるこずがわかりたす。 したがっお、それらを恐れおはいけたせんそしお結論ずしお、工堎がたったく曞けない方法が瀺されたす。
したがっお、2぀のグルヌプのコヌドを区別できたす。

ビゞネスコヌド-ビゞネスロゞックを反映するコヌド。顧客の芁件が倉化するず倉曎されるコヌド。 これはコヌドの䞻芁なカテゎリであり、これがコヌドを蚘述しおいる理由です。

バむンディングコヌド-ビゞネスコヌドが盞互に䜜甚し、䞀緒にバむンドできるようにするコヌド。 ボンディングコヌドにはビゞネスロゞックは含たれたせん。 これには、工堎、アプリケヌションの起動ポむント、およびその他のタむポむントが含たれたす。 リンクコヌドには倚くの新しい挔算子がありたす。

コヌドを2぀のグルヌプに分割するこのアプロヌチにより、アプリケヌションロゞックずアプリケヌションのオブゞェクトグラフの䜜成を混圚させるこずを回避できたす。 したがっお、垞にnew挔算子を䜿甚するずきは、コヌドのグルヌプで䜿甚するかどうかを怜蚎しおください。

䟋倖

new挔算子をビゞネスコヌドで䜿甚しお、動䜜を含たないストレヌゞオブゞェクトHashMap、Arrayなどを䜜成できたす。

次の䟋の別の䞀般的なケヌスを怜蚎しおください。

前テストされおいないコヌド
 class DocumentActions { Network network; DocumentModel documentModel; DocumentActions(Network network, DocumentModel documentModel) { //    this.network = network; this.documentModel = documentModel; } changeTextStyle(int textOffset, TextStyle style) { Revision revision = new Revision(this.network, this.documentModel, textOffset); revision.updateTextStyle(style); revision.apply(); } insertParagraph(int textOffset, ParagraphProps props, ParagraphStyle style) { Revision revision = new Revision(this.network, this.documentModel, textOffset); revision.addParagraph(props); revision.updateParagraphStyle(style); revision.apply(); } //      new Revision } //  //        Network network = new Network(...); DocumentModel documentModel = new DocumentModel(...); DocumentActions docActions = new DocumentActions(network, documentModel); //      docActions 


各メ゜ッドのDocumentActionsクラスはRevisionクラスを䜜成し、このクラスの実装をmokeeに眮き換える機胜のテストを奪いたす。 さらに悪いこずに、DocumentActionsでは、䜿甚しないコンストラクタヌのクラスが必芁です。 しかし、Revisionを䜜成するために、事前に知られおいない3番目の匕数textOffsetを毎回䞎える必芁がある堎合はどうでしょうか 終了リビゞョンを䜜成する方法の知識を匕き継ぐクラスを䜜成したす。 そしお、これは工堎に過ぎたせん

埌テストコヌド
 class DocumentActions { RevisionFactory revisionFactory; DocumentActions(RevisionFactory revisionFactory) { this.revisionFactory = revisionFactory; } changeTextStyle(int textOffset, TextStyle style) { Revision revision = this.revisionFactory.build(textOffset); revision.updateTextStyle(style); revision.apply(); } insertParagraph(int textOffset, ParagraphProps props, ParagraphStyle style) { Revision revision = this.revisionFactory.build(textOffset); revision.addParagraph(props); revision.updateParagraphStyle(style); revision.apply(); } //      this.revisionFactory.build } class RevisionFactory { Network network; DocumentModel documentModel; RevisionFactory(Network network, DocumentModel documentModel) { this.network = network; this.documentModel = documentModel; } Revision build(int textOffset) { return new Revision(this.network, this.documentModel, textOffset); } } //  class MyMockRevision extends Revision { //    } class MyMockRevisionFactory extends RevisionFactory { public Revision revision; Revision build(int textOffset) { this.revision = new MyMockRevision(this.network, this.documentModel, textOffset); return this.revision; } } RevisionFactory revisionFactory = new MyMockRevisionFactory(null, null); DocumentActions docActions = new DocumentActions(revisionFactory); //   //    Revision  revisionFactory.revision 


工堎は、リビゞョンを䜜成するためにNetworkずDocumentModelが絶察に必芁であるこずを知っおいたす。 したがっお、圌女自身がこれらのクラスを自分で必芁ずしたす。 たた、Revisionを䜜成するためのすべおの動的パラメヌタヌtextOffsetは、ファクトリビルドメ゜ッドの匕数ずしお必芁になりたす。

将来、Revisionクラスがコンストラクタヌに別の定数匕数を必芁ずする堎合、RevisionFactoryをわずかに修正するこずは難しくなく、DocumentActionsクラスはたったく倉曎されたせん。

グロヌバル倉数ずシングルトヌン


グロヌバル倉数は悪であるこずに誰もが同意するず思いたす。 しかし、本質的にはすべおが同じグロヌバル倉数であるにもかかわらず、倚くの人はシングルトンに問題はありたせん。 では、なぜシングルトンが悪いのでしょうか 以䞋に2぀の理由を瀺したすただし、いずれでも十分です。

1シングルトヌンを䜿甚する堎合の問題は、コヌドに緊密な接続性が導入されるこずです。 シングルトンを䜜成するずき、クラスはシングルトンが提䟛する単䞀の実装のみで機胜するず蚀いたす。 亀換する機䌚はありたせん。 テストの性質䞊、実装を別の実装に眮き換える機胜が必芁なため、クラスをシングルトンから分離しおテストするこずは困難です。 この蚭蚈を倉曎するたで、クラむアントをテストするために適切に動䜜するためにシングルトンに頌らなければなりたせん。

2シングルトンが存圚するず、「芋えない」䟝存関係が䜜成されるため、クラスの䟝存関係が嘘になりたす。 クラスの実際の䟝存関係を理解するには、コンストラクタヌ/メ゜ッドの䟝存関係のリストを芋るだけでなく、そのコヌドを完党に読む必芁がありたす。 そしお遅かれ早かれ、これはテストが隠されたグロヌバルな状態を介しお互いに圱響を及がし始めるずいう事実に぀ながりたす。

䞡方の問題をすぐに説明する実践の䟋Webアプリケヌションでは、ナヌザヌアクションをログに蚘録する必芁が生じたした。 このために、倚くのクラスで䜿甚されるシングルトンが䜜成されたした。 シングルトンがjQueryを䜿甚しおDOMから詳现情報を取埗したこずに蚀及したす。 アプリケヌションのノヌドバヌゞョンでクラスの䞀郚を再利甚する必芁があるたで、すべおがうたくいきたした。 このバヌゞョンは、テスト䞭に定期的に萜ち始めたした。 シングルトンロガヌを䜿甚するクラスがこのバヌゞョンになり、ノヌドにDOMがないこずが刀明したした。 これらのクラスは「芋えない」䟝存関係理由2を䜿甚しおいたした。その結果、この状況が発生したした。 ロガヌを別のロガヌに眮き換える機䌚はありたせんでした理由1。そのため、䞀郚のクラスをやり盎す必芁がありたした。

「意図的にシングルトンを䜿甚しお、アプリケヌション党䜓のクラスのむンスタンスを1぀だけにする」ず蚀う人もいたす。 ただし、シングルトンを䜜成する堎合、アプリケヌション党䜓ではなく、コヌド実行スペヌス党䜓Javaのjvm、rhinoたたはV8のjavascriptに固有のクラスむンスタンスを䜜成したす。 ほずんどの堎合、コヌド実行スペヌス内にアプリケヌションは1぀しかなく、これらのスペヌスは䞀臎しおいるこずがわかりたす。 しかし、これはテストの堎合ではありたせん。 各テストは、他のテストず同じ実行スペヌスで実行されるアプリケヌションの䞀郚です。

ミニアプリケヌションの倚くは、単䞀のかけがえのないシングルトヌンが存圚する1぀の実行スペヌスに䜏み始めたす。 これは、テストが盞互に圱響を及がし始めたずきにシングルトンの副䜜甚が珟れる堎所です。

さらに、おそらくい぀かは、1぀のコヌド実行スペヌスにアプリケヌションたたはその䞀郚の耇数のコピヌが必芁になるでしょう。 その堎合、アプリケヌションスペヌスはコヌド実行スペヌスず等しくなりたせん。 そしお、シングルトンを取り陀く以倖に䜕もするこずはありたせん。

この䟋を考えおみたしょう。PhoneAccountクラスをテストするように指瀺されたす。PhoneAccountクラスは、電話アカりントの操䜜を担圓したす。 考え盎すこずなく、次のように曞きたす。

詊行1
 PhoneAccount phoneAccount = new PhoneAccount('79008001020'); phoneAccount.addMoney(100); expect(phoneAccount.getBalance()).toBe(100); 


テストを単独で実行し、実行時にnullアクセス゚ラヌを取埗したす。 䜕が悪かったのですか このクラスを曞いた同僚に尋ねたす。 圌は長い間考え、シングルトンクラスPhoneAccountTransactionProcessorを初期化する必芁があるこずを思い出したす。 あなたは思う「その前にどのように掚枬すべきだったのか」 次に、远加したす

詊行2
 PhoneAccountTransactionProcessor.init(...); PhoneAccount phoneAccount = new PhoneAccount('79008001020'); phoneAccount.addMoney(100); expect(phoneAccount.getBalance()).toBe(100); 


ただし、起動時に、nullアクセス゚ラヌが発生したす。 困惑しお、あなたは再び同僚に尋ねたす「私は䜕を間違っおいるのですか」 「トランザクションキュヌ-AccountTransactionQueueを初期化したしたか」ずいう答えを受け取りたす。 少しむラむラしおコヌドを远加したしたが、これで問題は終わりではなく、「経隓のある」同僚から離れないように頌むこずができたす。

詊行3
 AccountTransactionQueue.start(...); PhoneAccountTransactionProcessor.init(...); PhoneAccount phoneAccount = new PhoneAccount('79008001020'); phoneAccount.addMoney(100); expect(phoneAccount.getBalance()).toBe(100); 


そしおたた間違い。 しかし、質問する時間がある前に、同僚が「トランザクションデヌタベヌスに接続しなかった」 さらに、深呌吞をしおください

詊行4
 TransactionsDataBase.connect(...) AccountTransactionQueue.start(...); PhoneAccountTransactionProcessor.init(...); PhoneAccount phoneAccount = new PhoneAccount('79008001020'); phoneAccount.addMoney(100); expect(phoneAccount.getBalance()).toBe(100); 


最埌に、テストに合栌したす。

あなたはすでにこのコヌドの䜕が問題なのか、ある皮の黒魔術を理解しおいるず思いたす。 3぀のクラスは䟝存関係にありたすが、初期化の順序はたさにそれである必芁がありたすが、コヌドはその順序を正確に指瀺したせん。

しかし、シングルトヌンがなく、各クラスがコンストラクタヌで動䜜するために必芁なすべおを必芁ずする堎合はどうでしょうか

正しいコヌド
 TransactionsDataBase db = new TransactionsDataBase(...); AccountTransactionQueue transactionQueue; transactionQueue = new AccountTransactionQueue(db); PhoneAccountTransactionProcessor transactionProcessor; transactionProcessor = PhoneAccountTransactionProcessor(transactionQueue); PhoneAccount phoneAccount = new PhoneAccount('79008001020', transactionProcessor); phoneAccount.addMoney(100); expect(phoneAccount.getBalance()).toBe(100); 


今では、魔法もクラス間のコミュニケヌションの隠れたチャンネルもありたせん。 チヌムの新しいメンバヌは、すべおの䟝存関係をすぐに確認できるため、このようなテストを独自に䜜成できたす。 コヌド自䜓が初期化の順序を指瀺するため、別の方法で実行するこずはできたせん。 実際のアプリケヌションでは、クラスの初期化で䜕十行もある可胜性があるため、これは倧きなプラスです。 そしおもう1぀の重芁な結論このようなコヌドでは、クラスの代わりにnullを枡す可胜な堎合か、クラスをmochに眮き換えるこずができたす。 シングルトンコヌドでは、これは䞍可胜でした。

別の䟋

前テストされおいないコヌド
 class LoginService { private static LoginService instance; private LoginService() {}; static LoginService getInstance() { if (instance == null) { instance = new RealLoginService(); } return instance; } //     //     ,   ! static setForTest(LoginService testDouble) { instance = testDouble; } //    //     ,   ! static resetForTest() { instance = null; } // ... } //    class AdminDashboard { boolean isAuthenticatedAdminUser(User user) { LoginService loginService = LoginService.getInstance(); return loginService.isAuthenticatedAdmin(user); } } //  AdminDashboard adminDashboard = new AdminDashboard() assertTrue(adminDashboard.isAuthenticatedAdminUser(user)); //    LoginService,    //   


シングルトンを修正したす。

埌テストコヌド
 class LoginService { // ... } //    class AdminDashboard { AdminDashboard(LoginService loginService) { this.loginService = loginService; } boolean isAuthenticatedAdminUser(User user) { return this.loginService.isAuthenticatedAdmin(user); } } //  AdminDashboard adminDashboard = new AdminDashboard(new MockLoginService()); assertTrue(adminDashboard.isAuthenticatedAdminUser(user)); 


LoginServiceを簡単か぀簡単に眮き換えるこずができ、「テスト専甚」のメ゜ッドは必芁ありたせん。

シングルトンを匕き続き䜿甚できる堎合の䟋倖


たた、䞀郚のシステムオブゞェクトには、 math.randomやnew Dateなどの非衚瀺のグロヌバル倉数がありたす。 そのようなオブゞェクトを䜿甚するクラスをテストする堎合は、それらを眮き換えるために独自のラッパヌを䜜成する必芁がありたす。

ベテランのコンストラクタヌ


ベテランコンストラクタヌは、クラスのフィヌルドを初期化する以倖のこずを行うコンストラクタヌです。 厳密に蚀えば、デザむナヌはクラスのフィヌルドを初期化するこずのみに責任があり、その他の䜜業は単䞀責任原則に違反したす。 コンストラクタヌでのこのような「䜙分な」䜜業は、テストがテストするクラスに䟝存性スタブを枡すこずができないため、テストを困難にしたす。 熟緎したデザむナヌの兞型的なパタヌンは次のずおりです。


最埌の3぀のパタヌンは、蚭蚈者だけでなく、䞊蚘の䞀般的なケヌスで考慮された特城的なものです。

コンストラクタヌ匕数の初期化


前テストされおいないコヌド
 class Metro { TicketPrices ticketPrices; Metro(TicketPrices ticketPrices) { this.ticketPrices = ticketPrices; ticketPrices.setCostCalculator(new MoscowCostCalculatorWithVerySlowConstructor()); } } //    TicketPrices ticketPrices = new TicketPrices(); Metro metro = new Metro(ticketPrices); expect(metro.isWork()).toBe(true); 


Metroクラスのコンストラクタヌはその矩務を果たしたせん-匕数を初期化したす。 このコヌドは倚くの理由で悪いですが、テスト可胜性に興味がありたす。 初期化が遅い堎合、Metroでのすべおのテストに時間がかかりたす。

埌テストコヌド
 class Metro { TicketPrices ticketPrices; Metro(TicketPrices ticketPrices) { this.ticketPrices = ticketPrices; } } class TicketPricesFactory { TicketPrices build() { TicketPrices ticketPrices = new TicketPrices(); ticketPrices.setCostCalculator(new VerySlowMoscowCostCalculator()); return ticketPrices; } } // test TicketPrices ticketPrices = new TicketPrices(); ticketPrices.setCostCalculator(null); Metro metro = new Metro(ticketPrices); expect(metro.isWork()).toBe(true); 


これで、テストでは、テストに必芁のないクラスを䜜成できなくなり、ダミヌに眮き換えられたす。 TicketPricesの䜜成ず初期化のロゞックは、ファクトリヌに移行したした。

条件ずサむクル


前テストされおいないコヌド
 class Car { IEngine engine; Car() { if (FLAG_ENGINE.get()){ this.engine = new V8Engine(); } else { this.engine = new V12Engine(); } } } // test // ,   FLAG_ENGINE    Car car = new Car(); //        


テストは特定のフラグに関連付けられおいたす。 先隓的に、2぀の゚ンゞンオプションのみをテストできたす。

:
 class Car { IEngine engine; Car(IEngine engine) { this.engine = engine; } } class EngineFactory { IEngine build(boolean isV8) { if (isV8){ return new V8Engine(); } else { return new V12Engine(); } } } //  ar   Car car = new Car(new EngineFactory().build(FLAG_ENGINE.get())); // test IEngine simpleEngine = new SimpleEngine(); Car car = new Car(simpleEngine); //   , simpleEngine    //     




前テストされおいないコヌド
 class Voicemail { User user; private List<Call> calls; Voicemail(User user) { this.user = user; } init(Server server) { this.calls = server.getCallsFor(this.user); } //   ,  !!! setCalls(List<Call> calls) { this.calls = calls; } // ... } //  User dummyUser = new DummyUser(); Voicemail voicemail = new Voicemail(dummyUser); voicemail.setCalls(buildListOfTestCalls()); 


init, , . init , calls init. , , . . — . . init. , init, initialize, setup , . Voicemail (server.getCallsFor). Voicemail:

:
 class Voicemail { List<Call> calls; Voicemail(List<Call> calls) { this.calls = calls; } // ... } class ProviderGetCalls { List<Call> getCalls(Server server, User user) { return server.getCallsFor(user); } } //  Voicemail voicemail = new Voicemail(buildListOfTestCalls()); //      



おわりに


, ( new , ), , Dependency Injection .

IoC , — (), . IoC . IoC — .

, IoC ( ), , Dependency Injection. , .

, . ( ), ( «» angular Miško Hevery ), .

Source: https://habr.com/ru/post/J267277/


All Articles