패스트캠퍼스 챌린지

패스트캠퍼스 환급 챌린지 24일차 : 9개 도메인 프로젝트로 끝내는 백엔드 웹 개발 (Java/Spring) 초격차 패키지 Online 강의 후기

ssoonearth 2025. 4. 24. 22:28

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

https://abit.ly/lisbva

 

테스트 코드 실습 화면

 

 

 

 

패스트캠퍼스의 '9개 도메인 프로젝트로 끝내는 백엔드 웹 개발 (Java/Spring) 초격차 패키지 Online' 강의 수강 24일차!
오늘은 커뮤니티 피드 서비스의 인수 테스트를 위한 템플릿을 만들고, 피드 서비스 테스트 코드를 작성하는 실습을 진행하였다.

 

빠르고 간편한 테스트 코드 작성을 위해 h2 데이터베이스를 사용하기로 했다.

하지만, h2를 사용해서 테스트를 진행하면 MySQL과 비교하였을 때 문법이나 쓰이는 함수, 인덱스 등에서 다른 점이 많기 때문에 한계가 있다.

확실한 인수 테스트 작성을 위해서는 docker 이미지 파일을 이용해서 빌드 및 배포 시에 외부 DB를 이미지화하여 같이 띄워 환경을 고립시키는 방법을 사용하는 것이 좋다.

 

application-test.yml 파일을 생성하여 테스트 환경에 대한 설정을 하였다.

깨끗한 환경에서 일관된 검증이 가능하도록, 매번 테스트를 실행할 때마다 기존 테이블을 제거하고 새롭게 테이블을 생성하는 create-drop 옵션으로 ddl을 설정하였다.

 

인수테스트에서 가장 유의해야 할 점은 테스트마다 독립성이 보장되어야 한다는 것이다.

환경에 따라서, 또는 다른 테스트를 같이 진행한다고 해서 다른 결과가 나오면 안된다.

그렇기 때문에 테스트 실행 전에 모든 테이블 데이터를 삭제하고 초기화하는 역할의 클래스를 만들어 항상 동일한 초기 상태에서 테스트가 실행될 수 있도록 하였다.

@Override
    public void afterPropertiesSet() throws Exception {
    	// @Entity 어노테이션이 붙은 모든 엔티티 클래스를 가져옴
        tableNames = entityManager.getMetamodel().getEntities().stream()
                .filter(entity -> entity.getJavaType().getAnnotation(Entity.class) != null)
                
                // 각 엔티티의 @Table 어노테이션에 설정된 테이블명 수집하여 tableNames 리스트에 저장
                .map(entity -> entity.getJavaType().getAnnotation(Table.class).name())
                .toList();
                
		// 복합키는 id 리셋에서 제외되어야 함
        notGeneratedIdTableNames = List.of("community_user_relation", "community_like");
    }
@Transactional
    public void execute() {
   
    	// 수행되지 않고 남아있는 쿼리문들을 모두 DB에 반영
        entityManager.flush();
        
        // FK 제약을 해제하여 테이블 삭제에 제한이 걸리지 않게 함
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
        
        // 테이블의 모든 데이터를 삭제
        // 복합키 테이블을 제외한 테이블들의 auto_increment값을 1로 초기화
        for(String tableName : tableNames) {
            entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
            if(!notGeneratedIdTableNames.contains(tableName)) {
                entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
            }
        }
        
        // 다시 FK 제약 조건을 걺
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
    }

 

 

 

아래와 같이 테스트를 위한 초기 데이터를 생성하고 로드하는 클래스도 작성하였다.

Rest assured를 사용하니 체이닝 방식으로 간단하게 API를 호출하고 응답을 추출할 수 있었다.

public static Long requestCreatePost(CreatePostRequestDto dto) {
        return RestAssured
                .given().log().all()
                .body(dto)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .when()
                .post("/post")
                .then().log().all()
                .extract()
                .jsonPath()
                .getObject("value", Long.class);
    }

 

 

 

이제 본 테스트! 이렇게 기존에 단위 테스트를 작성했던 것과 비슷하게 given-when-then 패턴으로 작성하였다.

//주석으로 명확하게 어떤 관계인지 보여주는 것이 테스트 코드 유지보수에 도움이 된다.
/**
 * User2 Create Post1
 * User1 Get Post1 From Feed
*/

    @Test
    void givenUserHasFollowerAndCreatePost_whenFollowerUserRequestFeed_thenFollowerCanGetPostFromFeed() {
        // given
        CreatePostRequestDto dto = new CreatePostRequestDto(2L, "user1 can get this post", PostPublicationState.PUBLIC);
        Long createdPostId = requestCreatePost(dto);

        // when, 팔로워 피드를 요청
        List<GetPostContentResponseDto> result = requestFeed(1L);

        // then
        assertEquals(1, result.size());
        assertEquals(createdPostId, result.get(0).getId());
    }

 

 

Request method:	GET
Request URI:	http://localhost:8080/feed/1
Proxy:			<none>
Request params:	<none>
Query params:	<none>
Form params:	<none>
Path params:	<none>
Headers:		Accept=application/json
Cookies:		<none>
Multiparts:		<none>
Body:			<none>

Hibernate: 
    select
        pe1_0.id,
        pe1_0.content,
        ue1_0.id,
        ue1_0.name,
        ue1_0.profile_image,
        pe1_0.reg_dt,
        pe1_0.upd_dt,
        pe1_0.comment_count,
        pe1_0.like_count,
        (le1_0.target_id is not null 
        and le1_0.target_type is not null 
        and le1_0.user_id is not null) 
    from
        community_user_post_queue upqe1_0 
    join
        community_post pe1_0 
            on upqe1_0.post_id=pe1_0.id 
    join
        community_user ue1_0 
            on upqe1_0.author_id=ue1_0.id 
    left join
        community_like le1_0 
            on pe1_0.id=le1_0.target_id 
            and le1_0.target_type=? 
            and le1_0.user_id=? 
    where
        upqe1_0.user_id=? 
    order by
        upqe1_0.post_id desc 
    fetch
        first ? rows only

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 24 Apr 2025 12:23:34 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
    "code": 0,
    "message": "ok",
    "value": [
        {
            "id": 1,
            "content": "user1 can get this post",
            "userId": 2,
            "userName": "test user",
            "userProfileImage": "",
            "createdAt": "2025-04-24T21:23:34.053744",
            "updatedAt": "2025-04-24T21:23:34.053744",
            "likeCount": 0,
            "likedByMe": false
        }
    ]
}

 

테스트를 실행하고 콘솔을 보면, http 관련 내용부터 결과값까지 기존에 postman을 통한 테스트와 내용이 똑같은 것을 확인할 수 있다. (+ 실행된 쿼리문)

 

 

 

인수테스트는 굉장히 복잡할 줄 알았는데 템플릿을 잘 만들어두니 전혀 어렵지 않게 작성할 수 있었다.

매번 서버를 재시작하며 테스트하던 것을 코드로 명시화하니 훨씬 편리하게 진행할 수 있게 되었다.

이런 표현밖에 안 나오네... 정말 대박이다...ㅋㅋㅋㅋㅋㅋㅋ

앞으로 여러 라이브러리를 적극 활용해서 코드를 짜야겠다는 생각이 든다.