最近のネットニュースで新AiのManusがすごいみたいな記事が出ていたので、先週末にアカウントを取得しに行きました。みた
ニュースでは招待コードが発行してもらうのが難しいということが記載されていたので、だめもとで申請したらあっさりアカウントが発行されました。
そんなわけで、ちょっとだけ使ってみました。
Manusって何?という人向けに、どなたかが書いた記事がありますので、ご参考としてください。
Manusにやってもらったこと
練習で作ったBookshelfというアプリのViewModelをManusに読み込ませて、テストコードを提案してと投げました。
Manus仕事をする
Manusが何やら作業をし始めました。
具体的にManusが今やっていることがログに表示されます。
Manus結果を出す
時間がかかるかもしれないと思い、別のことをやっていたのですが、
10分程度でテストコードを作成しました!と通知が来たのでのぞいてみると。。。
READMEにはテストの実装について詳細に記載がありました。
READMEはmdで記載されているので、そのままコピペします。
ぱっと見ですが、変なところもあるのでそのまま使えないかもしれませんが、とても参考になるのではないかと感じました。
なお、提案いただいたテストコードは未検証です!!!!
検証は今週末にしようと思ってます!
↓↓↓↓↓↓ 以下manusが書いたREADME ↓↓↓↓↓↓
BookshelfViewModel テストガイド
このドキュメントでは、BookshelfViewModel
のユニットテストの実装方法と実行方法について説明します。
テスト概要
BookshelfViewModel
は、GoogleBooksAPIから本のデータを取得し、UI状態を管理するViewModelです。
主な機能は以下の通りです:
-
fetchBooks()
: 指定したクエリで本を検索する -
setBookId()
: 選択した本のIDを設定する -
NetworkUiState
: 通信状態を管理するシールドクラス
テストでは、これらの機能が正しく動作することを確認します。
必要な依存関係
テストを実行するには、以下の依存関係をプロジェクトのbuild.gradle
ファイルに追加する必要があります:
dependencies {
// 既存の依存関係
// テスト用の依存関係
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.0.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
testImplementation 'androidx.arch.core:core-testing:2.1.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
}
テストの実装
テストはBookshelfViewModelTest.kt
ファイルに実装されています。テストクラスは以下の主要なセクションで構成されています:
- テスト環境のセットアップ
- 基本機能のテスト
- エッジケースのテスト
テスト環境のセットアップ
テストでは、以下の環境を設定しています:
-
InstantTaskExecutorRule
: LiveDataの非同期操作を同期的に実行するためのルール - テスト用のCoroutineディスパッチャー: コルーチンのテストを制御するためのディスパッチャー
- モックリポジトリ: 実際のAPIリクエストを行わずにテストするためのモック
@ExperimentalCoroutinesApi
class BookshelfViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val testScheduler = TestCoroutineScheduler()
private val testDispatcher = StandardTestDispatcher(testScheduler)
private lateinit var mockRepository: BooksRepository
private lateinit var viewModel: BookshelfViewModel
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
mockRepository = mock(BooksRepository::class.java)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
// テストメソッド...
}
基本機能のテスト
基本機能のテストでは、以下の項目を確認しています:
- 初期化時の動作
-
fetchBooks()
の成功、空結果、エラー時の動作 -
setBookId()
の有効なID、無効なID、異なる状態での動作
例えば、fetchBooks()
の成功時のテストは以下のようになります:
@Test
fun `fetchBooks成功時にSuccessステートに遷移する`() = runTest {
// モックの動作を設定
`when`(mockRepository.getBooks(any())).thenReturn(
BooksApiResponse(items = dummyBookList)
)
// ViewModelを初期化
viewModel = BookshelfViewModel(mockRepository)
testScheduler.advanceUntilIdle()
// 新しいクエリでfetchBooksを呼び出し
viewModel.fetchBooks("Kotlin")
// コルーチンの実行を待機
testScheduler.advanceUntilIdle()
// 状態がSuccessに変わったことを確認
val currentState = viewModel.uiState.value
assert(currentState is NetworkUiState.Success)
assertEquals(dummyBookList, (currentState as NetworkUiState.Success).bookList)
// リポジトリのgetBooksメソッドが呼ばれたことを確認
verify(mockRepository).getBooks("Kotlin")
}
エッジケースのテスト
エッジケースのテストでは、以下の項目を確認しています:
- 空のクエリでの
fetchBooks()
呼び出し -
fetchBooks()
の連続呼び出し - ネットワークエラー時の挙動
- 同じIDでの連続
setBookId()
呼び出し -
fetchBooks()
後のsetBookId()
呼び出し
例えば、fetchBooks()
の連続呼び出しのテストは以下のようになります:
@Test
fun `fetchBooks連続して呼び出した場合に最新の結果が反映される`() = runTest {
// 1回目と2回目の呼び出し用のモック設定
val firstBookList = listOf(/* ... */)
val secondBookList = listOf(/* ... */)
`when`(mockRepository.getBooks("first")).thenReturn(
BooksApiResponse(items = firstBookList)
)
`when`(mockRepository.getBooks("second")).thenReturn(
BooksApiResponse(items = secondBookList)
)
// ViewModelを初期化
viewModel = BookshelfViewModel(mockRepository)
testScheduler.advanceUntilIdle()
// 1回目のfetchBooks呼び出し
viewModel.fetchBooks("first")
testScheduler.advanceUntilIdle()
// 1回目の結果を確認
val firstState = viewModel.uiState.value
assert(firstState is NetworkUiState.Success)
assertEquals(firstBookList, (firstState as NetworkUiState.Success).bookList)
// 2回目のfetchBooks呼び出し
viewModel.fetchBooks("second")
testScheduler.advanceUntilIdle()
// 2回目の結果を確認(最新の結果が反映されていること)
val secondState = viewModel.uiState.value
assert(secondState is NetworkUiState.Success)
assertEquals(secondBookList, (secondState as NetworkUiState.Success).bookList)
}
テストの実行方法
テストを実行するには、以下の方法があります:
-
Android Studioから実行する場合:
- テストクラスを開き、クラス名の横にある実行ボタンをクリックする
- または、個々のテストメソッドの横にある実行ボタンをクリックする
-
コマンドラインから実行する場合:
./gradlew testDebugUnitTest
テスト実行時の注意点
- テストはJVMで実行されるため、Androidフレームワークに依存するコードはモック化する必要があります。
- Coroutinesのテストでは、
runTest
ブロック内でコルーチンを実行し、testScheduler.advanceUntilIdle()
を呼び出して非同期処理の完了を待つ必要があります。 -
@ExperimentalCoroutinesApi
アノテーションは、Coroutinesのテスト関連APIが実験的であることを示しています。将来のバージョンで変更される可能性があります。
テストカバレッジ
このテストコードは、BookshelfViewModel
の以下の機能をカバーしています:
- 初期化時の動作
-
fetchBooks()
の正常系と異常系 -
setBookId()
の正常系と異常系 - 様々なエッジケース
テストカバレッジを確認するには、Android Studioの「Run with Coverage」機能を使用するか、以下のGradleコマンドを実行します:
./gradlew testDebugUnitTestCoverage
今後の改善点
- UIテストの追加:Composeを使用したUIとの連携テスト
- 統合テスト:実際のリポジトリとの統合テスト
- パフォーマンステスト:大量のデータを扱う場合のパフォーマンステスト