본문 바로가기
프로젝트/팀 프로젝트) MEMO:RE

Test ) 간단한 단위 테스트 추가

by 휴일이 2024. 2. 22.

 

 

기존 MEMO:RE 테스트 코드는

무거운 통합 테스트 코드가 포함되어 있어서 빌드 속도가 느렸당 ㅡㅅㅡ

최근 실무에서는 무거운 통합테스트보다는 여러가지의 단위 테스트를 하는 것이 낫다는 정보를 입수해

리팩토링 과정에서 간단한 단위 테스트 코드를 추가해보았다.

 

 

MemoTest

public class MemoTest {

    private Memo memo;
    private Long ownerId;
    private Long otherUserId;

    @BeforeEach
    void setUp() {
        ownerId = 1L;
        otherUserId = 2L;

        memo = mock(Memo.class);
        doNothing().when(memo).checkAuthorizedUser(ownerId);

        doThrow(new BadRequestException(Exception.MEMO_NOT_FOUND))
                .when(memo).checkAuthorizedUser(otherUserId);
    }

    private Memo createMemo() {
        return Memo.builder()
                .id(1L)
                .keyword("초기 키워드")
                .content("초기 내용")
                .build();
    }

    @Test
    @DisplayName("메모 제목, 내용 변경")
    void updateMemoKeywordAndContent() {
        String keyword = "키워드 변경";
        String content = "내용 변경";

        Memo memo = createMemo();
        Memo sameMemo = createMemo();

        memo.updateMemo(keyword, content);

        assertNotEquals(memo.getKeyword(), sameMemo.getKeyword());
        assertNotEquals(memo.getContent(), sameMemo.getContent());

        assertEquals(memo.getKeyword(), keyword);
        assertEquals(memo.getContent(), content);
    }

    @Test
    @DisplayName("메모 내용만 변경")
    void updateMemoContent() {
        String content = "내용 변경";

        Memo memo = createMemo();
        Memo sameMemo = createMemo();

        memo.writeOnlyContent(content);

        assertEquals(memo.getKeyword(), sameMemo.getKeyword());

        assertNotEquals(memo.getContent(), sameMemo.getContent());
    }
}

 

해당 테스트에서는 JDBC 를 사용해야하는 코드가 없고

DI 가 필요한 객체도 사용하지 않기 때문에

굳이 스프링 서버를 올리지 않아도 테스트가 가능하다.

 

@SpringBootTest

그래서 우리가 익히 알고 있는 이 애노테이션이 없어도 된다 ^0^/

 

 

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
public @interface SpringBootTest {
    @AliasFor("properties")
    String[] value() default {};

    @AliasFor("value")
    String[] properties() default {};

    String[] args() default {};

    Class<?>[] classes() default {};

    WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;

    UseMainMethod useMainMethod() default SpringBootTest.UseMainMethod.NEVER;

    public static enum UseMainMethod {
        ALWAYS,
        NEVER,
        WHEN_AVAILABLE;

        private UseMainMethod() {
        }
    }

    public static enum WebEnvironment {
        MOCK(false),
        RANDOM_PORT(true),
        DEFINED_PORT(true),
        NONE(false);

        private final boolean embedded;

        private WebEnvironment(boolean embedded) {
            this.embedded = embedded;
        }

        public boolean isEmbedded() {
            return this.embedded;
        }
    }
}

 

 

참고로 @SpringBootTest 애노테이션 안으로 들어가보면 WAS 서버를 올리는 코드가 있다.

JDBC 등을 사용하거나 빈으로 등록한 객체를 사용해서 테스트를 해야한다면

해당 애노테이션을 붙이지 않으면 동작하지 않음. ㅡㅅㅡ!

 

 

 

Memo

@Getter
@Entity
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Memo {

    @Id @GeneratedValue
    private Long id;
    private String keyword;
    private String content;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public void updateMemo(String keyword, String content) {
        this.keyword = keyword;
        this.content = content;
    }

    public void writeOnlyContent(String content) {
        this.content = content;
    }

    // 보안을 위해 메모가 없다는 메시지를 띄운다.
    public void checkAuthorizedUser(Long requesterId) {
        if(!user.getId().equals(requesterId)){
            throw new BadRequestException(Exception.MEMO_NOT_FOUND);
        }
    }
}

 

메모 엔티티에는 간단한

 

1. 메모 전체를 업데이트하는 메서드와

2. 메모 내용만 쓰는 메서드

3. 그리고 요청 유저와 메모를 가진 유저의 id 가 다를 때 에러를 발생시키는 메서드

 

세 가지가 있다.

 

 

 

다시 테스트 코드를 확인해보쟈!

(슝슝)

public class MemoTest {

    private Memo memo;
    private Long ownerId;
    private Long otherUserId;

    @BeforeEach
    void setUp() {
        ownerId = 1L;
        otherUserId = 2L;

        memo = mock(Memo.class);
        doNothing().when(memo).checkAuthorizedUser(ownerId);

        doThrow(new BadRequestException(Exception.MEMO_NOT_FOUND))
                .when(memo).checkAuthorizedUser(otherUserId);
    }

    private Memo createMemo() {
        return Memo.builder()
                .id(1L)
                .keyword("초기 키워드")
                .content("초기 내용")
                .build();
    }

    @Test
    @DisplayName("메모 제목, 내용 변경")
    void updateMemoKeywordAndContent() {
        String keyword = "키워드 변경";
        String content = "내용 변경";

        Memo memo = createMemo();
        Memo sameMemo = createMemo();

        memo.updateMemo(keyword, content);

        assertNotEquals(memo.getKeyword(), sameMemo.getKeyword());
        assertNotEquals(memo.getContent(), sameMemo.getContent());

        assertEquals(memo.getKeyword(), keyword);
        assertEquals(memo.getContent(), content);
    }

    @Test
    @DisplayName("메모 내용만 변경")
    void updateMemoContent() {
        String content = "내용 변경";

        Memo memo = createMemo();
        Memo sameMemo = createMemo();

        memo.writeOnlyContent(content);

        assertEquals(memo.getKeyword(), sameMemo.getKeyword());

        assertNotEquals(memo.getContent(), sameMemo.getContent());
    }
}

 

 

    @BeforeEach
    void setUp() {
        ownerId = 1L;
        otherUserId = 2L;

        memo = mock(Memo.class);
        doNothing().when(memo).checkAuthorizedUser(ownerId);

        doThrow(new BadRequestException(Exception.MEMO_NOT_FOUND))
                .when(memo).checkAuthorizedUser(otherUserId);
    }

 

@BeforeEach 에서 우리만의 룰을 정한다.

memo 객체는 mock 객체(더미 객체라고 표현하는 게 맞을까?) 로 두고

memo 를 가진 유저 아이디는 반드시 ownerId 값 이라고 약속한다.

그래서 우리는 otherUserId 값이 memo 에 들어갈 경우, 해당 Exception 을 던지기로 약속을 하는 것것것.

 

 

 

그러면 실제 테스트 코드를 확인해보자

지금 아주 졸리니까 하나의 코드만 확인해보겠당.

 

 

참고로 처음 memo 객체는

    private Memo createMemo() {
        return Memo.builder()
                .id(1L)
                .keyword("초기 키워드")
                .content("초기 내용")
                .build();
    }

 

 

이런 값을 가지고 있다.

 

 

    @Test
    @DisplayName("메모 제목, 내용 변경")
    void updateMemoKeywordAndContent() {
        String keyword = "키워드 변경";
        String content = "내용 변경";

        Memo memo = createMemo();
        Memo sameMemo = createMemo();

        memo.updateMemo(keyword, content);

        assertNotEquals(memo.getKeyword(), sameMemo.getKeyword());
        assertNotEquals(memo.getContent(), sameMemo.getContent());

        assertEquals(memo.getKeyword(), keyword);
        assertEquals(memo.getContent(), content);
    }

 

 

두 메모를 생성하고

하나의 메모에 키워드와 내용을 변경한 뒤

실제로 키워드와 내용이 변경됐는지 확인해보는 코드를 작성했다.

 

 

 

 

결과는 아쥬 만족스럽게 passed 되었다 !

 

 

이렇게 JDBC 를 이용하거나 DI 되는 싱글톤 객체를 이용하지 않더라도

엔티티에 들어있는 작은 단위의 테스트를 할 수 있다잉다잉 ^0^/)

 

 

 

 

게다가 스프링 부트 애플리케이션이 실행되지 않아도 되는 테스트여서 아쥬 빠르다 ^0^/

방금 예시의 테스트는 한 테스트에 4번의 검증 작업이 있어서 417ms 정도 걸렸지만

나머지 테스트는 2ms, 11ms 가 걸리는 것을 보았능가,,,,? ㄷㄷㄷㄷㄷ

 

 

 

이제까지 통합 테스트만 작성할 줄 알았는데 단위 테스트를 배워서(사실 곁눈질로 배운 것) 써먹을 수 있어서 아쥬 뿌듯하다!

요만큼을 같이 개발하고 있는 개발자님께서 TDD 에 관한 책도 추천해주셨는데

지금 읽고 있는 책 다 읽은 담에 함 봐야겠당,,

요즘은 헤드퍼스트 디자인패턴 책 읽는 중! ㅋ_ㅋ

728x90