Skip to content

Commit 2455976

Browse files
authored
Merge pull request #212 from mori-atsushi/v1.3.0
Prepar v1.3.0
2 parents 3cc716e + 871c149 commit 2455976

23 files changed

+559
-89
lines changed

Diff for: build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ subprojects {
3131
}
3232

3333
tasks.dokkaHtmlMultiModule {
34-
moduleVersion.set("1.3.0-beta02")
34+
moduleVersion.set("1.3.0")
3535
outputDirectory.set(rootDir.resolve("docs/static/api"))
3636
}

Diff for: docs/blog-jp/2023-03-25-test-test-test.mdx

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
slug: test-test-test
3+
title: Koject v1.3.0 - DIコンテナを使ったテストコードを書こう
4+
authors: atsushi
5+
image: /blog/2023-03-25/ogp.png
6+
---
7+
8+
import {
9+
KojectStart,
10+
KojectStartTest,
11+
KojectRunTest,
12+
Inject,
13+
TestProvides,
14+
} from '@site/src/components/CodeLink';
15+
16+
# Koject v1.3.0 - DIコンテナを使ったテストコードを書こう
17+
18+
![](/blog/2023-03-25/banner.png)
19+
20+
継続的なソフトウェア開発を行う上で、テストコードは重要な役割を果たします。
21+
Koject v1.3.0ではテストのサポートが追加されました。
22+
この記事ではテスト時にDIコンテナを使う理由と、Kojectを使ったテストコードの書き方、Koject v1.3.0で追加されたもう一つの機能について紹介します。
23+
24+
<!--truncate-->
25+
26+
[**Read in English →**](/blog/test-test-test)
27+
28+
## テスト時もDIコンテナを使う
29+
[以前紹介](/blog/jp/first-stable-release)したように、Dependency Injectionのパターンに従うことで、テスト容易性を向上させることができます。
30+
テスト時に一部の依存関係を差し替えることで、外部との通信をなくして不安定なテストを避けたり、テストにかかる時間を短縮することが可能になります。
31+
32+
この際に利用される、実際とは異なるオブジェクトのことを、モックやフェイクと言います。
33+
一方で、単一のクラスをテストするために、そのクラスの全ての依存関係を実際とは異なるモックに差し替えると、テストの信頼性や保守性の面で問題が生じるかもしれません。
34+
35+
```kotlin
36+
class VideoUploadServiceTest {
37+
private val videoUploader = mock(VideoUploader::class)
38+
private val notificationManager = mock(NotificationManager::class)
39+
private val videoUploadService =
40+
VideoUploadService(videoUploader, notificationManager)
41+
42+
@Test
43+
fun test() {
44+
/* ... */
45+
}
46+
}
47+
```
48+
49+
モックオブジェクトは実際のオブジェクトと全く同じ動作をするわけではないので、それが理由でテストが失敗する可能性があります。
50+
テストが失敗した際に実際のコードが間違っているのか、もしくはうまくモックできていないのか、判断するのに手間がかかるようになります。
51+
反対に、モックが正しくないことにより、コードが間違っているのにテストが成功する可能性も存在します。
52+
53+
テストの対象のほとんどがモックの場合、あまり意味のあるテストとは言えないでしょう。
54+
55+
また、モックオブジェクトが増えるとその保守も難しくなります。
56+
モックしている対象が変わった場合、そのモックオブジェクトも追従する必要があります。
57+
メンテしにくいテストは次第に機能しなくなるため、テストのメンテナンス性は重要です。
58+
59+
できる限り実際の依存関係を利用することをおすすめします。
60+
制御が難しい外部と通信する場合や、そのままだと莫大な時間がかかる場合に、その該当箇所のみをテスト用のものに差し替えます。
61+
62+
テスト対象のクラスが多くの依存関係を持つ場合、インスタンスの生成に苦労すると考えるかもしれません。
63+
心配しないでください。
64+
本番環境と同じくDIコンテナを使えば、テスト時の依存関係も簡単に作成することができます。
65+
66+
## UnitテストでKojectを使う
67+
68+
テスト時にKojectを使う方法について紹介します。
69+
70+
例えば、このような依存関係を持つコードがあったとします。
71+
72+
```kotlin
73+
interface Api
74+
75+
@Provides
76+
@Binds
77+
class ApiImpl: Api
78+
79+
interface SampleRepository
80+
81+
@Provide
82+
@Binds
83+
class SampleRepositoryImpl(
84+
private val api: Api
85+
): SampleRepository
86+
87+
@Provides
88+
class SampleController(
89+
private val repository: SampleRepository
90+
)
91+
```
92+
93+
この`SampleController`に対して、テストコードを作成してみましょう。<KojectRunTest/>を使うことで、テスト用のDIコンテナを起動してテストすることができます。
94+
その後、<Inject/>関数を使って、依存が解決されたクラスを取得します。
95+
96+
```kotlin
97+
class SampleControllerTest() {
98+
@Test
99+
fun test() = Koject.runTest {
100+
val controller = inject<SampleController>() // can be injected
101+
}
102+
}
103+
```
104+
105+
Apiクラスをモックに差し替えたい場合、以下のように<TestProvides/>を使って上書きします。
106+
これにより、テスト時は`MockApi`が使われ、本番環境では`ApiImpl`が使われます。
107+
108+
```kotlin
109+
@TestProvides
110+
@Binds
111+
class MockApi: Api
112+
```
113+
114+
テストには追加の依存関係の設定が必要です。
115+
詳しくは、以下のドキュメントを参照してください。
116+
117+
* [テスト(共通)](/docs/test)
118+
* [Androidテスト](/docs/android/tests)
119+
* [iOSテスト](/docs/ios/tests)
120+
121+
## UIテストでKojectを使う
122+
123+
KojectはUIテスト等のintegrationテストでも利用することができます。
124+
125+
### Android UIテスト
126+
127+
Androidアプリでは、実機もしくはエミュレータを使った[instrumentedテスト](https://developer.android.com/training/testing/instrumented-tests)を作成するか、[Robolectoric](https://robolectric.org/)を利用することで、UIテストを実行します。
128+
129+
UIテストでテスト用DIコンテナを利用するには、アプリケーションクラスで呼んでいる<KojectStart/>を<KojectStartTest/>に置き換える必要があります。
130+
アプリケーションクラスの置き換えにはカスタム`Runner`を作成してください。
131+
132+
```kotlin
133+
class TestApplication : Application() {
134+
override fun onCreate() {
135+
super.onCreate()
136+
137+
Koject.startTest {
138+
application(this@TestApplication)
139+
}
140+
}
141+
}
142+
```
143+
```kotlin
144+
class TestRunner : AndroidJUnitRunner() {
145+
override fun newApplication(
146+
classLoader: ClassLoader?,
147+
className: String?,
148+
context: Context?
149+
): Application {
150+
return super.newApplication(classLoader, TestApplication::class.java.name, context)
151+
}
152+
}
153+
```
154+
155+
以下ドキュメントでより詳しい詳しい説明を行っています。
156+
157+
* [Androidテスト](/docs/android/tests)
158+
159+
160+
### iOS UIテスト
161+
162+
Kojectは[Kotlin Multiplatform Mobile](https://kotlinlang.org/lp/mobile/)に対応しており、iOSでも利用することができます。
163+
164+
iOSのUIテストは[XCTest](https://developer.apple.com/documentation/xctest)を利用し、Swiftで記述します。
165+
以下のようにテスト時用の分岐を作成し、テスト用のDIコンテナを開始してください。
166+
167+
```swift
168+
import SwiftUI
169+
import shared
170+
171+
@main
172+
struct MyApp: App {
173+
init() {
174+
#if DEBUG
175+
let isTesting = CommandLine.arguments.contains("TESTING")
176+
if isTesting {
177+
KojectHelper.shared.startTest()
178+
} else {
179+
KojectHelper.shared.start()
180+
}
181+
#else
182+
KojectHelper.shared.start()
183+
#endif
184+
}
185+
186+
var body: some Scene {
187+
/* ... */
188+
}
189+
}
190+
```
191+
```swift
192+
import XCTest
193+
194+
final class UITests: XCTestCase {
195+
let app = XCUIApplication()
196+
197+
func testSome() {
198+
app.launchArguments = ["TESTING"]
199+
app.launch()
200+
201+
/* ... */
202+
}
203+
}
204+
```
205+
206+
iOSでKojectを利用する詳しい情報は、以下ドキュメントから確認できます。
207+
208+
* [iOS(KMM)](/docs/ios/basic)
209+
* [iOSテスト](/docs/ios/tests)
210+
211+
## 推移的な依存関係の収集
212+
Koject v1.3.0のもう一つの重要な変更点は、gradleマルチモジュールを使った場合の依存関係の収集が改善されたことです。
213+
214+
Koject v1.2.0以前は、配布している全ての依存関係はappモジュールから直接参照できる必要がありました。
215+
216+
![](/blog/2023-03-25/module1.png)
217+
218+
Koject v1.3.0からは推移的な依存関係の収集に対応したため、直接参照できる必要はありません。
219+
220+
![](/blog/2023-03-25/module2.png)
221+
222+
有効化には、以下のようにモジュール名を指定する必要があります。
223+
224+
```diff title="build.gradle"
225+
dependencies {
226+
implementation("com.moriatsushi.koject:koject-core:1.3.0-beta02")
227+
ksp("com.moriatsushi.koject:koject-processor-lib:1.3.0-beta02")
228+
}
229+
230+
+ ksp {
231+
+ arg("moduleName", project.name)
232+
+ }
233+
```
234+
235+
詳細は[セットアップドキュメント](/docs/setup)を確認してください。
236+
237+
## Kojectを使って快適な開発を
238+
テスト時もDIコンテナを利用する有用性について紹介しました。
239+
Kojectを利用することで、部分的に依存関係を差し替え、すぐにテストを開始することができます。
240+
また、gradleマルチモジュールのサポートも強化されました。
241+
242+
KojectはDIコンテナとして基本的な機能を全て揃えており、今すぐ利用することができます。
243+
なにか問題があれば、いつでも[Issue](https://github.com/mori-atsushi/koject/issues)から教えて下さい。

0 commit comments

Comments
 (0)