Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] : 챗봇 서비스 단위 테스트 코드를 작성한다 #94

Merged
merged 6 commits into from
Nov 27, 2024
Merged
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ dependencies {

// S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// 비동기 테스트
testImplementation 'io.projectreactor:reactor-test'
}

java {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package kusitms.backend.chatbot.application.factory;

import kusitms.backend.chatbot.application.dto.request.ChatbotRequestDto;
import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto;
import kusitms.backend.chatbot.application.dto.request.MessageDto;
import kusitms.backend.chatbot.domain.enums.Role;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;

class ClovaRequestFactoryTest {

@InjectMocks
private ClovaRequestFactory clovaRequestFactory;

@Mock
private MessageFactory messageFactory;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

/**
* ClovaRequestFactory의 createClovaRequest 메서드 테스트
*/
@Test
void testCreateClovaRequest() {
// Given
MessageDto systemMessage = new MessageDto(Role.SYSTEM.getRole(), "System message content");
when(messageFactory.createSystemMessage()).thenReturn(systemMessage);

// When
ChatbotRequestDto chatbotRequest = clovaRequestFactory.createClovaRequest();

// Then
assertNotNull(chatbotRequest);
assertEquals(1, chatbotRequest.getMessages().size());
assertEquals(systemMessage, chatbotRequest.getMessages().get(0));

ClovaRequestDto clovaRequest = (ClovaRequestDto) chatbotRequest;
assertEquals(0.8, clovaRequest.topP());
assertEquals(0.3, clovaRequest.temperature());
assertEquals(256, clovaRequest.maxTokens());
assertEquals(5.0, clovaRequest.repeatPenalty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package kusitms.backend.chatbot.application.factory;

import kusitms.backend.chatbot.application.dto.request.MessageDto;
import kusitms.backend.chatbot.domain.enums.Role;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class MessageFactoryTest {

@InjectMocks
private MessageFactory messageFactory;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
ReflectionTestUtils.setField(messageFactory,
"baseballPrompt",
"64SI64qUIOyVvOq1rCDqsIDsnbTrk5wg7LGX67SHICfro6jtgqQn7JW8LiDsgqzsmqnsnpDqsIAg7JW86rWs7JmAIOq0gOugqOuQnCDsp4jrrLjsnYQg7ZWY66m0LCDsuZzsoIjtlZjqs6Ag67Cd7J2AIOunkO2IrOuhnCAifuyalCLroZwg64Gd64KY64qUIOuLteuzgOydhCDtlbTspJguIOuMgO2ZlOulvCDtkoDslrTqsIgg65WM64qUIOy5nOq3vO2VmOqyjCwg64SI66y0IOuUseuUse2VmOyngCDslYrqsowg7ZW07KSYLiDqsIDrgZQg64qQ64KM7ZGc64+EIOyNqOyEnCDsooAg642UIOuwneqzoCDrlLDrnLvtlZwg67aE7JyE6riw66W8IOyghOuLrO2VtOykmC4g7ZWY7KeA66eMIOuwmOunkOydgCDsoIjrjIAg7ZWY7KeAIOunkOqzoCwg7KG07KSR7ZWY66m07ISc64+EIOuEiOustCDqsqnsi50g7LCo66as7KeAIOyViuydgCDrjIDtmZQg67Cp7Iud7Jy866GcIO2VtOykmC4KCgotIOyYiOyLnDogIuyeoOyLpCDslbzqtazsnqXsnZgg6rW/7KaIIO2MkOunpOygkOydgCDslrTrlJTsnbjqsIDsmpQ/IuudvOqzoCDrrLzsnLzrqbQ6ICLsnqDsi6Qg7JW86rWs7J6lIOyVnuyXkCDsnojripQg6rW/7KaIIO2MkOunpOygkOydgCAn7Jyg64uI7YGsIOyKpO2PrOy4oCfsmIjsmpQhIO2ZiO2MgOyduCBMRyDtirjsnIjsiqTsmYAg65GQ7IKwIOuyoOyWtOyKpCDqtb/spojrj4Qg7IK0IOyImCDsnojri7Xri4jri6QuIgogIAotIOuwmOunkOuhnCDri7XtlZjsp4Ag7JWK6rOgIOyYiOydmOyeiOyngOunjCDrlLHrlLHtlZjsp4Ag7JWK7J2AIOuKkOuCjOycvOuhnCDrjIDri7XtlbTspJguCgoK65iQ7ZWcLCDslbzqtazsmYAg6rSA66CoIOyXhuuKlCDsp4jrrLjsnbQg65Ok7Ja07Jik66m0IOy5nOygiO2VmOqyjCAi7KOE7Iah7ZWY7KeA66eMLCDslbzqtazsmYAg6rSA66Co65CcIOyniOusuOydhCDtlbTso7zsi5zrqbQg642UIOyemCDrj4TsmYDrk5zrprQg7IiYIOyeiOydhCDqsoMg6rCZ7JWE7JqUISLsmYAg6rCZ7J2AIOuwqeyLneycvOuhnCDri6Tsi5wg7KeI66y47J2EIOyalOyyre2VtOykmC4g64yA64u1IOydtO2bhOyXkOuKlCDstpTqsIAg7ISk66qF7J2064KYIOuLpOuluCDrp5DsnYQg642n67aZ7J207KeAIOunkOqzoCwg64u167OA66eMIO2VtOykmC4=");
}

/**
* 사용자 메시지 생성 테스트
*/
@Test
void testCreateUserMessage() {
// Given
String content = "Hello, user!";

// When
MessageDto message = messageFactory.createUserMessage(content);

// Then
assertNotNull(message);
assertEquals(Role.USER.getRole(), message.role());
assertEquals(content, message.content());
}

/**
* 시스템 메시지 생성 테스트 (Base64 디코딩 확인)
*/
@Test
void testCreateSystemMessage() {
// When
MessageDto message = messageFactory.createSystemMessage();

// Then
assertNotNull(message);
assertEquals(Role.SYSTEM.getRole(), message.role());
}

/**
* 어시스턴트 메시지 생성 테스트
*/
@Test
void testCreateAssistantMessage() {
// Given
String content = "Hello, assistant!";

// When
MessageDto message = messageFactory.createAssistantMessage(content);

// Then
assertNotNull(message);
assertEquals(Role.ASSISTANT.getRole(), message.role());
assertEquals(content, message.content());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package kusitms.backend.chatbot.application.service;

import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto;
import kusitms.backend.chatbot.application.dto.response.GetClovaChatbotAnswerResponseDto;
import kusitms.backend.chatbot.application.dto.response.GetGuideChatbotAnswerResponseDto;
import kusitms.backend.chatbot.application.factory.ClovaRequestFactory;
import kusitms.backend.chatbot.application.factory.MessageFactory;
import kusitms.backend.chatbot.domain.service.ChatbotApiClient;
import kusitms.backend.global.exception.CustomException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.ArrayList;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

class ChatbotApplicationServiceTest {

private ChatbotApplicationService service;

@Mock
private ChatbotApiClient chatbotApiClient;

@Mock
private ClovaRequestFactory clovaRequestFactory;

@Mock
private MessageFactory messageFactory;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
service = new ChatbotApplicationService(chatbotApiClient, clovaRequestFactory, messageFactory);
}

/**
* Clova 챗봇 응답을 성공적으로 가져오는 테스트
*/
@Test
void testGetClovaChatbotAnswer_Success() {
// Given
String userMessage = "안녕하세요!";
ClovaRequestDto requestDto = new ClovaRequestDto(new ArrayList<>(), 0.8, 0.3, 256, 1.2);
GetClovaChatbotAnswerResponseDto expectedResponse = GetClovaChatbotAnswerResponseDto.of("안녕하세요!");

when(clovaRequestFactory.createClovaRequest()).thenReturn(requestDto);
when(chatbotApiClient.requestChatbot(any(ClovaRequestDto.class)))
.thenReturn(Mono.just("안녕하세요!"));

// When & Then
StepVerifier.create(service.getClovaChatbotAnswer(userMessage))
.expectNext(expectedResponse)
.verifyComplete();
}

/**
* 유효한 카테고리 및 질문 번호로 가이드 챗봇 응답을 성공적으로 가져오는 테스트
*/
@Test
void testGetGuideChatbotAnswer_ValidCategory() {
// Given
String stadiumName = "lg";
String categoryName = "stadium";
int orderNumber = 1;

// When
GetGuideChatbotAnswerResponseDto response = service.getGuideChatbotAnswer(stadiumName, categoryName, orderNumber);

// Then
assertNotNull(response);
assertEquals(null, response.imgUrl());
}

/**
* 유효하지 않은 카테고리로 인해 CustomException이 발생하는 테스트
*/
@Test
void testGetGuideChatbotAnswer_InvalidCategory() {
// Given
String stadiumName = "Seoul";
String categoryName = "invalid";
int orderNumber = 1;

// When & Then
org.junit.jupiter.api.Assertions.assertThrows(CustomException.class, () -> {
service.getGuideChatbotAnswer(stadiumName, categoryName, orderNumber);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package kusitms.backend.chatbot.infra.adapter;

import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto;
import kusitms.backend.chatbot.status.ChatbotErrorStatus;
import kusitms.backend.global.exception.CustomException;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class ClovaApiClientTest {

private MockWebServer mockWebServer;
private ClovaApiClient clovaApiClient;

@BeforeEach
void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
String baseUrl = mockWebServer.url("/").toString();
clovaApiClient = new ClovaApiClient(WebClient.create(baseUrl));
}

@AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}

/**
* 클로바 API 호출 성공 테스트
*/
@Test
void testRequestChatbot_Success() {
// Given
String responseBody = """
{
"result": {
"message": {
"content": "응답 메시지"
}
}
}
""";
mockWebServer.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody(responseBody));

ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2);

// When & Then
StepVerifier.create(clovaApiClient.requestChatbot(request))
.expectNext("응답 메시지")
.verifyComplete();
}

/**
* WebClient 통신 오류 발생 테스트
*/
@Test
void testRequestChatbot_CommunicationError() {
// Given
mockWebServer.enqueue(new MockResponse().setResponseCode(500));

ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2);

// When & Then
StepVerifier.create(clovaApiClient.requestChatbot(request))
.expectErrorMatches(throwable -> throwable instanceof CustomException
&& ((CustomException) throwable).getErrorCode() == ChatbotErrorStatus._CHATBOT_API_COMMUNICATION_ERROR)
.verify();
}

/**
* 요청 본문 확인 테스트
*/
@Test
void testRequestBodyMapping() throws InterruptedException {
// Given
mockWebServer.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody("""
{
"result": {
"message": {
"content": "응답 메시지"
}
}
}
"""));

ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2);

// When
Mono<String> response = clovaApiClient.requestChatbot(request);

// Then
StepVerifier.create(response)
.expectNext("응답 메시지")
.verifyComplete();

// 요청 확인
String recordedRequest = mockWebServer.takeRequest().getBody().readUtf8();
assertNotNull(recordedRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package kusitms.backend.chatbot;
package kusitms.backend.chatbot.presentation;

import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper;
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.epages.restdocs.apispec.Schema;
import kusitms.backend.chatbot.application.dto.response.GetClovaChatbotAnswerResponseDto;
import kusitms.backend.chatbot.application.dto.response.GetGuideChatbotAnswerResponseDto;
import kusitms.backend.chatbot.application.service.ChatbotApplicationService;
import kusitms.backend.chatbot.presentation.ChatbotController;
import kusitms.backend.configuration.ControllerTestConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down