Backend/spring boot

spring boot 사이드 프로젝트 : service 테스트

attlet 2023. 7. 12. 14:26

 

 

이번에는 service 레이어를 테스트했던 방법을 작성해봤다.

 

 

 

 

1. Entity


@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "board") //테이블과 매핑
public class Board extends BaseEntity {

    @Column(nullable = false)
    private String type;
    @Column(nullable = false)
    private String title;
    @Column(nullable = false)
    private String text;
    @Column(nullable = false)
    private String proceed_method; //진행 방식
    @Column(nullable = false)
    private LocalDateTime period; //예상 기간
    @Column
    private int comment_cnt;  //댓글 개수

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User author;               //작성자 정보 접근

    @OneToMany(mappedBy = "board")
    @ToString.Exclude
    private List<ApplicantBoardRelation> applicantBoardRelationList;     //게시글에 지원서를 제출한 유저에 대한 정보

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
    @ToString.Exclude
    private List<Comment> commentList;    //게시글에 작성된 댓글들

    @OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @ToString.Exclude
    private List<TagBoardRelation> tagBoardRelationList;   //태그

    @OneToMany(mappedBy = "clipedBoard", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
    @ToString.Exclude
    private List<ClipBoardRelation> clipBoardRelationList; //관심 클립으로 지정한 유저들

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @ToString.Exclude
    private List<RoleBoardRelation> roleBoardRelationList;   //직군 관련 태그

    public void updatePost(BoardDto boardDto){
        this.type = boardDto.getType();
        this.title = boardDto.getTitle();
        this.text = boardDto.getText();
        this.proceed_method = boardDto.getProceed_method();
        this.period = boardDto.getPeriod();
        this.tagBoardRelationList = boardDto.getTagBoardRelationList();
        this.roleBoardRelationList = boardDto.getRoleBoardRelationList();
    }
}

 

먼저 엔티티의 모습이다. 게시글을 보여줄 때 필요한 정보들을 담고 있다.

 

 

 

 

 

 

 

 

 

2. Service


 

 

 

게시글 관련 로직들이 구현된 BoardService이다. 그 중 getBoard는 id를 기반으로 board 엔티티를 찾는 메서드이다. 이 메서드를 테스트하는게 목적이다.

 

 

 

 

3. Test


 

스프링 부트에서 테스트 방식은 다양하다. 크게 보면 통합 테스트와 단위 테스트로 나뉜다.

 

 

Service에서 작성한 로직들이 잘 작동하는 지 보기 위해서 service만을 테스트하는 것은 단위 테스트라 할 수 있다. 이렇게 service만을 테스트하기 위해서는 외부로 부터의 요인들을 배제할 필요가 있다.

 

 

이제 본격적으로 board의 service 에 대한 테스트를 작성해본다. 실제로 애플리케이션을 서버에 올리고 운영할 때는 스프링 빈으로 등록된 repository를 주입받아 사용하지만, 단위 테스트에서는 repository는 외부 요인이라 볼 수 있다. 오로지 우리는 service만 테스트하고 싶기에, 이를 가짜 객체(mock)으로 대체해서 테스트 코드에 작성한다.

 

 

 

- ExtendWith 어노테이션을 통해 mockito에 있는 mock객체를 사용할 수 있다. mockito는 가짜 객체를 생성해주는 테스트 용 프레임워크이다. 

 

- mock 어노테이션을 통해 가짜 객체를 생성한다. service에 사용된 repository들을 가져온다.

 

- InjectMocks를 이용해 생성한 mock객체들을 주입받을 수 있다.

 

 

 

 

 

 

 

이제 BoardService의 메서드 중 하나인 getBoard를 테스트 해보자. getBoard는 게시글 하나의 id를 매개변수로 받아 하나의 게시글을 찾아내는 메서드이다. 테스트는 given - when - then 의 구조를 통해 진행한다.

 

 

 

**Given - when - then 패턴이란?


 

- given : 테스트를 실행하기 전 필요한 환경을 세팅하는 단계. 위에서 getBoard는 board객체 하나를 DB에서 찾아내는 것이므로 board객체가 DB에 있어야 한다.  

 

 

 

- when : 테스트에서 보고자 하는 주요 내용을 실행하는 단계. 테스트 하고자 하는 getBoard를 사용하는 부분이다. 

 

 

 

- then : 원하는 리턴 결과를 검증하는 단계. 리턴된 결과와 실제로 반환되어야 하는 값을 assert같은 단정문을 이용해 검증한다.

 

 

 

 

 

위의 코드를 하나하나 파보면, 중요한 코드가 있다.

 

 

given, willReturn메서드는  BDDMockito 라이브러리에 있는 메서드들이다. BDD와 관련된 내용은 다른 게시글에서 작성할 것이다.

 

 

 

given을 통해 mock 객체가 호출되었을 때의 상황을 가정하고, willReturn을 통해 객체가 동작한 후 기댓값을 설정한다. getBoard메서드에 나타나는 외부 요인으로는 boardRepository의 findById메서드가 있다. 그래서 이렇게 mock을 이용해 미리 결과를 정해주는 것이다.

 

 

 

이렇게 하면 이 테스트에서는 boardRepositroy의 save가 어떻게 되어있던 board 객체를 반환하게 되도록 정하는 것이다.

 

 

findById의 반환 타입은 Optional이기에 이에 맞춰 willReturn에 값을 넣어야한다. ofNullable을 통해 게시글이 존재하지 않는 null이라해도 허용하며, null이면 Optional.empty()를 리턴한다.

 

 

이렇게 하는 이유는 우리가 테스트하고자 하는 것은 오로지 service지 repository가 아니기 때문이다. repository를 실제로 가져와 사용하면 repository의 상태도 고려해야만 하기에 단위 테스트의 범위를 벗어난다.

 

 

 

테스트 결과 잘 통과하는 것을 볼 수 있다.