[삽질기록] 테스트 삽질기🤦🏻‍♀️

케이·2022년 10월 2일
0

삽질기록

목록 보기
3/7
post-thumbnail

틀린 내용이 있다면 지적해주시면 감사하겠습니다🙇🏻‍♀️

테스트를 작성하면서 마주했던 문제들을 정리했습니다. 정말 별거 아닌 내용일 수 있으나 혹시라도 누군가에게 도움이 되면 좋겠다는 생각에 (+기억보다 기록을 위해) 삽질기를 작성하고 있습니다. 틀린 내용이 있다면 지적해주시면 감사하겠습니다.

테스트 환경 구축하기

문제상황

  • 수정, 삭제 등의 테스트를 할 때 실제 DB에도 영향을 미침
    -> 트랜잭션 어노테이션이 먹히지 않는지 롤백이 되지 않는다.
    -> 왜? 우리는 테스트 시에 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)를 사용하고 있었는데 이처럼 랜덤포트를 사용하는 경우에는 별도의 다른 스레드에서 테스트가 수행되기 때문에 롤백이 되지 않는다.
    아래는 해당내용의 원본이다. 스프링 공식문서에서 확인할 수 있다.

해결방법

  • 독립 테스트 환경 구축!!!!!!!!!!!!!!
    -> 여러가지 후보들이 있었는데 가장 간편하게 사용할 수 있는 인메모리 DB로 독립 테스트 환경 구축
    사실 구글링을 하면서 테스트 컨테이너 등 여러 개념을 찾아보다가.. 아니 제일 쉬운 인메모리..가 있는데 왜 잊고 있었지...? 하고 바로 인메모리 DB를 사용하도록 했다.ㅎㅎ....

h2와 mysql의 AutoIncrement차이??

문제 상황

Repository 테스트를 하는데 아래와 같이 오류가 발생했다

@Test
@DisplayName("유저가 1개의 글을 찜했을 때 찜한 목록을 조회한다면 조회값이 1이어야한다")
void findByUser() {
        //given 유저가 글을 찜한다
        Bookmark bookmark = new Bookmark(user, article);
        bookmarkRepository.save(bookmark);
     	//중략
}

위의 테스트 코드에서 save할 때 발생하는 오류로 이미 PK 값이 존재하는데 그 위에 덮어 쓰려고 시도해서 나는 것으로 보였다.

Data.sql에는

INSERT INTO wbookmark (id, article_id, user_id)
VALUES
(1, 13, 1),
(2, 14, 1),
(3, 1, 2);

이런식으로 되어 있던 터라 너무 당연히 4번부터 이어서 저장이 될 것이다 라고 생각했는데 전혀 다르게 동작했다.

h2와 mysql의 AutoIncrement

mysql 같은 경우에는 위처럼 data.sql에 더미데이터를 넣어놓아도 이어서 PK값을 증가시키면서 데이터 삽입이 가능하다.(위의 경우에서는 3번까지의 데이터가 있으니 PK 4번부터 이어서 저장하게 된다)

h2의 경우는 PK값을 이어서 저장하지 못해서 오류가 난다.

@GeneratedValue(strategy = GenerationType.IDENTITY)

  • 기본 키 생성을 데이터베이스에 위임한다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다. 보통 id 값은 비워두고 자동으로 데이터베이스에서 자동으로 값을 입력한다. 그렇기 때문에 데이터베이스에 값을 저장하고 나서야 비로소 기본 키 값을 구할 수 있다.
    이 전략을 사용하면 JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회하게 된다. 즉 em.persist()를 호출해서 엔티티를 저장한 직후 할당된 식별자 값을 조회하여 영속성 컨텍스트에 값을 채워 넣는다.

@GeneratedValue(strategy = GenerationType.SEQUENCE)

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이 시퀀스를 사용하여 기본키를 생성한다. 주로 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.

(출처: https://hyeonic.tistory.com/196)

해결방법

@AutoConfigureTestDatabase(replace =NONE) 을 달아주고 테스트를 하면 된다.

@AutoConfigureTestDatabase(replace =NONE) 을 다느냐?
-> 우리의 Repository 테스트에서는 @DataJpaTest를 쓰고 있는데 @DataJpaTest에는 @AutoConfigureDataJpa 가 포함되어있다.

@AutoConfigureDataJpa에서 NONE으로 해주어야 우리가 만들어 놓은 테스트 환경 (test-resource 아래에 있는 파일들 - 테스트를 위한 application.yml 등)을 사용할 수 있다. NONE으로 하지 않을 시에는 임베디드 DB를 사용하게 된다. (아래 캡쳐 참고)

우리의 data.sql은 mysql 기준으로 쓰여졌고 autoIncrement기능도 mysql기준으로 사용하는 걸 전제로 하는데 위의 코드에서는 mysql 문법을 인식하지 못한다. (우리의 application.yml파일에는 spring.datasource.url에 MODE=MySQL을 추가해주었기 때문에 가능)


DTO 안의 DTO 검증

아니.. 이건 정말..
그냥 공식문서를 보면 한번에 해결되는 것이었는데... 이상하게 딴 곳가서 삽질하다가 뒤늦게 공식문서를 보고 알게 되었다. 이해가 쉽게 예시를 보자면..

{
   "lotto":{
      "lottoId":5,
      "winning-numbers":[2,45,34,23,7,5,3],
      "winners":[
         {
            "winnerId":23,
            "numbers":[2,45,34,23,3,5]
         },
         {
            "winnerId":54,
            "numbers":[52,3,12,11,18,22]
         }
      ]
   }
}

이렇게 lotto안에 winners가 있는 형식은 어떻게 검증해야 하는지를 한참 찾았는데.. 생각보다 너무 간단하다..
테스트코드 예시)

@Test 
public void lotto_resource_returns_200_with_expected_id_and_winners() {

    when().
            get("/lotto/{id}", 5).
    then().
            statusCode(200).
            body("lotto.lottoId", equalTo(5),
                 "lotto.winners.winnerId", hasItems(23, 54));

}

위의 테스트 코드 예시에서 body부분을 눈여겨보면. lotto.winners.winnderId 라고 쓰여 있는 부분을 확인 할 수 있다. 이처럼 필드명.필드명 의 형식으로 쓰면 된다.


테스트에서 로그를 가지고 Rest Docs 문서 만들기?

테스트 코드를 짜다가 log를 사용할 수 있다는 것을 알게되었다. 단위 테스트에서 구현한 내용들이 모두 검증되고 rest docs 문서만 만들 필요가 있다면 log를 사용해서 만드는 것도 괜찮지 않을까 생각했다.
하지만 내가 속해있는 팀프로젝트에서는 단위테스트 검증 완료 전에 api문서 제공을.. 목적으로 했으므로 그렇게 사용하지는 못했다.
log를 사용한다면 log().all() 을 사용하면 된다.
예시)

@Test
void 양수글을_상세조회한다() {
        given(documentationSpec)
                .filter(document("get-wanted-article", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
                .log().all()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .header("access-token", jwtToken.getAccessToken().getTokenCode())
                .when()
                .get("/houses/wanted/1")
                .then()
                .log().all()
                .extract();
    }

참고

https://rest-assured.io
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
https://charliezip.tistory.com/21
https://kukim.tistory.com/105 (역시 갓 쿠킴;;;;;)
https://hyeonic.tistory.com/196

profile
삽질하며 깨닫고 배웁니다. (a.k.a 프로삽질러) + 이 구역의 회고왕

0개의 댓글