質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.32%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

1回答

250閲覧

mockitoによる重複チェックのテスト

Fitz

総合スコア1

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2025/02/06 09:38

編集2025/02/08 01:17

実現したいこと

テストで実現したいこと:以下概要の処理で①検索メソッドがきちんと機能すること、➁重複チェック処理で所定のメッセージが表示されること
概要:ReservationService内で、予約情報をもとに会議の開始時間と終了時間が被らないようにするため、anyMatchメソッドを用いて重複チェックを行う処理を記述した。この処理でtrueだった場合、UnavailableReservationExceptionメソッドでメッセージを表示させるようにした。

発生している問題・分からないこと

重複チェックをテストにどのように落とし込むかが分かりません。

検索メソッドがきちんと機能するかについては基本的なメソッドのテストで実現できると思っています。

該当のソースコード

ReservationService

1@Service 2@Transactional 3public class ReservationService { 4 5 @Autowired 6 ReservationRepository reservationRepository; 7 8 @Autowired 9 ReservableRoomRepository reservableRoomRepository; 10 11 //指定の会議室の予約一覧を取得 12 public List<Reservation> findReservations(ReservableRoomId reservableRoomId){ 13 14 return reservationRepository.findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 15 } 16 17 //予約機能 18 public Reservation reserve(Reservation reservation) { 19 20 ReservableRoomId reservableRoomId = reservation.getReservableRoom().getReservableRoomId(); 21 22 //追記:開始時刻を取得 23 LocalTime startTime =reservation.getStartTime(); 24 25 //追記:終了時刻を取得 26 LocalTime endTime =reservation.getEndTime(); 27 28 LocalDate reservedDate = reservableRoomId.getReservedDate(); 29 30 //追記重複チェック 31 //指定された部屋で検索 32 List<Reservation> reservationlist = findReservations(reservableRoomId); 33 boolean resultTrue = reservationlist.stream() 34 .anyMatch(reservate -> 35 (reservate.getStartTime().isBefore(endTime) && reservate.getEndTime().isAfter(startTime)) 36 || (reservate.getStartTime().equals(startTime) || reservate.getEndTime().equals(endTime)) 37 ); 38 39 40 //悲観ロックできるようにしたメソッドを呼び出す 41 ReservableRoom reservable = reservableRoomRepository.findOneForUpdateByReservableRoomId(reservableRoomId); 42 43 if(reservable == null) { 44 throw new UnavailableReservationException("入力の日付、部屋の組み合わせは予約できません。"); 45 } 46 //追記箇所 47 if(reservedDate.isBefore(LocalDate.now()) || reservedDate.equals(LocalDate.now())) { 48 if(startTime.isBefore(LocalTime.now())) { 49 throw new UnavailableReservationException("その時間は予約できません。"); 50 } 51 } 52 53 //追記箇所検索した内容で審議値がtrueになるとメッセージ表示 54 if (resultTrue == true) { 55 throw new UnavailableReservationException("入力の時間帯はすでに予約済みです。"); 56 } 57 58 59 //予約情報登録 60 reservationRepository.save(reservation); 61 return reservation; 62 }

ReservationServiceTest

1@ExtendWith(MockitoExtension.class) 2class ReservationServiceTest { 3 4 5 //依存しているオブジェクトのモック作成 6 7 @Mock 8 private ReservationRepository reservationRepository; 9 @Mock 10 private ReservableRoomRepository reservableRoomRepository; 11 @Mock 12 private Reservation reservation; 13 @Mock 14 private ReservableRoomId reservableRoomId_mock; 15 16 private AutoCloseable closeable; 17 18 //今回テストするクラスにmockを注入 19 @InjectMocks 20 private ReservationService reservationService; 21 22 //モックの初期化 23 @BeforeEach 24 void initService() { 25 closeable = MockitoAnnotations.openMocks(this); 26 } 27 28 @AfterEach 29 void closeService() throws Exception { 30 closeable.close(); 31 } 32 @Test 33 @DisplayName("予約情報取得") 34 public void testFindReservations() { 35 36 //予約日の日付を初期化 37 LocalDate date = LocalDate.of(2025,01,15); 38 //比較用のreservableRoomIdを作成 39 //reservableRoomIdにはroomIdとreservedDateが設定されている 40 ReservableRoomId reservableRoomId = new ReservableRoomId(1, date); 41 42 //比較用のstartTime,endTime 43 LocalTime startTime = LocalTime.of(17, 00, 00); 44 LocalTime endTime = LocalTime.of(21, 00, 00); 45 46 //比較用のreservableRoom 引数はreservableRoomId 47 ReservableRoom reservableRoom = new ReservableRoom(reservableRoomId); 48 49 //比較用のreservationId 50 Integer reservationId = 83; 51 52 //Reservationに比較用の予約情報を設定 53 reservation = new Reservation(); 54 reservation.setReservationId(reservationId); 55 reservation.setStartTime(startTime); 56 reservation.setEndTime(endTime); 57 reservation.setReservableRoom(reservableRoom); 58 59 //モックに戻り値を設定。 60 doReturn(Optional.of(reservation)).when(reservationRepository) 61 .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 62 63 Reservation reservation_Test = (Reservation) reservationRepository 64 .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 65 66 //値の比較 67 assertEquals(reservation.getReservationId(),reservation_Test.getReservationId()); 68 assertEquals(reservation.getStartTime(),reservation_Test.getStartTime()); 69 assertEquals(reservation.getEndTime(),reservation_Test.getEndTime()); 70 assertEquals(reservation.getReservableRoom(),reservation_Test.getReservableRoom()); 71 72 // 呼び出しの検証 73 verify(reservationRepository, times(1)) 74 .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 75 }

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

if文の検証は見当たりませんでした。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2025/02/07 17:34

>if文の検証は見当たりませんでした。 というのは、本件では重複チェックがと言われてますけども、そもそも状況・条件によって動作が変わるメソッドのテストをしたことが無いということでしょうか。 ちなみに検索メソッドのテストはどのようなコードでしょう。
Fitz

2025/02/08 01:17

検索メソッドのテストは以下のとおり記述しています。 @Test @DisplayName("予約情報取得") public void testFindReservations() { //予約日の日付を初期化 LocalDate date = LocalDate.of(2025,01,15); //比較用のreservableRoomIdを作成 //reservableRoomIdにはroomIdとreservedDateが設定されている ReservableRoomId reservableRoomId = new ReservableRoomId(1, date); //比較用のstartTime,endTime LocalTime startTime = LocalTime.of(17, 00, 00); LocalTime endTime = LocalTime.of(21, 00, 00); //比較用のreservableRoom 引数はreservableRoomId ReservableRoom reservableRoom = new ReservableRoom(reservableRoomId); //比較用のreservationId Integer reservationId = 83; //Reservationに比較用の予約情報を設定 reservation = new Reservation(); reservation.setReservationId(reservationId); reservation.setStartTime(startTime); reservation.setEndTime(endTime); reservation.setReservableRoom(reservableRoom); //モックに戻り値を設定。 doReturn(Optional.of(reservation)).when(reservationRepository) .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); Reservation reservation_Test = (Reservation) reservationRepository .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); //値の比較 assertEquals(reservation.getReservationId(),reservation_Test.getReservationId()); assertEquals(reservation.getStartTime(),reservation_Test.getStartTime()); assertEquals(reservation.getEndTime(),reservation_Test.getEndTime()); assertEquals(reservation.getReservableRoom(),reservation_Test.getReservableRoom()); // 呼び出しの検証 verify(reservationRepository, times(1)) .findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); }
jimbe

2025/02/08 04:09

失礼しました、質問のコードが検索のテストだったんですね。 で、検索のテストは何を目的としているのでしょうか。 リポジトリオブジェクトの検索メソッドを呼ぶこと(だけ)を想定しているのであれば、その呼び出しの確認だけで良いはずです。実際にどのようなデータが得られなければならないのかはリポジトリオブジェクトのメソッドのテストのほうでやるべきでしょう。 一方実際に得られるデータを確認するのであればリポジトリオブジェクトのメソッドの呼び出しは関係無く、データが有る場合と無い場合という二つのテストメソッドを作り、有る場合はソートされているかまで確認するべきでは無いでしょうか。 同様に予約メソッドも正常な(登録される)場合と異常な(例外が発生する)場合の二つは最小限必要で、例外が発生する条件が細かいならその条件毎にテストメソッドを分けて例外が発生することを確認することも必要かもしれません。 テストをメソッド1つで終わらせる必要はありません。むりやり1つに詰め込むと返って何のテストなのか分かり難くなります。 またどれほどのテストをすれば良いのか等は常々議論されていることです。テスト技法等の情報を探されると良いかと思います。
Fitz

2025/02/08 07:30

検索のテストは「reserve」メソッドで使用されている同クラスの「findReservations」メソッドが呼び出され、重複チェックが正常に行われているかテストするために記述しました。 検索のテストで「findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc」メソッドが使用されているのは、「findReservations」メソッドが対象のメソッド結果を返していたためそれを呼び出せば「findReservations」メソッドが呼び出されたことになるのではないか?と思ったからです。 jimbeさんのコメントをもとに自身がしたいことを要約すると ①「findReservations」メソッドが呼び出されているかテストする(検索データが正常に取得できているか確認する場合は必要に応じてリポジトリオブジェクトのテストクラスを作成し、そちらで作成・データ検証を行う) ②呼び出し後に記述されている重複チェックは別途テストメソッドを用意しテストする ということでしょうか? なお、今回は追記箇所のテストを目的にしています。
jimbe

2025/02/08 08:08

検索メソッドがたまたま(?)予約メソッドから分かれただけということなら、一回呼ぶ一行だけを分けても意味は無いような気はします。予約メソッドに展開したほうが簡単なのでは。 予約メソッドは、追加個所が異常終了する部分とはいえ異常じゃないモノを異常と判断しないことを示すテストも必要なのですから、結局メソッド全体の動作のテストは必要ではないでしょうか。 今まで予約メソッドのテストが無かったとしても、「重複した予約を入れて例外が発生してテスト終わり」・・・では、重複していない予約なら例外が発生 *しない* かどうか保証出来ないですよね。 もし重複判定の条件の組み合わせとか不等号とかが間違っていて重複してないはずが重複判定されるバグが潜んでいるとしたら、テストで発見されていなくてはなりません。 その為には(1つのメソッドに対して複数の)正常時・異常時テスト(メソッド)が必要になるはずです。
guest

回答1

0

※どうテストするか(どういうテストコードにするか)は議論がある所だと思いますので、以下はざっくりという事でご了承ください。また想像仕様が多分に含まれます。

テストし易いように ReservationService の DI はコンストラクタにします。

java

1 ReservationService(ReservationRepository reservationRepository, ReservableRoomRepository reservableRoomRepository) { 2 this.reservationRepository = reservationRepository; 3 this.reservableRoomRepository = reservableRoomRepository; 4 }

同様に Reservation もセッターでは無くコンストラクタで設定できるようにします。

java

1 private final int id; 2 private final ReservableRoom room; 3 private final LocalTime start, end; 4 5 Reservation(int id, ReservableRoom room, LocalTime start, LocalTime end) { 6 this.id = id; 7 this.room = room; 8 this.start = start; 9 this.end = end; 10 }

で、重複チェックは Reservation の start/end との比較なのでそのメソッドも Reservation に入れてしまいます。
※ start は含み、 end は含まないこととしています。

java

1 boolean isDuplicate(LocalTime start, LocalTime end) { 2 return !(this.end.compareTo(start) <= 0 || end.compareTo(this.start) <= 0); 3 }

このメソッドのテスト

java

1 @Test 2 public void testIsDuplicate() { 3 Reservation reservation = new Reservation(0, null, LocalTime.of(10,0), LocalTime.of(14,0)); 4 5 Assert.assertTrue(reservation.isDuplicate(LocalTime.of(10,1), LocalTime.of(13,59))); 6 Assert.assertTrue(reservation.isDuplicate(LocalTime.of(10,0), LocalTime.of(14,0))); 7 Assert.assertTrue(reservation.isDuplicate(LocalTime.of(9,59), LocalTime.of(14,1))); 8 9 Assert.assertFalse(reservation.isDuplicate(LocalTime.of(9,0), LocalTime.of(10,0))); 10 Assert.assertTrue(reservation.isDuplicate(LocalTime.of(9,0), LocalTime.of(10,1))); 11 12 Assert.assertFalse(reservation.isDuplicate(LocalTime.of(14,0), LocalTime.of(15,0))); 13 Assert.assertTrue(reservation.isDuplicate(LocalTime.of(13,59), LocalTime.of(15,0))); 14 }

ReservationService の予約メソッドを整理して

java

1 //予約 2 public Reservation reserve(Reservation reservation) { 3 ReservableRoomId roomId = reservation.getReservableRoom().getReservableRoomId(); 4 LocalDate date = roomId.getReservedDate(); 5 LocalTime startTime = reservation.getStartTime(); 6 LocalTime endTime = reservation.getEndTime(); 7 8 //TODO: ここで直接現在の日時を得て使用するのは宜しくないが・・・ 9 if(date.atTime(startTime).compareTo(LocalDateTime.now()) <= 0) { 10 throw new UnavailableReservationException("その時間は予約できません。"); 11 } 12 13 //悲観ロックできるようにしたメソッドを呼び出す 14 ReservableRoom reservable = reservableRoomRepository.findOneForUpdateByReservableRoomId(roomId); 15 if(reservable == null) { 16 throw new UnavailableReservationException("入力の日付、部屋の組み合わせは予約できません。"); 17 } 18 19 List<Reservation> reservationlist = reservationRepository.findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(roomId); 20 boolean duplicated = reservationlist.stream().anyMatch(r -> r.isDuplicate(startTime, endTime)); 21 if(duplicated) { 22 throw new UnavailableReservationException("入力の時間帯はすでに予約済みです。"); 23 } 24 25 reservationRepository.save(reservation); 26 27 return reservation; 28 }

そのテスト(とりあえず正常と重複例外)

java

1 @Test 2 @DisplayName("予約OK") 3 public void testReserve() { 4 ReservableRoomId reservableRoomId = new ReservableRoomId(1, LocalDate.now().plusDays(1)); //TODO:テスト対象内で直接システム日付を得ている為ここでも固定値に出来ない 5 6 Reservation reservation = new Reservation(83, new ReservableRoom(reservableRoomId), LocalTime.of(17,0), LocalTime.of(21,0)); 7 8 Mockito.doReturn( 9 List.of( 10 new Reservation(80, new ReservableRoom(reservableRoomId), LocalTime.of(10,0), LocalTime.of(11,0)), 11 new Reservation(81, new ReservableRoom(reservableRoomId), LocalTime.of(16,0), LocalTime.of(17,0)), 12 new Reservation(82, new ReservableRoom(reservableRoomId), LocalTime.of(22,0), LocalTime.of(23,30)) 13 ) 14 ).when(reservationRepository).findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 15 Mockito.doReturn( 16 new ReservableRoom(reservableRoomId) 17 ).when(reservableRoomRepository).findOneForUpdateByReservableRoomId(reservableRoomId); 18 19 Reservation result = new ReservationService(reservationRepository, reservableRoomRepository).reserve(reservation); 20 21 Assert.assertTrue(result == reservation); 22 Mockito.verify(reservationRepository, Mockito.times(1)).save(reservation); 23 } 24 25 @Test 26 @DisplayName("予約済み例外") 27 public void testReserve_duplicatedThrow() { 28 ReservableRoomId reservableRoomId = new ReservableRoomId(1, LocalDate.now().plusDays(1)); 29 30 Reservation reservation = new Reservation(83, new ReservableRoom(reservableRoomId), LocalTime.of(17,0), LocalTime.of(21,0)); 31 32 Mockito.doReturn( 33 List.of( 34 new Reservation(80, new ReservableRoom(reservableRoomId), LocalTime.of(10,0), LocalTime.of(11,0)), 35 new Reservation(81, new ReservableRoom(reservableRoomId), LocalTime.of(18,0), LocalTime.of(19,0)), //これと重複 36 new Reservation(82, new ReservableRoom(reservableRoomId), LocalTime.of(22,0), LocalTime.of(23,30)) 37 ) 38 ).when(reservationRepository).findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId); 39 Mockito.doReturn( 40 new ReservableRoom(reservableRoomId) 41 ).when(reservableRoomRepository).findOneForUpdateByReservableRoomId(reservableRoomId); 42 43 try { 44 45 new ReservationService(reservationRepository, reservableRoomRepository).reserve(reservation); 46 47 Assert.fail(); 48 } catch(UnavailableReservationException e) { 49 Assert.assertEquals(e.getMessage(), "入力の時間帯はすでに予約済みです。"); 50 Mockito.verify(reservationRepository, Mockito.times(0)).save(reservation); 51 } 52 }

投稿2025/02/09 09:23

jimbe

総合スコア13318

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Fitz

2025/02/09 11:18

回答いただきありがとうございます。記述方法についてお聞きしたいことがあります。 ① return !(this.end.compareTo(start) <= 0 || end.compareTo(this.start) <= 0); ②boolean duplicated = reservationlist.stream().anyMatch(r -> r.isDuplicate(startTime, endTime)); ①、➁ともに、条件式の簡略化ということでよろしいでしょうか?
jimbe

2025/02/09 12:38

① の式は簡略化と言えるかもしれませんが、メソッドに分けたのはテストし易さ・記述し易さです。 and/or で繋がる式はテストの場合分けパターンが細かくなりがちです。それが reserve のように準備が必要なメソッドの中に直接書いてあると reserve のテストメソッド数が多くなります。それを判定メソッドにすることでその判定メソッドのテストで式のパターンを網羅しておいて、 reserve のほうでは判定メソッドの結果 true/false の 2 パターンのみで済ましています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.32%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問