내배캠/프로젝트, 개인과제 트러블슈팅

[내일배움캠프/백엔드] JPA 심화 개인과제 트러블 슈팅

jy3574 2024. 12. 19. 12:27

1. @DynamicInsert 사용

1)개요

DynamicInsert를 통해 DB의 기본값을 적용하려고 했지만, ItemEntity 테스트 코드를 작성 했을 때 nullable = false의 조건을 확인하는 과정에서 테스트 코드가 정상적으로 동작하지 않는 문제 발생

 

2)문제상황

기본 값 설정을 @PrePersist를 사용해 처리하는 방식으로 수정을 했는데, 테스트 환경에서 DB에 직접적인 기본값 적용 확인이 어려워 DynamicInsert와 중복으로 인하여 여전히 동작하지 않는 문제 발생

 

DynamicInsert는 SQL INSERT 문을 생성할 때 null 필드를 제외시켜 DB의 기본값을 적용하지만, 코드에서 설정된 값과 충돌하거나 테스트에서 null 처리가 누락이 되면 동작하지 않는 문제 발생

 

3)해결

DynamicInsert 동작 원리를 다시 공부한 후에 테스트 코드에는 null 값을 직접 넣어서 처리할 수 있도록 변경하여 엔티티의 기본 값이 제대로 들어가도록 설정

    @Test
    @DisplayName("status가 null일 경우 DB에서 예외 발생")
    void statusCannotBeNull() {
        // given : 필드만 null로 설정한 Item 생성
        User owner = new User("USER", "owner@test.com", "OwnerName", "password");
        User manager = new User("ADMIN", "manager@test.com", "ManagerName", "password");
        entityManager.persist(owner);
        entityManager.persist(manager);

        Item item = new Item("TestItem", "TestDescription", manager, owner);
        // PrePersist를 우회하기 위해 직접 status를 null로 설정
        item.setStatus(null);

        // when
        Item savedItem = itemRepository.saveAndFlush(item);

        //then
        assertNotNull(savedItem.getStatus(), "status 값은 null이 아니어야한다.");
        assertEquals("PENDING", savedItem.getStatus(), "status 기본값은 'PENDING'");
    }

 

테스트 코드에서 DB의 기본값을 활용하는 경우와 코드에서 처리하는 경우를 분리해야함. 따라서 테스트 환경에서 Dynamic Insert가 적용되지 않는 경우를 생각해 @PrePersist를 사용하여 기본값을 설정

 

4)결론

DynamicInsert와 PrePersist를 활용한 기본값 설정 로직의 역할을 분리하고, 값을 명시적으로 처리함

테스트 코드를 작성하기 위해서는 코드의 동작 원리를 정확히 이해해야한다는 것을 알게 되었음.

 

<DynamicInsert의 동작 원리>

  • DynamicInsert는 SQL문을 생성할 때 null 값을 가진 필드를 제외하여 INSERT문을 간소화함
  • DB에 기본값이 적용되도록 도와주지만, 코드에서 기본 값을 추가로 설정할 시에 충돌할 수 있음

2. Q클래스 생성 과정

1)개요

QueryDSL 사용을 위해 Q 클래스를 생성하는 과정에서 의존성을 여러 번 수정했는데도 빌드가 실패하거나 Q클래스가 제대로 생성되지 않는 문제 발생

 

2)문제상황

QueryDSL 관련 설정을 여러번 수정하면서 querydsl-apt, annotationProcessor, build 설정을 재구성하고, Q클래스 생성이 안되는 문제를 해결하기 위해 compileQueryDSL 설정을 추가하고 annotaionProcessor 경로를 확인

하지만 gradle 설정이 꼬여 빌드 생성에 실패

 

3)해결

build.gradle 파일을 재구성하고, QueryDSL 관련 설정을 통합

build/generated 디렉토리로 Q클래스가 생성되도록 경로를 확인하고 고정

//Java 컴파일러에 대한 설정 정의
//어노테이션 프로세서를 통해 소스파일 출력 디렉토리를 지정
tasks.withType(JavaCompile).configureEach {
    options.generatedSourceOutputDirectory.set(layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main"))
}

//소스 디렉토리 경로에 어노테이션 프로세서를 통해 생성된 Q클래스 디렉토리를 추가
//Gradle이 해당 디렉토리를 프로젝트의 소스 경로로 인식하여 Q클래스를 사용할 수 있게 설정
sourceSets {
    main {
        java {
            srcDirs += "$buildDir/generated/sources/annotationProcessor/java/main"
        }
    }
}

 

4)결론

Q클래스 생성 문제를 해결해 나가면서 Gradle 설정에 관한 것을 조금 알게 되었고, 의존성 설정에 대한 공부를 해야겠다고 생각하게 되었음


3. 테스트 코드 작성

1)개요

테스트 코드를 특강으로 잠시 배우고 작성해보면서 단위 테스트와 통합 테스트의 개념도 명확히 잡히지 않았는데, 과제에 작성해야하는 ItemEntity 테스트의 경우 통합 테스트를 하라는 것 같은데 단위테스트를 해야하나 헷갈려서 코드 작성하는 과정에서 어려움이 있었음

 

2)문제상황

테스트 코드에 대한 정확한 이해 없이 코드를 작성하다 보니 혼동이 오는 경우가 많았고, 특히 mock&mockito 의 구분이나 import를 할 때 어떤걸 해야하는지 assert는 왜 쓰는지에 대한 이해를 해야하는 상황이 생김

 

3)해결

테스트 코드에 대한 공부를 함

  • 테스트의 목적 구분
    • 단위 테스트
      • 코드 레벨에서 비즈니스 로직이나 특정 메서드를 검증
      • Mock 객체를 활용해 의존성을 제거하고 테스트 대상만 집중적으로 확인
    • 통합 테스트
      • 여러 계층을 연결하여 실제 데이터베이스와 상호작용
      • 전체 시스템이 제대로 동작하는지 검증
      • Spring Boot의 @SpringBootTest나 @DataJpaTest 사용
  • Mock vs Mockito
    • Mock : 테스트 객체를 생성할 때 사용하고, @Mock을 통해 주입
    • Mockito : Mock 객체의 동작을 정의하거나 결과를 검증할 때 사용
  • 주요 테스트 어노테이션
    • @DataJpaTest : JPA와 관련된 것을 테스트 할 때 사용. 기본적으로 H2 DB를 활용
    • @SpringBootTest : 전체 애플리케이션의 통합 테스트를 수행
    • @ExtendWith(MockitoExtension.class) : Mockito를 활용한 단위 테스트 설정. 의존성 주입과 Mock 객체 관리

4)결론

테스트 코드에 대한 이해가 아직 부족한 것 같아 공부를 더 해야겠다고 생각하게 되었다. 그래도 처음 테스트 코드를 작성할 때 코드 하나를 완성하기 위해 10시간 넘게 걸렸는데 이제는 3~4시간으로 줄었다.

단위 테스트와 같은 간단한 케이스를 작성하는 연습을 하고, 테스트 성공 유무를 바로 실행해서 확인 한 후에 문제점을 하나씩 고쳐나가는 방식으로 공부를 해야할 것 같다.

또한, 테스트 코드를 작성해 보니 CRUD와 각 계층 별 동작 원리를 정확히 이해하고 있어야겠다고 생각하게 되었다.


4. Jacoco 의존성 추가

1)개요

과제에 Jacoco로 코드 커버리지를 측정해서 60% 달성해보라는 내용이 적혀 있어 첨부해준 자료를 읽고 의존성을 추가했는데, 제대로 동작하지 않는 문제 발생

 

2)문제상황

첨부해준 자료와 내가 지금 사용하고 있는 Gradle, Spring Boot 버전이 달라 Jacoco가 정상적으로 설정되지 않았고, 플러그인을 찾을 수 없다는 에러가 발생

최신 Gradle 버전에 맞는 Jacoco 플러그인 버전을 찾아서 의존성을 추가해야하는 상황

 

3)해결

현재 사용하는 Gradle, SpringBoot 버전에 맞는 Jacoco 의존성을 찾아 적용

build.gradle에 Jacoco 플러그인을 추가하고, jacocoTestReport와 jacocoTestCoverageVerification 태스크를 설정

jacoco {
    toolVersion = "0.8.10" // 최신 JaCoCo 버전을 명시적으로 설정
}

jacocoTestReport {
    reports {
        html.required.set(true) // HTML 리포트 활성화
        xml.required.set(true)  // XML 리포트 활성화
        csv.required.set(false) // CSV 리포트 비활성화
    }
}

jacocoTestCoverageVerification {
    violationRules {
        rule {
            element = 'CLASS'
            limit {
                counter = 'INSTRUCTION'
                value = 'COVEREDRATIO'
                minimum = 0.60 // 커버리지 기준 설정 (60%)
            }
        }
    }
}

tasks.test {
    finalizedBy(tasks.jacocoTestReport) // 테스트가 끝난 후 리포트를 생성
}

 

4)결론

지금 내가 쓰고 있는 버전과 호환되는 플러그인을 찾아 사용하여 문제를 해결하였지만, 이런 문제가 발생할 때 마다 구글링을 통해 찾기에는 시간이 너무 오래 걸린다는 것을 인지하고 어떻게 해결을 해야하나 찾아보던 중 공식문서를 활용하면 된다는 내용이 있어 공식문서를 참고하였더니 조금 더 명확해진다는 장점이 있었다. 앞으로 공식문서를 우선 참고 해야겠다.

 

https://www.jacoco.org/jacoco/trunk/doc/

 

JaCoCo - Documentation

Documentation Concepts See what this is all about and understand the basic ideas. Using JaCoCo Use JaCoCo tools out-of-the-box. Integrating JaCoCo Integrate JaCoCo technology with your tools. Developing JaCoCo Improve the implementation and add new feature

www.jacoco.org

 

https://docs.gradle.org/current/userguide/jacoco_plugin.html

 

The JaCoCo Plugin

The JaCoCo plugin adds the following dependency configurations: Table 2. JaCoCo plugin - dependency configurations Name Meaning jacocoAnt The JaCoCo Ant library used for running the JacocoReport and JacocoCoverageVerification tasks. jacocoAgent The JaCoCo

docs.gradle.org