ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2. 블로그 프로젝트
    프로그래밍 2022. 7. 5. 15:49
    반응형

    updatePost, deletePost 를 만들고 댓글 엔티티인 Reply, reply의 CRUD 코드를 작성하였다.

     

    PostService

    @AllArgsConstructor
    @Service
    public class PostService {
        private PostRepository postRepository;
    
        @Transactional
        public void createPost(String title, String content) {
            Post newPost = new Post(title, content);
            postRepository.save(newPost);
        }
    
        @Transactional
        public void updatePost(String title, String content, Long id) {
            Post findPost = postRepository.findById(id).orElseThrow(() -> new IllegalArgumentException());
    
            findPost.update(title, content);
        }
    
        @Transactional
        public void deletePost(Long id) {
            postRepository.deleteById(id);
        }
    }

    JPA에서는 update 메소드가 따로 없다. 이는 엔티티 자체에서 update 메소드를 제공하여 그 안에서 값을 바꾸면 update가 된다. 이 때 @Transactional은 필수다. 이 방식의 장점은 update를 위한 쿼리를 작성할 필요가 없고 엔티티 자체의 값을 바꾸므로 좀 더 객체지향적으로 코드를 작성할 수 있다는 것이다.

     

    Reply

    @Getter
    @NoArgsConstructor
    @Entity
    public class Reply {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "REPLY_ID")
        private Long id;
    
        @Column(name = "WRITER")
        private String writer;
    
        @Column(name = "CONTENT")
        private String content;
    
        @ManyToOne
        @JoinColumn(name = "POST_ID")
        private Post post;
    
        public Reply(String writer, String content, Post post) {
            this.writer = writer;
            this.content = content;
            this.post = post;
        }
    
        public void update(String writer, String content) {
            this.writer = writer;
            this.content = content;
        }
    }

    여러 댓글은 하나의 게시물에 등록된다. 따라서 Many To One 관계이며, @ManyToOne 을 이용하여 나타내주었다. Post 엔티티에 @OneToMany를 통해 Reply들의 List로써 관리하면 cascade를 이용할 수 있어서 더 편리하지만, 내 블로그 프로젝트의 경우 관계가 복잡하지 않기 때문에 ManyToOne만 사용하고, 만약 댓글이 달려있는 게시물을 통째로 지우고 싶을 때는 댓글들을 먼저 다 지운 뒤 게시물을 지우는 방식으로 구현할 것이다. ManyToOne과 OneToMany를 이용하면 양방향 관계를 만들 수 있다고 하지만 이는 사실 두개의 단방향이라는 사실을 책에서 본적이 있다. 또한 데이터베이스에서도 마찬가지로 참조하는 테이블의 외래키를 가질 뿐이지 참조당하는 테이블이 자신을 참조하는 테이블에 대한 리스트를 가지고 있지 않다. 그러므로 ManyToOne만 사용할 것이다.

     

    ReplyServiceTests

    @SpringBootTest
    public class ReplyServiceTests {
        @Autowired
        ReplyService replyService;
        @Autowired
        ReplyRepository replyRepository;
        @Autowired
        PostRepository postRepository;
    
        @AfterEach
        void afterEach() {
            replyRepository.deleteAll();
            postRepository.deleteAll();
        }
    
        @Test
        void createReplyTest() {
            Post newPost = new Post("테스트1", "테스트 내용1");
            postRepository.save(newPost);
    
            // test
            replyService.createReply("작성자1", "내용1", newPost.getId());
    
            assertThat(replyRepository.findAll().get(0).getContent()).isEqualTo("내용1");
            assertThat(replyRepository.findAll().get(0).getPost()).isNotNull();
        }
    
        @Test
        void updateReplyTest() {
            Post newPost = new Post("테스트1", "테스트 내용1");
            postRepository.save(newPost);
            Reply newReply = new Reply("작성자1", "내용1", newPost);
            replyRepository.save(newReply);
    
            // test
            replyService.updateReply("작성자2", "내용2", newReply.getId());
    
            Reply testReply = replyRepository.findAll().get(0);
            assertThat(testReply.getWriter()).isEqualTo("작성자2");
            assertThat(testReply.getPost()).isNotNull();
        }
    
        @Test
        void deleteReplyTest() {
            Post newPost = new Post("테스트1", "테스트 내용1");
            postRepository.save(newPost);
            Reply newReply = new Reply("작성자1", "내용1", newPost);
            replyRepository.save(newReply);
    
            // test
            replyService.deleteReply(newReply.getId());
    
            assertThat(replyRepository.count()).isEqualTo(0L);
    
        }
    }

    예전에 게시판을 만든 적이 있었는데, 사실 그 때는 테스트 코드가 중요하다는 것만 알았지 왜 그런지 느껴본 적이 없었다. 하지만 이번에 API를 만들어보면서 알 수 있었다. 예전에는 서버를 계속 구동시키고, 실제 데이터가 렌더링되는 것을 보면서 api를 테스트했었는데, 이는 시간이 오래 걸리고 불편했다. 하지만 이번에는 화면단을 아예 만지지 않고 api를 테스트할 수 있었다. 테스트 코드는 서버 안에서 api를 테스트할 수 있었고, 화면을 렌더링하며 체크할 필요가 없었다. 이런 소규모 프로젝트에서도 편리함을 느끼는데, 대규모 프로젝트라면 테스트 코드는 필수라는 생각이 들었고, 그래서 서비스 기업들이 테스트 코드를 중요하게 여기는구나 생각하게 되었다. 

     

    현재까지 만든 API는 다음과 같다.

    GET /
    GET /create
    POST /api/post
    PUT /api/post/{id}
    DELETE /api/post/{id}
    POST /api/reply/{postID}
    PUT /api/reply/{replyID}
    DELETE /api/reply/{replyID}

     

    또한 MockMvc를 이용한 테스트 코드도 작성하였다.

    @SpringBootTest
    @AutoConfigureMockMvc
    public class PostApiControllerTests {
        @Autowired
        MockMvc mockMvc;
        @Autowired
        ObjectMapper objectMapper;
        @Autowired
        PostRepository postRepository;
    
        @AfterEach
        void afterEach() {
            postRepository.deleteAll();
        }
    
        @Test
        void createPostTest() throws Exception {
            Map<String, String> input = new HashMap<>();
            input.put("title", "제목1");
            input.put("content", "내용1");
    
            this.mockMvc.perform(post("/api/post")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(input)))
                    .andExpect(status().isOk())
                    .andDo(print());
        }
    
        @Test
        void updatePostTest() throws Exception {
            Post newPost = new Post("제목1", "내용1");
            postRepository.save(newPost);
            Map<String, String> input = new HashMap<>();
            input.put("title", "제목2");
            input.put("content", "내용2");
            String url = "/api/post/" + newPost.getId();
    
            this.mockMvc.perform(put(url)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(input)))
                    .andExpect(status().isOk())
                    .andDo(print());
        }
    
        @Test
        void deletePostTest() throws Exception {
            Post newPost = new Post("제목1", "내용1");
            postRepository.save(newPost);
            String url = "/api/post/" + newPost.getId();
    
            this.mockMvc.perform(delete(url))
                    .andExpect(status().isOk())
                    .andDo(print());
        }
    }

    이 테스트 코드는 service 클래스에 대한 테스트 코드가 아닌 API에 대한 테스트 코드이다. mockmvc를 사용했으므로 서버를 구동하지 않고 api 통신에 대한 테스트를 수행할 수 있다. 중간에 에러가 계속 났었는데, api에서 데이터를 받을 때, String으로 제목과 내용을 직접 받기로 했는데 각각에 모두 @RequestBody를 써버려서 생긴 문제였다. RequestBody는 어떤 객체를 인자로 넘기면 그 안에 일치하는 필드들을 채워주는 것인데, 나는 이 사실을 잊고 RestController로 통신하면 반드시 사용해야 한다고만 기억이 나서 이런 오류를 범하게 되었다. 무작정 외우고 사용하면 반드시 문제를 마주하게 된다고 생각했고, 항상 어떤 메소드든 어노테이션이든 확실히 알고 넘어가야겠다고 생각했다.

    반응형

    '프로그래밍' 카테고리의 다른 글

    5. 블로그 프로젝트  (0) 2022.07.17
    4. 블로그 프로젝트  (0) 2022.07.09
    3. 블로그 프로젝트  (0) 2022.07.06
    1. 블로그 프로젝트  (0) 2022.07.02
    IntelliJ 실수 되돌리기  (0) 2021.11.13
Designed by Tistory.