Manusさんってすごいかもしれない？

:::note info
最近のネットニュースで新AiのManusがすごいみたいな記事が出ていたので、先週末にアカウントを取得しに行きました。みた
ニュースでは招待コードが発行してもらうのが難しいということが記載されていたので、だめもとで申請したらあっさりアカウントが発行されました。
そんなわけで、ちょっとだけ使ってみました。
:::
Manusって何？という人向けに、どなたかが書いた記事がありますので、ご参考としてください。 

https://qiita.com/syukan3/items/2c2c5fc71c16d850f95c

# Manusにやってもらったこと
練習で作ったBookshelfというアプリのViewModelをManusに読み込ませて、テストコードを提案してと投げました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/b6df6378-e89f-4e7c-b6f6-a42e7f7a3a7f.png)

# Manus仕事をする
Manusが何やら作業をし始めました。
具体的にManusが今やっていることがログに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/6aaa49e5-bd1f-4900-b42e-0c0d02fe54ad.png)

# Manus結果を出す
時間がかかるかもしれないと思い、別のことをやっていたのですが、
10分程度でテストコードを作成しました！と通知が来たのでのぞいてみると。。。

以下Manusの出力の画面ショットです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/c4c03194-e49b-43ef-98b4-525f18201ee4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/7cafffec-3150-4ef7-8e2f-8eef3754a0f9.png)

READMEにはテストの実装について詳細に記載がありました。
READMEはmdで記載されているので、そのままコピペします。
ぱっと見ですが、変なところもあるのでそのまま使えないかもしれませんが、とても参考になるのではないかと感じました。

:::note alert
なお、提案いただいたテストコードは未検証です！！！！
検証は今週末にしようと思ってます！
:::

## ↓↓↓↓↓↓　以下manusが書いたREADME　↓↓↓↓↓↓

# BookshelfViewModel テストガイド

このドキュメントでは、`BookshelfViewModel`のユニットテストの実装方法と実行方法について説明します。

## テスト概要

`BookshelfViewModel`は、GoogleBooksAPIから本のデータを取得し、UI状態を管理するViewModelです。
主な機能は以下の通りです：

- `fetchBooks()`: 指定したクエリで本を検索する
- `setBookId()`: 選択した本のIDを設定する
- `NetworkUiState`: 通信状態を管理するシールドクラス

テストでは、これらの機能が正しく動作することを確認します。

## 必要な依存関係

テストを実行するには、以下の依存関係をプロジェクトの`build.gradle`ファイルに追加する必要があります：

```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`ファイルに実装されています。テストクラスは以下の主要なセクションで構成されています：

1. テスト環境のセットアップ
2. 基本機能のテスト
3. エッジケースのテスト

### テスト環境のセットアップ

テストでは、以下の環境を設定しています：

- `InstantTaskExecutorRule`: LiveDataの非同期操作を同期的に実行するためのルール
- テスト用のCoroutineディスパッチャー: コルーチンのテストを制御するためのディスパッチャー
- モックリポジトリ: 実際のAPIリクエストを行わずにテストするためのモック

```kotlin
@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()
    }
    
    // テストメソッド...
}
```

### 基本機能のテスト

基本機能のテストでは、以下の項目を確認しています：

1. 初期化時の動作
2. `fetchBooks()`の成功、空結果、エラー時の動作
3. `setBookId()`の有効なID、無効なID、異なる状態での動作

例えば、`fetchBooks()`の成功時のテストは以下のようになります：

```kotlin
@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")
}
```

### エッジケースのテスト

エッジケースのテストでは、以下の項目を確認しています：

1. 空のクエリでの`fetchBooks()`呼び出し
2. `fetchBooks()`の連続呼び出し
3. ネットワークエラー時の挙動
4. 同じIDでの連続`setBookId()`呼び出し
5. `fetchBooks()`後の`setBookId()`呼び出し

例えば、`fetchBooks()`の連続呼び出しのテストは以下のようになります：

```kotlin
@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)
}
```

## テストの実行方法

テストを実行するには、以下の方法があります：

1. Android Studioから実行する場合：
   - テストクラスを開き、クラス名の横にある実行ボタンをクリックする
   - または、個々のテストメソッドの横にある実行ボタンをクリックする

2. コマンドラインから実行する場合：
   ```
   ./gradlew testDebugUnitTest
   ```

## テスト実行時の注意点

1. テストはJVMで実行されるため、Androidフレームワークに依存するコードはモック化する必要があります。
2. Coroutinesのテストでは、`runTest`ブロック内でコルーチンを実行し、`testScheduler.advanceUntilIdle()`を呼び出して非同期処理の完了を待つ必要があります。
3. `@ExperimentalCoroutinesApi`アノテーションは、Coroutinesのテスト関連APIが実験的であることを示しています。将来のバージョンで変更される可能性があります。

## テストカバレッジ

このテストコードは、`BookshelfViewModel`の以下の機能をカバーしています：

- 初期化時の動作
- `fetchBooks()`の正常系と異常系
- `setBookId()`の正常系と異常系
- 様々なエッジケース

テストカバレッジを確認するには、Android Studioの「Run with Coverage」機能を使用するか、以下のGradleコマンドを実行します：

```
./gradlew testDebugUnitTestCoverage
```

## 今後の改善点

1. UIテストの追加：Composeを使用したUIとの連携テスト
2. 統合テスト：実際のリポジトリとの統合テスト
3. パフォーマンステスト：大量のデータを扱う場合のパフォーマンステスト