다음으로 작성할 테스트 코드는 서비스 테스트 코드이다. [[4-0. 리포지터리 테스트 코드|이전 리포지터리 테스트 코드]]와 유사하게 테스트 코드를 먼저 작성한 다음 실제 서비스 코드를 작성한다. ## `config` 작성 엔티티와 DTO 변환은 컨트롤러에서 할 생각이라 서비스는 엔티티만 다룬다. 따라서 따로 Config를 작성할 필요 없이 [[4-0. 리포지터리 테스트 코드#`PromptTemplateTestConfig`|이전 테스트에서 작성한 Config]]를 그대로 사용하면 된다. ![[4-0. 리포지터리 테스트 코드#`PromptTemplateTestConfig`]] ## `PromptTemplateServiceTest` 작성 ### 클래스 생성 ```java @SpringBootTest @TestPropertySource(locations = "classpath:test.properties") @Import(PromptTemplateTestConfig.class) public class PromptTemplateServiceTest { } ``` - 테스트에서 많이 사용하는 `@Transactional`은 사용하지 않는다. H2의 휘발성 때문에 설정하지 않아도 테스트 결과가 다 지워지기 때문이다. ### 의존성 주입 ```java @SpringBootTest @TestPropertySource(locations = "classpath:test.properties") @Import(PromptTemplateTestConfig.class) public class PromptTemplateServiceTest { @Autowired private PromptTemplateService service; @Autowired private SingleArgAssertor<PromptTemplate> assertNotNull; @Autowired private DoubleArgAssertor<PromptTemplate, PromptTemplate> assertEquality; } ``` ### 메서드 작성 #### `service` 주입 여부 확인 ```java @Test public void testServiceExistence(){ assertNotNull(service); } ``` #### CRUD 테스트 ```java @Test public void testCreate(){ //Given PromptTemplate promptTemplate = PromptTemplate.builder() .id("test_instance") .content("Say ${word} twice!") .modifiedTime(LocalDateTime.now()) .build(); //When final PromptTemplate savedTemplate = service.create(promptTemplate); //Then notNullAssertor.assertify(savedTemplate); equalityAssertor.assertify(promptTemplate, savedTemplate); } ``` ```java @Test public void testReadById(){ //Given PromptTemplate promptTemplate = PromptTemplate.builder() .id("test_instance2") .content("What is ${word}?") .modifiedTime(LocalDateTime.now()) .build(); //When service.create(promptTemplate); final PromptTemplate readTemplate = service.readById("test_instance2"); //Then notNullAssertor.assertify(readTemplate); equalityAssertor.assertify(promptTemplate, readTemplate); assertThrows( EntityNotFoundException.class, ()->service.readById("I am really Id. Trust me!") ); } ``` - 이번 예시 프로젝트에서 예외 처리는 `@ControllerAdvice`에서 전부 수행한다. 따라서 `PromptTemplateService`는 예외 처리를 하지 않고 엔티티가 없으면 `EntityNotFoundException`를 throw 하게 할 생각이다. ```java @Test public void testUpdateById(){ //Given PromptTemplate promptTemplate = PromptTemplate.builder() .id("test_instance3") .content("") .modifiedTime(LocalDateTime.now()) .build(); PromptTemplate modifiedTemplate = PromptTemplate.builder() .content("What is the most biggest ${word}?") .modifiedTime(LocalDateTime.now()) .build(); //When service.create(promptTemplate); PromptTemplate readTemplate = service.readById("test_instance3"); final PromptTemplate updatedTemplate = service.updateById("test_instance3", modifiedTemplate); modifiedTemplate.setId("test_instance3"); //Then notNullAssertor.assertify(updatedTemplate); equalityAssertor.assertify(updatedTemplate, modifiedTemplate); assertThrows( EntityNotFoundException.class, ()->service.updateById("I am really Id. Trust me!", modifiedTemplate) ); } ``` ```java @Test public void testDeleteById(){ //Given PromptTemplate promptTemplate = PromptTemplate.builder() .id("test_instance4") .content("haha") .modifiedTime(LocalDateTime.now()) .build(); //When service.create(promptTemplate); service.deleteById("test_instance4"); //Then assertThrows( EntityNotFoundException.class, ()->service.readById("test_instance4") ); } ``` #### 템플릿을 통한 프롬프트 생성 기능 테스트 ChatGPT 프록시 서버의 [[0. ChatGPT 프록시 서버 소개 및 주요 서비스#주요 서비스|주요 서비스]] 중 **프롬프트 템플릿을 통한 프롬프트 생성**은 `PromptTemplateService`에서 구현되어야 한다. 그러니 템플릿을 통한 프롬프트 생성 서비스를 테스트하는 코드를 작성해보자. ```java @Test public void testGeneratePromptByTemplate() throws IOException, TemplateException { //Given String templateContent = Files.readString(Paths.get("src/test/resources/templates/example1.template")); String reference = Files.readString(Paths.get("src/test/resources/templates/example1.reference")); String input = Files.readString(Paths.get("src/test/resources/templates/example1.input")); String answer = Files.readString(Paths.get("src/test/resources/templates/example1.answer")); Map<String, String> map = new HashMap<>(); map.put("reference", reference); map.put("input", input); PromptTemplate promptTemplate = PromptTemplate.builder() .id("test_instance5") .content(templateContent) .modifiedTime(LocalDateTime.now()) .build(); //When service.create(promptTemplate); String prompt = service.generatePromptByTemplate("test_instance5", map); //Then assertEquals(prompt, answer); } ``` - 프롬프트 템플릿과 키워드들의 길이가 너무 길어서 따로 파일로 저장한 다음 불러오도록 설정했다. 각 파일의 내용은 [[4-1. 서비스 테스트 코드#프롬프트 템플릿 예시 파일|맨 마지막]]에 적어두었다. 다만, 파일의 정확한 내용은 중요하지 않으니 굳이 보지 않아도 된다. - 중요한 건 `generatePromptByTemplate` 메서드가 템플릿과 그 템플릿에 들어갈 키워드를 받아 템플릿 안에 집어 넣은 뒤 반환한다는 것이다. - 템플릿에서 키워드는 `${키워드}` 의 형식으로 표시된다. 그 외의 반복문을 위한 표시 방법 같은 경우는 [여기](https://freemarker.apache.org/docs/dgui_template_exp.html)를 참조하면 확인 가능하다. ## [[6. Service 구현|서비스 코드 작성]] ![[6. Service 구현#클래스 생성 및 의존성 주입]] ![[6. Service 구현#CRUD 메서드]] ![[6. Service 구현#템플릿을 통한 프롬프트 생성 메서드]] ## 테스트 결과 확인 ![[Pasted image 20231213185602.png]] ## 프롬프트 템플릿 예시 파일([[4-1. 서비스 테스트 코드#프롬프트 템플릿을 통한 프롬프트 생성|돌아가기]]) ### example1.template ```markdown # Order I want you to act like a web server developer. I will give you the code I wrote under the "Reference". And I'll give you a code that includes "[blank]" under the "Input". Then you can refer to the "Reference" code and fill in "[blank]" according to the following constraints. # Constraints - only reply with the output inside one unique code block, and nothing else - do not write explanations # Reference ${reference} # Input ${input} # Output type [output code] ``` ### example1.reference ```java import lombok.*; @Getter @Setter @Entity @Builder @EntityListeners(AuditingEntityListener.class) public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="post_id", updatable = false) private int id; private String content; private String title; @CreatedDate @Column(name = "posted_date") private LocalDateTime postedDate; @ManyToOne @OnDelete(action = OnDeleteAction.CASCADE) @JoinColumn(name = "user_id") private Users user; } ``` ### example1.input ```java import lombok.*; [blank] public class PostRequestDTO { [blank] public [blank] toEntity([blank]){ [blank] } } ``` ### example1.answer ```markdown # Order I want you to act like a web server developer. I will give you the code I wrote under the "Reference". And I'll give you a code that includes "[blank]" under the "Input". Then you can refer to the "Reference" code and fill in "[blank]" according to the following constraints. # Constraints - only reply with the output inside one unique code block, and nothing else - do not write explanations # Reference import lombok.*; @Getter @Setter @Entity @Builder @EntityListeners(AuditingEntityListener.class) public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="post_id", updatable = false) private int id; private String content; private String title; @CreatedDate @Column(name = "posted_date") private LocalDateTime postedDate; @ManyToOne @OnDelete(action = OnDeleteAction.CASCADE) @JoinColumn(name = "user_id") private Users user; } # Input import lombok.*; [blank] public class PostRequestDTO { [blank] public [blank] toEntity([blank]){ [blank] } } # Output type [output code] ```