이전 글 까지 읽고 와야 이번 글을 이해할 수 있다.
간단한 CRUD 환경을 예시코드로 남깁니다.
일단 휘갈겨진.. 야매의 상황이라 이후 코드를 조금 더 정갈하게 올리겠습니다.
UserService
package com.test.admin.service;
import com.test.admin.entity.User;
import java.util.List;
public interface UserService {
String register(User newUser);
User update(User newUser);
void deleteById(String id);
List<User> findAll();
List<User> findByName(String name);
}
유저 생성, 유저 업데이트, 유저 삭제, 유저 모두 찾기, 유저 이름으로 찾기
UserServiceLogic
package com.test.admin.service.logic;
import com.test.admin.entity.User;
import com.test.admin.repository.UserRepository;
import com.test.admin.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserServiceLogic implements UserService {
// DI 적용 방법 1
// UserRepository를 구현한 구현클래스 UserRepositoryLogic을 찾아서 userRepositry 변수에 주입함.
private final UserRepository userRepository; // @RequiredArgsConstructor와 세트
@Override
public String register(User newUser) {
return this.userRepository.save(newUser).getId();
}
@Override
public List<User> findByName(String name) {
return this.userRepository.findByName(name);
}
@Override
public void deleteById(String id) {
userRepository.deleteById(id);
}
// PK인 id 그대로 두고, 나머지 값 수정하면 update
@Override
public User update(User newUser) {
User user = userRepository.findById(newUser.getId()).get();
return userRepository.save(new User(user.getId(), newUser.getName(), newUser.getEmail()));
}
@Override
public List<User> findAll() {
return this.userRepository.findAll();
}
}
참고로 Builder를 적용하지 않아서 그냥 User 객체를 new로 생성한다.
동작은 하지만 이 부분도 수정해야한다.
UserRepository
package com.test.admin.repository;
import com.test.admin.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
// JpaRepository<엔티티명, PK 타입>
@Repository
public interface UserRepository extends JpaRepository<User, String> {
//비워있어도 잘 작동한다. 기본적인 CRUD는 자동으로 추가된다.
// findBy뒤에 컬럼명을 붙여주면 이를 이용한 검색이 가능하다.
public List<User> findByName(String name);
}
PK로 설정해놓은 Id에 대한 기본적인 로직은 모두 자동구현되므로,
findByName을 통해, 이름으로 찾는 것만 추가로 등록했다.
User
package com.test.admin.entity;
import com.google.gson.Gson;
import lombok.Builder;
import lombok.Getter;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.UUID;
// @Entity가 붙은 클래스는 JPA가 관리하는 클래스이고, 테이블과 매핑할 테이블에 해당 어노테이션을 붙인다.
@Getter
@Entity
public class User {
// @ID 어노테이션은 Primary key를 뜻함.
@Id
String id;
String name;
String email;
public User() {
this.id = UUID.randomUUID().toString();
}
public User(String name, String email) {
this();
this.name = name;
this.email = email;
}
public User(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public static User sample() {
return new User("Park", "Park@gmail.com");
}
}
UserController
package com.test.admin.controller;
import com.test.admin.entity.User;
import com.test.admin.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Users", description = "유저 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
// json 데이터가 body로 넘어올텐데, User 객체로 받아오기 위해선 @RequestBody를 이용한다
@Operation(summary = "Create User")
@PostMapping("")
public String register(@RequestBody User newUser) {
return userService.register(newUser);
}
@Operation(summary = "Find all User")
@GetMapping("")
public List<User> findAll() {
return userService.findAll();
}
@Operation(summary = "Delete User")
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id) {
userService.deleteById(id);
}
@Operation(summary = "Find user by Name")
@GetMapping("/name/{name}")
public List<User> find(@PathVariable String name) {
return userService.findByName(name);
}
// 참고로,, PathVariable을 써도 상관없음. 구현하기 나름.
@Operation(summary = "Update User")
@PutMapping("")
public User update(@RequestBody User newUser) {
return userService.update(newUser);
}
}
이제 Controller 기반 단위테스트를 진행한다.
이전 글에서는 Rest client인 insomnia를 소개했는데, 단위테스트로 코드를 작성해놓는 것이 좋은 습관이다.
저번에는 Test 코드를 만들 때, 패키지부터 수작업으로 직접 만들어줬는데,
사실 테스트하려는 클래스에 커서를 두고 윈도우(alt+enter) 맥(option+enter) 누르면 create Test가 뜬다.
setUp, tearDown 까지 생성시켜줄 수 있다.
기본 뼈대 코드가 작성된다.
package com.test.admin.controller;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class UserControllerTest {
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void register() {
}
@Test
void findAll() {
}
@Test
void deleteById() {
}
@Test
void find() {
}
@Test
void update() {
}
}
간단하게 register를 테스트해보자.
package com.test.admin.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.admin.entity.User;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
// Controller Test 때, MockMvc 사용해야해요
@Autowired
private MockMvc mockMvc;
// User 객체를 json 형태의 String으로 바꿔주는데 ObjectMapper가 사용됨
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void register() throws Exception {
User sample = User.sample();
String content = objectMapper.writeValueAsString(sample);
mockMvc.perform(post("/users")
.content(content)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(sample.getId()))
.andDo(print()); // print로 콘솔에 통신 과정과 결과를 찍어볼 수 있다.
}
@Test
void findAll() {
}
@Test
void deleteById() {
}
@Test
void find() {
}
@Test
void update() {
}
}
post(), status(), content(), print()는 static import가 되어야 한다.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
테스트가 성공적으로 진행된다. ( 테스트 할 함수 옆에 재생 버튼을 누르면 된다)
데이터도 새로 추가된 것을 확인할 수 있다.
대신 매번 이렇게 데이터가 추가될 수 있으므로,
BeforeEach나 AfterEach를 이용해서, 각 테스트 함수 전 후에 데이터를 추가 삭제하여 원상복귀 시켜놓는 것이 중요하겠다.
RestClient보다 단위테스트가 중요한 이유는, 이전에도 언급했지만
코드가 추가되었을 때도 테스트가 매번 진행되므로 전체 코드가 잘 진행되는지 여부를 아주 견고하게 확인할 수 있다.
즉, 코드의 안정성이 보장된다.
참고로,,
서버를 실행시키거나, 배포를 하려고 하면 Test가 자동으로 진행된다.
다음에는 더 코드를 정갈하게 정리해서 다시 글을 작성해보겠습니당..
'[SpringBoot]' 카테고리의 다른 글
JPA - OneToMany (0) | 2022.12.20 |
---|---|
JPA 설명 -1 / JDBC (0) | 2022.12.15 |
SpringBoot Swagger 연동 (0) | 2022.12.13 |
SpringBoot NginX 연동 (MAC local) (0) | 2022.12.13 |
WAS WebServer (Feat. SpringBoot, NginX, Node.js) (0) | 2022.12.13 |