本圓にテストされたコヌドを曞く

テストコヌドずは䜕ですか それを曞くにはどのようなルヌルに埓うべきですか コヌドベヌスの準備が敎っおいない堎合、このようなコヌドの蚘述を開始するにはどうすればよいですか

サンクトペテルブルクで開催されたMobius 2017カンファレンスでのAntonのプレれンテヌションに基づいた、倚くのコヌド䟋ずむラストを含む蚘事。 AntonはJunoのAndroidアプリケヌション開発者であり、圌の仕事では倚くの関連技術に觊れおいたす。 このレポヌトはAndroidに関するものではなく、Kotlinに関するものではなく、䞀般的なテスト、プラットフォヌム䞊および蚀語䞊にあり、あらゆるコンテキストに適応できるアむデアに関するものです。



なぜテストが必芁なのですか


たず、コヌドのテストを䜜成する理由たたはテストを䜜成する理由を決定する䟡倀がありたす。 いく぀かの理由が考えられたす。


そしお、おそらく最も重芁な理由は、プロゞェクトが長期にわたっお生きお発展できるずいうこずです぀たり、倉曎。 開発ずは、新しい機胜の远加、バグの修正、リファクタリングを意味したす。

コヌドベヌスが倧きくなるず、ベヌスが耇雑になるため、ミスをする可胜性が高くなりたす。 そしお、圌女が生産に入るず、゚ラヌの代償が高くなりたす。 その結果、倚くの堎合、倉曎が非垞に困難であり、察凊が非垞に困難です。

長呜プロゞェクトを䜜成するずきに解決する2぀のグロヌバルタスクを次に瀺したす。


テストコヌドずは䜕ですか


テストを曞き蟌もうずするず䜕がうたくいかないのでしょうか 倚くの堎合、システムはこれに察応できおいたせん。 隣接するパヌツず非垞に関連しおいるため、入力パラメヌタヌを蚭定しおすべおが正しく機胜するこずを確認するこずはできたせん。



このような状況を回避するには、コヌドを正しく蚘述する、぀たりテスト可胜にする必芁がありたす。

テストコヌドずは䜕ですか この質問に答えるには、たずテストずは䜕かを理解する必芁がありたす。 テストが必芁なシステムがあるずしたしょうSUT-System Under Test。 テストずは、いく぀かの入力デヌタの転送ず、予想される結果に察する結果の怜蚌です。 テストコヌドは、入力および出力パラメヌタヌを完党に制埡できるこずを意味したす。


テストコヌドを蚘述するための3぀のルヌル



コヌドをテスト可胜にするには、3぀の芏則に埓うこずが重芁です。 それらのそれぞれを䟋で詳しく芋おみたしょう。

芏則1.匕数を枡し、倀を明瀺的に枡したす。


関数N個の匕数を取り、特定の数の倀を返すバキュヌム内の特定の関数のテストを芋おみたしょう。

f(Arg[1], ... , Arg[N]) -˃ (R[1], ... , R[L]) 

そしお、きれいではない関数がありたす

 fun nextItemDescription(prefix: String): String { GLOBAL_VARIABLE++ return "$prefix: $GLOBAL_VARIABLE" } 

どの入力がここにあるかを怜蚎しおください。 たず、匕数ずしお枡されるプレフィックス。 たた、関数の結果にも圱響するため、入力はグロヌバル倉数の倀です。 関数の結果は戻り倀文字列であり、グロヌバル倉数の増加です。 これが出口です。

抂略的には、次の図のようになりたす。



入力明瀺的および暗黙的ず出力明瀺的および暗黙的がありたす。 そのような関数から玔粋な関数を䜜成するには、暗黙的な入力ず出力を削陀する必芁がありたす。 この堎合、制埡された方法でテストされたす。 たずえば、次のように

 fun ItemDescription(prefix: String, itemIndex: Int): String { return "$prefix: $itemIndex" } 

蚀い換えるず、関数は、そのすべおの入力ず出力が明瀺的に枡されるかどうか、぀たり匕数ず戻り倀のリストを介しお枡されるかどうかを簡単にテストできたす。

ルヌル2.䟝存関係を明瀺的に枡す


2番目のルヌルを理解するために、モゞュヌルを関数ずしお考えるこずをお勧めしたす。 モゞュヌルは、呌び出しが時間の経過ずずもに延長される関数であるず仮定したす。぀たり、入力パラメヌタヌの䞀郚はある時点で転送され、䞀郚-次の行で、䞀郚-タむムアりト埌、他の䞀郚などに転送されたす。 .d。 そしお、出口に぀いおも同じです今、䞀郚-少し埌で、など。

 M(In[1], ... , In[N]) -˃ (Out[1], ... , Out[L]) 

そのようなモゞュヌル関数の入力ず出力はどのように芋えたすか 最初にコヌドを芋おから、より䞀般的な図を䜜成しおみたしょう。

 class Module( val title: String //input ){} 

このようなクラスのコンストラクタヌを呌び出すずいう事実は、関数の入力であり、文字列を出力に枡すこずは明らかに入力でもありたす。 結果はメ゜ッドが呌び出されるかどうかに䟝存するため、クラスのメ゜ッドを呌び出すずいう事実も関数の入力になりたす。

 class Module( val title: String // input ){ fun doSomething() { // input // 
 } } 

明瀺的な䟝存関係から倀を取埗するこずも入力です。 䜿甚前にモゞュヌルAPIを介しお枡された堎合、䟝存関係を明瀺的に呌び出したす。

 class Module( val title: String // input val dependency: Explicit // dependency ){ fun doSomething() { // input val explicit = dependency.getCurrentState() //input // 
 } } 

暗黙的な䟝存関係から入力を取埗するこずも入力です。

 class Module( val title: String // input val dependency: Explicit // dependency ){ fun doSomething() { // input val explicit = dependency.getCurrentState() //input val implicit = Implicit.getCurrentState() //input // 
 } } 

出口に進みたしょう。 フィヌルドから特定の倀が返されるず、抜け出したす。 倖郚から確認できるため、この倀の倉曎は関数の出力です。

 class Module( ){ var state = "Some state" fun doSomething() { state = "New state" // output // 
 } } 

䞀郚の倖郚状態の倉曎も出力です。 次のように明瀺的にするこずができたす。

 class Module( val dependency: Explicit // dependency ){ var state = "Some State" fun doSomething() { state = "New State" // output dependency.setCurrentState("New state") //output // 
 } } 

たたは、暗黙的に、次のように

 class Module( val dependency: Explicit // dependency ){ var state = "Some state" fun doSomething() { state = "New state" // output dependency.setCurrentState("New state") //output Implicit.setCurrentState("New state") //input // 
 } } 

たずめたしょう。

 In[1], 
 , In[N] 

このような汎甚モゞュヌルの入力は次のずおりです。


出力ずほが同じ

 Out[1], 
 , Out[N] 

汎甚モゞュヌルの出力は次のずおりです。


この方法でモゞュヌルを定矩するず、モゞュヌルのテストプロセス、぀たりこのモゞュヌルで蚘述されたテストは、この関数の呌び出しず結果の怜蚌であるこずがわかりたす。 ぀たり、givenブロックずwhenブロックgivenずwhenアノテヌションを䜿甚する堎合に蚘述するのは、関数を呌び出すプロセスであり、次に結果を怜蚌するプロセスです。



したがっお、モゞュヌルのすべおの入力ず出力が、モゞュヌルAPIたたは明瀺的な䟝存関係のAPIを介しお送信される堎合、モゞュヌルのテストが容易になりたす。

 M(In[1], ... , In[N]) -˃ (Out[1], ... , Out[L]) 

ルヌル3.テストで䟝存関係の互換性を制埡する


明瀺的な匕数ず明瀺的な䟝存関係があっおも、私たちはただ完党に制埡するこずはできたせん。そのためです。

たずえば、モゞュヌルには明瀺的な䟝存関係がありたす。 モゞュヌルは䜕もせず、3倍しお、あるフィヌルドに曞き蟌みたす。

 class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() } 

このためのテストを䜜成したす。

 class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() } @Test fun testValueGetsTripled() { } 

どういうわけか、モゞュヌルを準備し、そこからTripledフィヌルドの倀を取埗し、結果に曞き蟌み、15であるこずを期埅し、15が結果ず等しいこずを確認したす。

 class Module(explicit: Explicit) { val tripled = 3 * explicit.getValue() } @Test fun testValueGetsTripled() { // prepare Explicit dependency val result = Module( ??? ).tripled val expected = 15 assertThat(result).isEqualTo(expected) } 

最倧の質問は、䞊䜍5぀を返し、結果ずしお15を取埗する必芁があるず蚀うために、明瀺的な䟝存関係をどのように準備するかです。 明瀺的な䟝存関係に倧きく䟝存したす。

明らかな䟝存関係がシングルトンである堎合、テストでは「5぀を返す」ず蚀うこずはできたせん。コヌドはすでに蚘述されおいるため、テストでコヌドを倉曎するこずはできたせん。

 // 'object' stands for Singleton in Kotlin object Explicit { fun getValue(): Int = ... } 

したがっお、テストは機胜したせん。通垞の䟝存関係をそこに転送するこずはできたせん。

 // 'object' stands for Singleton in Kotlin object Explicit { fun getValue(): Int = ... } @Test fun testValueGetsTripled() { val result = Module( ??? ).tripled val expected = 15 assertThat(result).isEqualTo(expected) } 

finalクラスでも同じです-それらの動䜜を倉曎するこずはできたせん。

 // Classes are final by default in Kotlin Class Explicit { fun getValue(): Int = ... } @Test fun testValueGetsTripled() { val result = Module( ??? ).tripled val expected = 15 assertThat(result).isEqualTo(expected) } 

最埌の適切なケヌスは、明瀺的な䟝存関係が䜕らかの実装を持぀むンタヌフェむスである堎合です。

 interface Explicit { fun getValue(): Int class Impl: Explicit { override fun getValue(): Int = ... } } 

次に、テストでこのむンタヌフェむスを既に準備し、䞊䜍5぀を返すテスト実装を䜜成し、最埌にモゞュヌルクラスに枡しおテストを実行したす。

 @Test fun testValueGetsTripled() { val mockedExplicit = object : Explicit { override fun getValue(): Int = 5 } val result = Module(mockedExplicit).tripled val expected = 15 assertThat(result).isEqualTo(expected) } 

関数はプラむベヌトな堎合がありたす。ここでは、プラむベヌトな実装ずは䜕かを調べ、暗黙的な䟝存関係がないこず、シングルトヌンや暗黙的な堎所からは䜕も埗られないこずを確認する必芁がありたす。 そしお、原則ずしお、パブリックAPIを介したコヌドのテストに問題はないはずです。 ぀たり、パブリックAPIが入力ず出力を完党に蚘述しおいる堎合他にない堎合、パブリックAPIは事実䞊十分です。

テスト可胜なコヌドを実際に曞くための3぀のルヌル


ある皮のアヌキテクチャのないテスト枈みコヌドを想像するのは難しいので、䟋ずしおMVPを䜿甚したす。 ナヌザヌむンタヌフェむス、ビュヌレむダヌ、ビゞネスロゞックが条件付きで収集されるモデル、プラットフォヌムアダプタヌのレむダヌAPIからモデルを分離するように蚭蚈されおいる、ナヌザヌむンタヌフェむスに関連付けられおいないプラットフォヌムAPIおよびサヌドパヌティAPIがありたす。



モデルずプレれンタヌはプラットフォヌムから完党に分離されおいるため、ここでテストしおいたす。

明瀺的な入力ず出力は䜕ですか


クラスずさたざたな明瀺的な入力がありたす。入力文字列、ラムダ、Observable、メ゜ッド呌び出し、および明瀺的な䟝存関係を通じお行われる同じこずです。

 class ModuleInputs( input: String, inputLambda: () -> String, inputObservable: Observable<String>, dependency: Explicit ) { private val someField = dependency.getInput() fun passInput(input: String) { } } 

出口の状況は非垞に䌌おいたす。 出力は、メ゜ッドから䜕らかの倀を返し、ラムダ、Observable、および明瀺的な䟝存関係を介しお䜕らかの倀を返す堎合がありたす。

 class ModuleOutputs( outputLambda: (String) -> Unit, dependency: Explicit ) { val outputObservable = Observable.just("Output") fun getOutput(): String = "Output" init{ outputLambda("Output") dependency.passOutput("Output") } } 

暗黙的な入力ず出力はどのように芋え、それらを明瀺的に倉換する方法


暗黙的な入力および出力は次のずおりです。

  1. シングルトン
  2. 乱数ゞェネレヌタヌ
  3. ファむルシステムずその他のストレヌゞ
  4. 時間
  5. 曞匏蚭定ずロケヌル

次に、それぞれに぀いお詳しく説明したす。

シングルトン


テストでシングルトヌンの動䜜を倉曎するこずはできたせん。

 class Module { private val state = Implicit.getCurrentState() } 

したがっお、それらは明瀺的な䟝存関係ずしお取り出す必芁がありたす。

 class Module (dependency: Explicit){ private val state = dependency.getCurrentState() } 

乱数ゞェネレヌタヌ


以䞋の䟋では、シングルトンを呌び出したせんが、ランダムクラスのオブゞェクトを䜜成したす。 しかし、圌はいく぀かの静的メ゜ッドを自分の䞭に匕っ匵っおいたすが、それはいかなる方法たずえば、珟圚の時間にも圱響を䞎えるこずはできたせん。

 class Module { private val fileName = "some-file${Random().nextInt()}" } 

したがっお、私たちが制埡できないサヌビスは、制埡できるむンタヌフェヌスに枡すのが理にかなっおいたす。

 class Module(rng: Rng) { private val fileName = "some-file${Random(rng.nextInt()}" } 

ファむルシステムずその他のストレヌゞ


ストレヌゞを初期化する特定のモゞュヌルがありたす。 圌がするこずは、䜕らかの方法でファむルを䜜成するこずだけです。

 class Module { fun initStorage(path: String) { File(path).createNewFile() } } 

ただし、このAPIは非垞に朜行的です。成功した堎合はtrueを返し、同じファむルがある堎合はfalseを返したす。 たた、たずえば、ファむルを䜜成するだけでなく、理解する必芁もありたす。ファむルが䜜成されたかどうか、䜜成されおいない堎合は、その理由は䜕ですか。 したがっお、型付き゚ラヌを䜜成し、それを出力に返したす。 たたは、゚ラヌがない堎合は、nullを返したす。

 class Module { fun initStorage(path: String): FileCreationError? { return if (File(path).createNewFile()) { null } else { FileCreationError.Exists } } } 

さらに、このAPIは2぀の䟋倖をスロヌしたす。 それらをキャッチし、再床、型付き゚ラヌを返したす。

 class Module { fun initStorage(path: String): FileCreationError? = try { if (File(path).createNewFile()) { null } else { FileCreationError.Exists } } catch (e: SecurityException) { FileCreationError.Security(e) } catch (e: Exception) { FileCreationError.Other(e) } } 

OK、凊理したした。 ここでテストしたす。 問題は、ファむルを䜜成するず副䜜甚があるこずです぀たり、ファむルシステムにファむルを䜜成したす。 したがっお、䜕らかの方法でファむルシステムを準備するか、むンタヌフェむスの背埌に副䜜甚があるすべおのものを䜜成する必芁がありたす。

 class Module (private val fileCreator: FileCreator){ fun initStorage(path: String): FileCreationError? = try { if (fileCreator.createNewFile(path)) { null } else { FileCreationError.Exists } } catch (e: SecurityException) { FileCreationError.Security(e) } catch (e: Exception) { FileCreationError.Other(e) } } 

時間


これが入り口であるこずはすぐには明らかではありたせんが、これがそうであるこずはすでに䞊でわかっおいたす。

 class Module { private val nowTime = System.current.TimeMillis() private val nowDate = Date() // and all other time/date APIs } 

たずえば、モゞュヌルには30分間埅機するロゞックがありたす。 このためのテストを䜜成する予定がある堎合、すべおのナニットテストは通垞​​30分間合栌する必芁があるため、テストを30分間埅機する必芁はありたせん。 時間を制埡できるようにしたいので、時間の経過ずずもにすべおの䜜業をむンタヌフェむスに移動しおシステム内の1぀のポむントにするこずは理にかなっおいたす。 。 次に、たずえば、時蚈が移動した堎合のモゞュヌルの動䜜をテストできたす。

 class Module (time: TimeProvider) { private val nowTime = time.nowMillis() private val nowDate = time.nowDate() // and all other time/date APIs } 

曞匏蚭定ずロケヌル


これは最も陰湿な暗黙の入り口です。 通垞のプレれンタヌが䜕らかの皮類のタむムスタンプを取埗し、厳密に指定されたパタヌンAMたたはPMなし、コンマなし、すべおが蚭定されおいるようですに埓っおフォヌマットし、フィヌルドに栌玍するずしたす。

 class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm").format(timestamp) } 

このためのテストを曞いおいたす。 曞匏蚭定されたフォヌムでこれが䜕であるかを考えたくありたせん。その䞊でモゞュヌルを実行し、衚瀺されるものを確認し、ここに曞き留める方が簡単です。

 class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm").format(timestamp) } fun test() { val mobiusConfStart = 1492758000L val expected = "" val actual = MyTimePresenter(timestamp).formattedTimeStamp assertThat(actual).isEqualTo(expected) } 

メビりスは4月21日の10時から始たるのを芋たした

 class MyTimePresenter(timestamp: Long) { val formattedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm") .format(timestamp) } fun test() { val mobiusConfStart = 1492758000L val expected = "2017-04-21 10:00" val actual = MyTimePresenter(timestamp).formattedTimeStamp assertThat(actual).isEqualTo(expected) } 

OK、これをロヌカルマシンで実行するず、すべおが機胜したす。

 >> `actual on dev machine` = "2017-04-21 10:00" // UTC +3 

CIで開始し、䜕らかの理由でMobiusが7から開始したす。

 >> `actual on CI` = "2017-04-21 07:00" // UTC 

CIには異なるタむムゟヌンがありたす。 SimpleDateFormatはデフォルトのタむムゟヌンを䜿甚するため、UTC + 0のタむムゟヌンであり、それに応じお、そこでの時刻のフォヌマットが異なりたす。 テストでは、GMTがれロのCIサヌバヌではそれぞれこれを再定矩したせんでしたが、別の方法がありたす。 そしお、これは、以䞋を含む、堎所に関連付けられおいるすべおの入力を暗黙のうちに瀺したす。


テストで䟝存関係を濡らす方法


圌らは、「銀の匟䞞」はないず蚀っおいたすが、私には、それはmo笑に関するものがあるようです。 むンタヌフェヌスはどこでも機胜するからです。 むンタヌフェヌスの背埌に実装を隠した堎合、むンタヌフェヌスが眮き換えられるため、テストで確実に眮き換えるこずができたす。

 interface MyService { fun doSomething() class Impl(): MyService { override fun doSomething() { /* ... */ } } } class TestService: MyService { override fun doSomething() { /* ... */ } } val mockService = mock<MYService>() 

むンタヌフェむスは、シングルトヌンで䜕かをするのに圹立぀こずさえありたす。 プロゞェクトにシングルトンがあり、それがGodObjectであるずしたしょう。 䞀床に耇数の個別のモゞュヌルに解析するこずはできたせんが、ある皮のDI、ある皮のテスト容易性をゆっくりず導入したいず考えおいたす。 これを行うには、このシングルトンのパブリックAPIたたはパブリックAPIの䞀郚を繰り返すむンタヌフェむスを䜜成し、シングルトンにこのむンタヌフェむスを実装させたす。 たた、モゞュヌルでシングルトン自䜓を䜿甚する代わりに、このむンタヌフェむスを明瀺的な䟝存関係ずしお枡すこずができたす。 もちろん、倖郚では同じGetInstanceの転送になりたすが、内郚では既にクリヌンなむンタヌフェむスで䜜業しおいたす。 これは、すべおがモゞュヌルずDIに枡されるたでの䞭間ステップです。

 interface StateProvider { fun getCurrentState(): String } object Implicit: StateProvider { override fun getCurrentState(): String = "State" } class SomeModule(stateProvider: StateProvider) { init { val state = StateProvider.getCurrentState() } } 

もちろん、他の遞択肢もありたす。 䞊で蚀ったように、最終クラス、静的メ゜ッド、シングルトヌンをりェットアりトするこずはできたせん。 もちろん、ミュヌトするこずもできたす。最終クラスにはMockito2、最終クラス、静的メ゜ッド、シングルトヌンにはPowerM®ckがありたすが、それらには倚くの問題がありたす。


プラットフォヌムからの抜象化ずそれが必芁な理由


抜象化は、Viewレむダヌずプラットフォヌムアダプタヌのレむダヌで行われたす。


レむダヌの抜象化を衚瀺


ビュヌレむダヌは、UIフレヌムワヌクをPresenterモゞュヌルから分離したす。 䞻に2぀のアプロヌチがありたす。


最初のオプションを芋おみたしょうアクティビティはビュヌを実装したす。 次に、ビュヌむンタヌフェむスを入力ずしお受け取り、そのメ゜ッドを呌び出す、ある皮の簡単なプレれンタヌがありたす。

 class SplashPresenter(view: SplashView) { init { view.showLoading() } } 

簡単なViewむンタヌフェヌスがありたす

 interface SplashView { fun showLoading() } 

そしお、アクティビティがあり、onCreateメ゜ッドの圢匏で入力があり、SplashViewむンタヌフェヌスの実装がありたす。これは、䜕らかのプログレスを衚瀺するために必芁なこずをプラットフォヌムの方法で既に盎接実装しおいたす。

 interface SplashView { fun showLoading() } class SplashActivity: Activity, SplashView { override fun onCreate() { } override fun showLoading() { findViewById(R.id.progress).show() } } 

したがっお、Presenterは次のように行いたす。OnCreateで䜜成し、これをViewずしお枡したす。 倚くの堎合、完党に有効なオプションです。

 interface SplashView { fun showLoading() } class SplashActivity: Activity, SplashView { override fun onCreate() { SplashPresenter(view = this) } override fun showLoading() { findViewById(R.id.progress).show() } } 

2番目のオプションがありたす-別のクラスずしお衚瀺したす。 ここでは、Presenterはたったく同じで、むンタヌフェむスはたったく同じですが、実装はActivityに関連しない別個のクラスです。

 class SplashPresenter(view: SplashView) { init { view.showLoading() } interface SplashView { fun showLoading() class Impl : SplashView { override fun showLoading() { } } } } 

したがっお、プラットフォヌムコンポヌネントで動䜜できるように、プラットフォヌムビュヌが入力で送信されたす。 そしお、圌はすでに圌が必芁ずするすべおをしおいたす。

 interface SplashView { fun showLoading() class Impl(private val viewRoot: View) : SplashView { override fun showLoading() { viewRoot.findViewById(R.id.progress).show() } } } 

この堎合、アクティビティはわずかにオフロヌドされたす。 ぀たり、むンタヌフェむスを組織化する代わりに、このプラットフォヌムビュヌを取埗しおSplashPresenterを䜜成し、別のクラスをビュヌずしお䜜成したす。

 class SplashActivity: Activity, SplashView { override fun onCreate() { // Platform View class val rootView: View = ... SplashPresenter( view = SplashView.Impl(rootView) ) } } 

実際、テストの芳点から芋るず、これら2぀のアプロヌチは同じです。なぜなら、私たちはただむンタヌフェヌスから䜜業しおいるからです。 モックビュヌを䜜成し、それを枡すプレれンタヌを䜜成し、特定のメ゜ッドが呌び出されるこずを確認したす。

 @Test fun testLoadingIsShown() { val mockedView = mock<SplashView>() SplashPresenter(mockedView) verify (mockedView).showLoading() } 

唯䞀の違いは、アクティビティロヌルずビュヌロヌルの衚瀺方法です。 Viewロヌルが他のActivityロヌルず混ざらないほど倧きいず思われる堎合は、別のクラスに配眮するこずをお勧めしたす。

プラットフォヌムラッパヌレむダヌの抜象化


次に、プラットフォヌムアダプタヌレむダヌ䞊のプラットフォヌムからの抜象化に぀いお説明したす。 プラットフォヌムラッパヌは、モデルレむダヌの分離です。 問題は、プラットフォヌム偎のこの局の背埌にプラットフォヌムAPIずサヌドパヌティAPIがあり、それらが異なる圢匏で提䟛されるため、䞀般的にそれらを倉曎できないこずです。 これらは、静的メ゜ッド、シングルトヌン、最終クラス、および非最終クラスずしお提䟛されたす。 最初の3぀のケヌスでは、実装に圱響を䞎えるこずはできたせん;テストでの動䜜を眮き換えるこずはできたせん。 そしお、それらが最終クラスでない堎合にのみ、テストでの動䜜に䜕らかの圢で圱響を䞎えるこずができたす。

したがっお、このようなAPIを盎接䜿甚する代わりに、ラッパヌを䜜成するのが理にかなっおいる堎合がありたす。 これは、APIが盎接䜿甚される堎所です。

 class Module { init { ThirdParty.doSomething() } } 

そうする代わりに、サヌドパヌティのAPIのメ゜ッドを転送するだけの最も簡単なケヌスのラッパヌを䜜成したす。

 interface Wrapper { fun doSomething() class Impl: Wrapper { override fun doSomething() { ThirdParty.doSomething()     } } } 

実装のラッパヌを取埗し、むンタヌフェむスの背埌に隠したした。したがっお、モゞュヌル内で既にWrapperを呌び出しおいたす。これは明瀺的な䟝存関係ずしお提䟛されたす。

 class Module(wrapper: Wrapper) { init { wrapper.doSomething() } } 

保蚌されたテスト容易性に加えお、これは以䞋を提䟛したす。


Design Smell, , . , – . , Smell. , , Smell, . .


Android ID- . -, . , , R- .

 class SplashPresenter(view: SplashView, resources: Resources) { init { view.setTitle(resources.getString(R.string.welcome)) view.showLoading() } } 

– , Android, ID-. , , , :

 class SplashPresenter(view: SplashView, resources: Resources) { init { view.setTitle(resources.getString(R.string.welcome)) view.showLoading() } } interface Resources { fun getString(id: Int): String } 

, , ID, , . .

 public final class R { public static final class string { public static final int welcome=0x7f050000; } } 

, , - . iOS Android, .


, . - , .

 class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } 

, , , .

 class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } class AnotherModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } 

, , - .

 class SomeModule(input: String) { val state = calculateInitialState(input) // Pure private fun calculateInitialState(input: String): String = "Some complex computation for $input" } object StateCalculator { fun calculateInitialState(input: String): String = "Some complex computation for $input" } 

? calculateInitialState, . , , , .

 class SomeModule(input: String, stateCalculator: StateCalculator){ val state = stateCalculator.calculateInitialState(input) } interface StateCalculator { fun calculateInitialState(input: String): String class Impl: StateCalculator { override fun calculateInitialState(input: String): String = "Some complex computation for $input" } } 

, , calculateInitialState - . , extension- ( Kotlin), static-, , .

,


, , ( , ). , , , , , .



, .



:


, - (Activity, , broadcast-
), - ( Activity View), , , Presenter. , Presenter, DI.



, .


, ( View), . , ( ).



結論の代わりに


: « , , , – ».

. , , . , . , - . , . , .




– , Mobius 2017 Moscow :

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


All Articles