|
| 1 | +--- |
| 2 | +name: test |
| 3 | +description: 테스트 코드를 작성하거나 수정할 때 이 프로젝트의 테스트 컨벤션과 패턴을 참고합니다 |
| 4 | +--- |
| 5 | + |
| 6 | +# 테스트 코드 작성 가이드 |
| 7 | + |
| 8 | +## 테스트 기본 설정 |
| 9 | + |
| 10 | +모든 통합 테스트는 `@TestContainerSpringBootTest` 어노테이션을 사용합니다. |
| 11 | + |
| 12 | +```java |
| 13 | +@TestContainerSpringBootTest |
| 14 | +@DisplayName("채팅 서비스 테스트") |
| 15 | +class ChatServiceTest { |
| 16 | + // 테스트 코드 |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +**제공 기능:** |
| 21 | +- MySQL, Redis 자동 실행 |
| 22 | +- Spring Boot 컨텍스트 로드 |
| 23 | +- 테스트 후 자동 DB 초기화 |
| 24 | +- JUnit 5 기반 |
| 25 | + |
| 26 | +## Fixture 패턴 |
| 27 | + |
| 28 | +테스트 데이터는 Fixture로 생성합니다 (FixtureBuilder + Fixture 패턴). |
| 29 | + |
| 30 | +**위치:** `src/test/java/com/example/solidconnection/[domain]/fixture/` |
| 31 | + |
| 32 | +``` |
| 33 | +fixture/ |
| 34 | +├── [Entity]FixtureBuilder.java # Builder 패턴 구현 |
| 35 | +└── [Entity]Fixture.java # 편의 메서드 제공 |
| 36 | +``` |
| 37 | + |
| 38 | +### 예제: ChatRoomFixtureBuilder |
| 39 | + |
| 40 | +```java |
| 41 | +@TestComponent |
| 42 | +@RequiredArgsConstructor |
| 43 | +public class ChatRoomFixtureBuilder { |
| 44 | + |
| 45 | + private final ChatRoomRepository chatRoomRepository; |
| 46 | + |
| 47 | + private boolean isGroup; |
| 48 | + private Long mentoringId; |
| 49 | + |
| 50 | + public ChatRoomFixtureBuilder chatRoom() { |
| 51 | + return new ChatRoomFixtureBuilder(chatRoomRepository); |
| 52 | + } |
| 53 | + |
| 54 | + public ChatRoomFixtureBuilder isGroup(boolean isGroup) { |
| 55 | + this.isGroup = isGroup; |
| 56 | + return this; |
| 57 | + } |
| 58 | + |
| 59 | + public ChatRoomFixtureBuilder mentoringId(long mentoringId) { |
| 60 | + this.mentoringId = mentoringId; |
| 61 | + return this; |
| 62 | + } |
| 63 | + |
| 64 | + public ChatRoom create() { |
| 65 | + ChatRoom chatRoom = new ChatRoom(mentoringId, isGroup); |
| 66 | + return chatRoomRepository.save(chatRoom); // DB 저장 |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +### 예제: ChatRoomFixture |
| 72 | + |
| 73 | +```java |
| 74 | +@TestComponent |
| 75 | +@RequiredArgsConstructor |
| 76 | +public class ChatRoomFixture { |
| 77 | + |
| 78 | + private final ChatRoomFixtureBuilder chatRoomFixtureBuilder; |
| 79 | + |
| 80 | + // 편의 메서드: 기본값으로 생성 |
| 81 | + public ChatRoom 채팅방(boolean isGroup) { |
| 82 | + return chatRoomFixtureBuilder.chatRoom() |
| 83 | + .isGroup(isGroup) |
| 84 | + .create(); |
| 85 | + } |
| 86 | + |
| 87 | + public ChatRoom 멘토링_채팅방(long mentoringId) { |
| 88 | + return chatRoomFixtureBuilder.chatRoom() |
| 89 | + .mentoringId(mentoringId) |
| 90 | + .isGroup(false) |
| 91 | + .create(); |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +**편의 메서드 작성 팁:** |
| 97 | + |
| 98 | +- 한국어 메서드명 사용 (가독성) |
| 99 | +- 자주 사용되는 기본값 조합만 제공 |
| 100 | +- Builder를 조합하여 필요한 데이터 설정 |
| 101 | + |
| 102 | +### 테스트에서 사용 |
| 103 | + |
| 104 | +```java |
| 105 | +@TestContainerSpringBootTest |
| 106 | +class ChatServiceTest { |
| 107 | + |
| 108 | + @Autowired |
| 109 | + private ChatRoomFixture chatRoomFixture; |
| 110 | + |
| 111 | + @Test |
| 112 | + void 채팅방을_생성할_수_있다() { |
| 113 | + // 편의 메서드 사용 |
| 114 | + ChatRoom room = chatRoomFixture.채팅방(false); |
| 115 | + |
| 116 | + // Builder 직접 사용 |
| 117 | + ChatRoom customRoom = chatRoomFixture.chatRoomFixtureBuilder.chatRoom() |
| 118 | + .isGroup(true) |
| 119 | + .mentoringId(100L) |
| 120 | + .create(); |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +## 테스트 네이밍 컨벤션 |
| 126 | + |
| 127 | +### 테스트 메서드 네이밍 규칙 |
| 128 | + |
| 129 | +테스트 메서드명은 **한국어로 명확하게** 작성하며, 다음 패턴을 따릅니다: |
| 130 | + |
| 131 | +#### 1. 정상 동작 테스트 |
| 132 | + |
| 133 | +```java |
| 134 | +// 패턴: 어떤_것을_하면_어떤_결과가_나온다 |
| 135 | +@Test |
| 136 | +void 채팅방이_없으면_빈_목록을_반환한다() { ... } |
| 137 | + |
| 138 | +@Test |
| 139 | +void 최신_메시지_순으로_정렬되어_조회한다() { ... } |
| 140 | + |
| 141 | +@Test |
| 142 | +void 참여자는_메시지를_전송할_수_있다() { ... } |
| 143 | + |
| 144 | +@Test |
| 145 | +void 페이징이_정상_작동한다() { ... } |
| 146 | +``` |
| 147 | + |
| 148 | +#### 2. 예외 테스트 |
| 149 | + |
| 150 | +```java |
| 151 | +// 패턴: 어떤_것을_하면_예외_응답을_반환한다 |
| 152 | +@Test |
| 153 | +void 참여하지_않은_채팅방에_접근하면_예외_응답을_반환한다() { ... } |
| 154 | + |
| 155 | +@Test |
| 156 | +void 존재하지_않는_사용자로_메시지를_전송하면_예외_응답을_반환한다() { ... } |
| 157 | + |
| 158 | +@Test |
| 159 | +void 권한이_없으면_예외_응답을_반환한다() { ... } |
| 160 | + |
| 161 | +@Test |
| 162 | +void 필수_파라미터가_없으면_예외_응답을_반환한다() { ... } |
| 163 | +``` |
| 164 | + |
| 165 | +## BDD 테스트 작성 |
| 166 | + |
| 167 | +테스트는 Given-When-Then 구조로 작성합니다. |
| 168 | + |
| 169 | +```java |
| 170 | +@Test |
| 171 | +@DisplayName("최신 메시지순으로 채팅방 목록을 조회한다") |
| 172 | +void 최신_메시지_순으로_조회한다() { |
| 173 | + // Given: 테스트 사전 조건 |
| 174 | + SiteUser user = siteUserFixture.사용자(); |
| 175 | + ChatRoom room1 = chatRoomFixture.채팅방(false); |
| 176 | + ChatRoom room2 = chatRoomFixture.채팅방(false); |
| 177 | + chatMessageFixture.메시지("오래된 메시지", user.getId(), room1); |
| 178 | + chatMessageFixture.메시지("최신 메시지", user.getId(), room2); |
| 179 | + |
| 180 | + // When: 실제 동작 |
| 181 | + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); |
| 182 | + |
| 183 | + // Then: 결과 검증 |
| 184 | + assertAll( |
| 185 | + () -> assertThat(response.chatRooms()).hasSize(2), |
| 186 | + () -> assertThat(response.chatRooms().get(0).id()).isEqualTo(room2.getId()) |
| 187 | + ); |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +## 테스트 그룹화 (@Nested) |
| 192 | + |
| 193 | +기능별로 테스트를 그룹화합니다. |
| 194 | + |
| 195 | +```java |
| 196 | +@TestContainerSpringBootTest |
| 197 | +class ChatServiceTest { |
| 198 | + |
| 199 | + @Nested |
| 200 | + @DisplayName("채팅방 목록 조회") |
| 201 | + class 채팅방_목록을_조회한다 { |
| 202 | + |
| 203 | + @Test |
| 204 | + void 빈_목록을_반환한다() { ... } |
| 205 | + |
| 206 | + @Test |
| 207 | + void 최신_메시지_순으로_조회한다() { ... } |
| 208 | + } |
| 209 | + |
| 210 | + @Nested |
| 211 | + @DisplayName("채팅 메시지 전송") |
| 212 | + class 채팅_메시지를_전송한다 { |
| 213 | + |
| 214 | + @BeforeEach |
| 215 | + void setUp() { |
| 216 | + // 이 그룹에만 적용되는 초기 설정 |
| 217 | + } |
| 218 | + |
| 219 | + @Test |
| 220 | + void 참여자는_메시지를_전송할_수_있다() { ... } |
| 221 | + } |
| 222 | +} |
| 223 | +``` |
| 224 | + |
| 225 | +## 자주 사용하는 Assertion |
| 226 | + |
| 227 | +```java |
| 228 | +// 기본 검증 |
| 229 | +assertThat(value).isEqualTo(expected); |
| 230 | +assertThat(value).isNotNull(); |
| 231 | + |
| 232 | +// 컬렉션 |
| 233 | +assertThat(list).hasSize(3); |
| 234 | +assertThat(list).isEmpty(); |
| 235 | +assertThat(list).contains(item); |
| 236 | + |
| 237 | +// 예외 검증 |
| 238 | +assertThatCode(() -> method()) |
| 239 | + .isInstanceOf(CustomException.class) |
| 240 | + .hasMessage("error message"); |
| 241 | + |
| 242 | +// 복수 검증 |
| 243 | +assertAll( |
| 244 | + () -> assertThat(a).isEqualTo(1), |
| 245 | + () -> assertThat(b).isEqualTo(2) |
| 246 | +); |
| 247 | +``` |
0 commit comments