前回( https://qiita.com/yoshikawaa/items/b61650446157625f3962 )に引き続き、バイブコーディングした感想をつらつら書いていきます。
今回は、前回実装したアプリケーションを利用して、Github CopilotにSpring Boot Testを実装してもらおうと思います。
記事の構成
今回の開発では、Github Copilotに与えるプロンプトで1Issueを作成し、Github Copilotの提示をレビューしつつ目的とする形にしていき1PRを作成します。1プロンプトでIssueが完了することはなく、何度もやりとりしています。
本記事では、1Issueごとに#段落で書いていきます。
リポジトリの単体テストを作る
プロンプト
リポジトリの単体テストを作る。
Spring Boot TestのMyBatis拡張をベースとして、テストケースを作る。
なんと一発でできました。
実際には全項目のアサーションなどテストとしては不足しているところもありますが、とりあえず動作するものができたので満足です。
コード:https://github.com/yoshikawaa/ai-sample/pull/14
サービスの単体テストを作る
プロンプト
サービスの単体テストを作る。
Spring Boot Testをベースとして、テストケースを作る。
以下のような問題がありました。
-
@Mockや@MockBeanではなく@MockitoBeanを利用する(Spring FW理解度の問題) - 存在しないコンストラクタを使ってしまう(Java言語知識の問題)
- アプリ実装時に利用したLombokの存在を忘れる
- 存在するメソッドに対してコンパイルエラーになるテストを生成する(Java言語知識の問題)
- パスワードのハッシュ化を考慮できていない(Spring FW理解度の問題)
これらの問題は何度かやりとりすることで解決できました。
もっと大きな問題もありました。
- サービスの存在しないメソッドに対してテストをしようとする
- テスト対象メソッドのロジックを間違えて理解して、テストが失敗する
- カバレッジ100%になる追加テストの提案ができない
特に「カバレッジ100%になる追加テストの提案ができない」ことは問題で、わざわざAIにテストを実装してもらう意義に関わってきます。
どこの部分のカバレッジが不足しているかを指摘したら、そこに対するテストは実装してくれたので、理解できないわけではないと思います。カバレッジは人間が見て提案してあげる必要がある、という結論になります。
コード:https://github.com/yoshikawaa/ai-sample/pull/16
コントローラの単体テストを作る
プロンプト
コントローラの単体テストを作る。
Spring Boot TestのWebMvcTestをベースに、MockMvcを利用したテストを作る。
コントローラクラスのカバレッジ100%となるようにテストケースを作る。
カバレッジが気になるので、今回は最初から条件として提示しました。
今までのリポジトリ・サービスと異なり、コントローラは複数あるので、1つずつ実装してもらう必要がありました。
最初の関門はSpring Security
まず最初にトップ画面のコントローラに対してテストを実装してもらいました。
結果は200ではなく401です。Spring Securityが有効にも関わらず、WebMvcTestでコントローラのみ有効化しているため、セキュリティ設定が読み込まれておらず、認可で弾かれました。
実態に合わせて、@Import(SecurityConfig.class)を追加することで解決しました。
次の関門はテスト対象コントローラの大きさ
次の顧客情報一覧機能のコントローラは適切にテストできましたが、その次に顧客情報登録機能のコントローラをテストしようとしたとき、Copilotは実在するコントローラに対するテストを実装せず、例として簡略化したコントローラに対するテストを実装してきました。これを指摘しても、ついに実在するコントローラに対するテストを一発で実装してくれることはありませんでした。
コントローラのクラス名#メソッド名に対してテストを実装するよう注文することで解決しました。小規模の実装しか受け付けてくれないようです。
コントローラのテストにおける問題
その後、コントローラでは以下のような問題が発生しました。
- リクエストマッピングを間違える(クラスとメソッドの
@RequestMapping合成を忘れる) - サービスのモック化を忘れる
- 画面アプリケーションなのに勝手に
application/jsonリクエストを送信しようとする - リクエストパラメータをMultiValueMapで送信せず文字列連結しようとしてくる
- Spring SecurityでCSRF対策しているのに非武装で突っ込もうとする
- 入力チェックのテストをしているのにmodel().attributeHasNoErrorsを省略しようとする
- 入力チェックのテストをしているのに入力値を省略しようとする
-
@ExceptionHandlerのテストでリクエスト属性を利用してエラーを渡そうとする(Spring FWの裏側を使おうとする) - 複数のコントローラのテストを実装し始めると、前のテストでのお作法を忘れはじめる
Spring FW、特にSpring Testの知識の問題が主のようで、学習の足りなさを感じました。何度かやりとりして解決したものの、こちらからヒントを提示したものもあります。
セキュリティの統合テストにおける問題
今回は、コントローラのテストに合わせてセキュリティ設定もテストすることにしました。単体テストから統合テストにランクアップした形です。
セキュリティの統合テストでは以下のような問題が発生しました。
-
@SpringBootTestでMockMvcが有効にならない - ログインのテストで「そんなユーザいないよ」になる(テスト用ユーザのセットアップ不足)
- カスタムUserDetailsを利用しているコントローラの認可テストで
@MockUserを使おうとする(デフォルトUserになってしまう)
特に「カスタムUserDetailsを利用しているコントローラの認可テストで@MockUserを使おうとする」はテスト用ユーザのセットアップと絡んでいたため深刻で、Copilotは完全に解決することはできませんでした。最終的にWithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)で解決することを、私から提案しました。
コード:https://github.com/yoshikawaa/ai-sample/pull/18
バリデータの単体テストを作る
プロンプト
バリデータの単体テストを作る。
Spring Boot Testをベースにテストを作る。
バリデータクラスのカバレッジ100%となるようにテストケースを作る。
バリデータのテストはドライバも使わず素のテストを実装してきたのですが、今回は@SpringBootTestでBean ValidationのValidatorをインジェクトしてテストするよう指示しなおしました。
ここでは以下の問題がありました。
- テスト対象メソッドのロジックを間違えて理解して、テストが失敗する
- カバレッジ100%になる追加テストの提案ができない
ここでやっとバグらしいバグが見つかりました。
テストを実装してもらった意義がありました。
具体的には、バリデータの中でSecurityContextHolderからパスワードを取得するのですが、この時のAuthenticationとPrincipalはnullになる可能性があるということです。
// ダメなコード
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof CustomerUserDetails userDetails) {
String currentPasswordHash = userDetails.getPassword();
return passwordEncoder.matches(value, currentPasswordHash);
}
カバレッジを100%にする段階でテストエラーになったため、Spring Securityのドキュメントを調べたら@Nullableになっていました。
コード:https://github.com/yoshikawaa/ai-sample/pull/20
まとめ
今回は、Github CopilotにSpring Boot Testでテストしてもらいました。
バイブコーディングしてみた結果としては、アプリケーションを作る時よりさらにテストは介助が必要になった感じです。
それでも自分はコードほぼ書いてないし、やっぱり知らない人に任せるよりはいいという感想。
今回はミニマルなアプリケーションを作った結果として、テストもそれぞれ実装が異なり、流用できる部分が少なかったです。似たような業務がたくさんある場合、大量実装のお供にAIを使うとより効果を発揮できるかもしれません。
テストが動くまでにある程度人の手がいるのと、内容はレビュー必須だと思うので、知らない人がAIに頼るのではなく、ある程度有識者がAIを利用する必要があると思います。