본문 바로가기

(8)

스프링부트 버전업 이슈 LocalTime 값을 DB에서 조회시 Invalid value for NanoOfSecond 오류 분석

현상 스프링부트 버전업 이후 LocalTime 필드를 가지는 Entity를 조회할 때, 아래와 같이 LocalTime의 nano second에 음수 값이 할당되어 에러가 발생했음.PostgreSQL DB에 저장된 TIME 타입 컬럼을 java의 LocalTime 변환 시에 아래와 같은 오류 발생java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -843000000LocalTime 변환 시, 나노초 값으로 음수가 유입되면서 발생원인변경된 스프링부트 버전업에 대응되는 하이버네이트 버전에서 변경이 있었고 이 부분에 버그가 존재했음.스프링 부트 버전: 2.6.4 ➡️ 3.2.4하이버네이트 버전: 5.6...

Springboot 3 환경에서 Feign Client 에러

물리치료 스터디에 도움이될만한 사이드 프로젝트를 진행하고있다. 프로젝트는 SpringBoot 3.x + Kotlin 1.8.22 버전을 사용하고있다. 해당 프로젝트의 MVP는 프론트 혹은 앱과 같은 클라이언트의 리소스를 들이지 않고, 오로지 Slack API와 통신하며 쓸 수 있는 애플리케이션을 만드는 것이다. 슬랙 API(= 외부 API)를 사용해야했는데, 이런 Http 클라이언트를 무엇을 쓸지 고민해보다가, Netflix의 Feign 클라이언트가 사용성이 좋았던 기억이 있어서, Feign 클라이언트로 셋티을 했다. 그런데 셋팅을 분명 잘한 것 같은데, 아래와 같은 에러가 계속해서 떳다. Consider defining a bean of type 'org.springframework.cloud.open..

[Github Action] failed to compute cache key: failed to calculate checksum of ref ... 에러 발생

하고있는 작업 Github Action으로 ECS(Elastic Container Service)로 스프링부트 애플리케이션을 배포하려고한다. github action script는 깃헙 액션에서 의도에 따라서 템플릿을 복붙해서 쓸 수 있게 잘 제공해주기 때문에 요리조리 잘 짬뽕해서 작성했다. (멀티모듈을 서비스별로 배포하는 부분이 조금 막혔지만 회사 코드들을 참고하면서 비슷하게 설정해봤다) 문제 상황 Github Action Script는 아래와 같이 작성했다. (궁금하면 아래에서 확인할 수있다) 더보기 해당 프로젝트는 저장소를 공개했기 때문에 직접 확인할 수 있다. 😃 링크: https://github.com/Rok93/physical-therapy/blob/main/.github/workflows/d..

gradle 환경에서 Spring REST Docs 문서가 서버에 띄어지지 않는 경우

필자는 백기선님의 '스프링 기반 REST API 개발' 강의를 들으면서 실습을 진행하던 중에 문제가 발생하였고 실습과 동일하게 설정하는 방법에 대해 기록한 내용입니다. 문제 발생 원인 실제 인프런 강의는 Maven Build Tool 환경에서 진행했으나, 필자는 철저한 Gradle 파이기 때문에, Gradle로 실습을 진행하였습니다. 문제는 Spring REST Docs 문서를 빌드할 때 발생했습니다. Maven의 경우 플러그인 설정을 추가하고 Spring REST Docs 공식 레퍼런스에 따라서 설정을 진행하고 IntelliJ IDE의 Build Tool 메뉴에서 package를 더블클릭하면, 설정했던 plugin들에 의해서 test에 설정했던 snippsets 들을 생성하여 문서를 만들..

Typo in static class property declaration react/no-typos 오류 해결법

니콜라스 쌤의 https://nomadcoders.co/react-fundamentals ReactJS로 영화 웹 서비스 만들기 - Nomad Coders React Fundamentals nomadcoders.co 강의를 수강하는 도중에 문제가 발생했다. Props가 우리가 예상한 Props인지 확인하는 방법이었고, npm 명령어를 통해 prop-types 모듈(라이브러리라고 해야할까? 지적 해주시면 수정하도록 하겠다)을 다운받고, 해당 컴포넌트에 {컴포넌트명}.PropTypes function을 추가함으로써, 유효성 검사를 할 수 있는데 중요한건 아직 내용을 하나도 넣지 않았는데도 오류로그가 출력됐다(니콜라스 쌤은 잘 돼더라 ...). 바로 아래와 같은 오류 로그였다. npm install을 하고 서..

java.lang.Exception: No runnable method 오류 해결법

테스트 코드를 실행하는데 다른 테스트코드는 괜찮았는데 저 테스트 클래스만 JUnit Vintage라는 오류가 발생했다. 이 문제를 해결해보고자 구글링을 해보았다. 먼저 JUnit5를 잘못 사용한 것이 아닌가? 하는 의심이 들었다.(실은 크게 잘못 사용할만한 부분이 없었다) 테스트코드에 대한 기초지식도 없었을 때, Junit5는 JUnit Vintage + JUnit Jupiter이다라고 하면서 공부했던 적이있는데, 어쨋거나 jUnit5의 두 요소중 하나의 요소만 테스트에 성공했으니 junit Vintage의 요소를 제대로 못받아 온게 아닌가... 즉 의존성 설정부분에서 실수한 것이 있을 것 같다는 생각이 들었다. 아래의 링크를 참조해서 설정을 해보았다. https://flyburi.com/607 [Jun..

각종 오류 및 해결 방법

스프링부트 버전업 이슈 LocalTime 값을 DB에서 조회시 Invalid value for NanoOfSecond 오류 분석

728x90

현상 

스프링부트 버전업 이후 LocalTime 필드를 가지는 Entity를 조회할 때, 아래와 같이 LocalTime의 nano second에 음수 값이 할당되어 에러가 발생했음.

PostgreSQL DB에 저장된 TIME 타입 컬럼을 java의 LocalTime 변환 시에 아래와 같은 오류 발생

java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -843000000

LocalTime 변환 시, 나노초 값으로 음수가 유입되면서 발생

원인

변경된 스프링부트 버전업에 대응되는 하이버네이트 버전에서 변경이 있었고 이 부분에 버그가 존재했음.

  • 스프링 부트 버전: 2.6.4 ➡️ 3.2.4
  • 하이버네이트 버전: 5.6.5 ➡️ 6.5.5

하이버네이트 버전이 변경되면서 DB에 LocalTime 저장 로직이 변경됨.

  • LocalTime → Time 객체로 변환하는 로직이 변경됨 
  • 5.6.5 버전에서는 ms 를 버린채(=0 고정) Time 객체를 생성했으나 
  • 6.5.5 버전에서는 LocalTime의 nano second에서 ms (소숫점 3자리)를 추출하여 Time 객체에 할당해줌. 즉 ms가 0이 아님
  • DB에 저장할 때는 ms값을 잘 변환하여 실제 ms와 동일한 양수의 값으로 잘 저장된다.

DB에서 시간값을 조회한 후 Time을 LocalTime으로 변환할 때 버그가 존재함

  • DB에서 시간 값을 읽어서 Time 객체를 생성하는 과정은 이상이 없음 
  • Time 객체를 LocalTime 객체로 변환할 때, Time 객체로부터 ms를 계산하여 초기화하고있는데 time 객체의 getTime 메서드 값이 음수가 나오는 경우가 있음 
    • ms 계산 로직: time.getTime() % 1000 
  • LocalTime은 날짜 정보(년,월,일)를 가지지 않아서 default 날짜(유닉스 시간(Unix time)인 1970년 1월 1일 00:00:00 UTC에서 년원일)가 임의로 지정됨. 
    • 1970년 1월 1일 00:00:00일 때 time.getTime() = 0 
  • KST 타임존인 경우에는 UTC의 기준을 맞추기 위해서 9시간을 빼주게 되고 이때, 0시~9시 시간일 경우 time.getTime()은 음수가 되고 ms값도 음수가 됨

재현 조건

  1. 타임존 설정
    • KST TimeZone인 경우에 DateTimeException 에러 발생
    • UTC TimeZone인 경우에는 에러 발생 X
  2. 특정 시간대
    • 0 ~ 8시: DateTimeException 에러 발생 
    • 9 ~ 23시: 에러 발생 X
  3. 나노초 여부
    • 나노초 O: DateTimeException 에러 발생 
    • 나노초 X: 에러 발생 X 
  4. 나노초의 소수점 자릿수
    • 소수점 3자리 존재: DateTimeException 에러 발생
    • 소수점 3자리 이후(4~9자리) 존재: 에러 발생 X (소수점 4번째 자리가 5이상인 경우 반올림되어서 예외 발생)

원인 분석 상세 

  • LocalTime 객체는 원래부터 ms를 가지고있다. 
  • 1970년 이전은 timestamp가 음수 
  • Localtime 은 날짜가 없기 떄문에 timestamp계산을 위해 1970으로 날짜 셋팅 
  • Long type의 timestamp는 zone이 없기때문에 UTC TimeZone을기본으로 사용 
  • 우리시스템은 서울이므로 -9시간을 해줌. 1970/1/1 00:09:00 - 9시간 -> 음수발생. 하지만 ms를 사용하지 않아서 마지막은 000으로 끝나서 그동안 이슈없었음 우리에러는 timestamp에서 ms를 추출하기위해 timestamp % 1,000을 사용하는데 이게 음수여서 발생

✅ Application 에서 (PostgreSQL) DB로 시간을 저장하는 과정

LocalTime 타입을 DB에 저장할 때는 TimeJdbcType.getBinder 메서드를 호출하게된다. 이 내부에서 TemporalJavaType.unwrap 메서드를 호출하여 Time 객체로 변환한다. … ➡️ TimeJdbcType.doBind ➡️ LocalTimeJavaType.unwrap ➡️ new Time(time.getTime() + DateTimeUtils.roundToPrecision( value.getNano(), 3 ) / 1000000)

HikariProxyPreparedStatement.setTime ➡️ PreparedStatementSpy.setTime(index, time)

DB에 저장할 떄는 RdbmsSpecifics.formatParameterObject 메서드를 호출하여 (DML 쿼리를 위한) 문자열로 변경하여 new SimpleDateFormat(dateFormat).format(object) 를 호출하여  MM/dd/yyyy HH:mm:ss.SSS 문자열 포맷으로 파싱하여 저장한다. 이때 object로 들어가는 값은 LocalTimeJavaType.unwrap으로 생성한 Time 이다. 

실제로 저장하려고했던 LocalTime은 08:32:00.943476 으로 ms 까지 잘 변환된 것으로 보인다. (4번째 자리 반올림)

❌ DB에서 Application으로 시간값을 조회하는 과정

DB에 저장된 데이터를 읽어오는 경우 
TimeJdbcType.doExtract ➡️ LocalTimeJavaType.wrap 호출하여  LocalTime 으로 변환하게 된다.

Time 객체는 unwrap했을 때(Application → DB)와 동일한 값의 Time 객체를 생성했다. 하지만 ms를 구하는 로직에서 문제가있어 음수의 값이 도출되게 된다.

LocalTime은 날짜 정보(년,월,일)를 가지지 않지만 Time 계산을 위해서는 날짜 정보가 필요하다. 따라서 default 날짜(epochTime or 유닉스 시간(Unix time)인 1970년 1월 1일 00:00:00 UTC에서 년원일)을 임의로 지정해준다. 여기에서 KST 타임존인 경우에는 UTC의 기준을 맞추기 위해서 9시간을 빼주게되고 이때, epochTime은 음수가 나오게된다.   (위에서 언급한 유닉스 시간의 경우 0) LocalTime을 Time으로 변환하여 DB에 저장할 때는 문제가 없지만 Time을 LocalTime으로 변환하는 과정에서는 0 ~  UTC와 KST의 시간차이 - 1 시간인 경우에는 fastTime(Time 객체의 상태값) 이 음수가되는데 ms를 이 값에서 1000을 나눈 나머지로 계산하기 때문에 음수 ms가 발생하게된다.

참고로 해당 버그 fix된 것으로 보이지만 아직 release가 안된 상태이다. 
LocalTime 객체를 저장하거나 조회할 때는 nano seconds를 제거(or 0으로 초기화)하는 Converter를 구현하여 설정해주어서 저장 혹은 조회 시, nano second가 저장되지 않도록 임시 방편을 취했고 추후 버그 fix 버전으로 release되면 hibernate 버전 업을 진행하려고한다. 

728x90
각종 오류 및 해결 방법

Springboot 3 환경에서 Feign Client 에러

728x90

물리치료 스터디에 도움이될만한 사이드 프로젝트를 진행하고있다. 
프로젝트는 SpringBoot 3.x + Kotlin 1.8.22 버전을 사용하고있다. 

해당 프로젝트의 MVP는 프론트 혹은 앱과 같은 클라이언트의 리소스를 들이지 않고, 오로지 Slack API와 통신하며 쓸 수 있는 애플리케이션을 만드는 것이다. 

슬랙 API(= 외부 API)를 사용해야했는데, 이런 Http 클라이언트를 무엇을 쓸지 고민해보다가, Netflix의 Feign 클라이언트가 사용성이 좋았던 기억이 있어서, Feign 클라이언트로 셋티을 했다.

그런데 셋팅을 분명 잘한 것 같은데, 아래와 같은 에러가 계속해서 떳다. 

Consider defining a bean of type 'org.springframework.cloud.openfeign.FeignContext' in your configuration

스택오버플로우를 찾아보니 해당 이슈는 버그로 보이고, 수정되었다고 하는데, 나는 계속 동일한 이슈가 있어서, 아래에 동일한 이슈가 있는데 해결했다는 다른 사람들의 해결책을 따라해보니 해결됐다!

해결 방법

Feign 클라이언트 Configuration 설정 클래스에 FeignAutoConfiguration, HttpClientConfigurationimport 해주면 된다!

@ImportAutoConfiguration({FeignAutoConfiguration.class, HttpClientConfiguration.class})

 

참고자료

https://stackoverflow.com/questions/74593433/consider-defining-a-bean-of-type-org-springframework-cloud-openfeign-feignconte

728x90
각종 오류 및 해결 방법

[Github Action] failed to compute cache key: failed to calculate checksum of ref ... 에러 발생

728x90

하고있는 작업

Github Action으로 ECS(Elastic Container Service)로 스프링부트 애플리케이션을 배포하려고한다.
github action script는 깃헙 액션에서 의도에 따라서 템플릿을 복붙해서 쓸 수 있게 잘 제공해주기 때문에 요리조리 잘 짬뽕해서 작성했다. (멀티모듈을 서비스별로 배포하는 부분이 조금 막혔지만 회사 코드들을 참고하면서 비슷하게 설정해봤다)

문제 상황

Github Action Script는 아래와 같이 작성했다. (궁금하면 아래에서 확인할 수있다)

더보기

해당 프로젝트는 저장소를 공개했기 때문에 직접 확인할 수 있다. 😃
링크: https://github.com/Rok93/physical-therapy/blob/main/.github/workflows/deploy.yml

name: Deploy to Amazon ECS

on:
  push:
    branches: [ "main" ]

env:
  AWS_REGION: ap-northeast-2
#  ECR_REPOSITORY: physical-theraphy-ecr-prod
  ECS_SERVICE: physical-theraphy-service
  ECS_CLUSTER: physical-theraphy-ecs-cluster-prod
  ECS_TASK_DEFINITION: physical-the경raphy-task
  
  CONTAINER_NAME: physical-theraphy-container

permissions:
  contents: read

jobs:

  build-jar:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Install JDK 17
        uses: actions/setup-java@v3.12.0
        with:
          distribution: 'temurin'
          java-version: '17'
          cache: 'gradle'

      - name: build Jar
#        run: ./gradlew bootJar --build-cache -Dorg.gradle.jvmargs=-Xmx4g
        run: ./gradlew bootJar -Dorg.gradle.jvmargs=-Xmx4g

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs:
      - build-jar
    strategy:
      fail-fast: false
      matrix:
        service: [ app-api ] # 추후 배포할 서비스가 늘어나면 여기에 추가하면 됌
        environment: [ prod ]

    environment: ${{ matrix.service }}-${{ matrix.environment }}

    env:
      ECR_REPOSITORY: physicaltherapy-${{ matrix.service }}-${{ matrix.environment }}

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
        SERVICE_TAG: ${{ matrix.service }}
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $SERVICE_TAG
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

 

그런데 트리거가 발생해서 deploy Job이 실행됐을 때, 애플리케이션을 Docker Image로 build 하는 Job이 계속 실패했다.

명령어 자체가 잘못됐나 싶어서, 이것저것 수정해봤었는데 별다른 성과는 없었다.
구글링하면서 위와 비슷한 이슈 문의 글을 찾았다. >> 사실 딱히 해결책을 콕 찝어서 말해주는 사람은 없었으나 결국 경로문제라는 늬앙스의 의견들이 많았다. 

경로 문제를 의심하며 다시 에러 로그를 살펴보았다. 

ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref moby::tsxtwwzzic4fhd2i0sw9qrb33: failed to walk /var/lib/docker/tmp/buildkit-mount2057748808/build/libs: lstat /var/lib/docker/tmp/buildkit-mount2057748808/build/libs: no such file or directory

내가 생각한 것과 실제 결과 중에 다른 부분이 무엇일까? 

내가 의도한 순서는 아래와 같다.

[1]SpringBoot 애플리케이션을 빌드해서 jar로 만든다(이 때, jar 파일은 {서비스모듈명}/build/libs/xxx.jar 로 생성) > [2] jar 파일에 접근해서 해당 파일을 root에 복사한다. > [3]docker image build를 한다. > [4] build한 image를 private ECR에 push 한다. > ... 

현재 문제되는 부분은              색깔로 처리했다. 
잘 생각해보면 [1]과 [2] 사이에서 내가 캐치하지 못한 것이 있을 것이라 생각했다. 🤔

다시 deploy.sh (github action 배포 스크립트) 파일을 살펴봤다. 
현재 Job 구성을 (1) 빌드하여 jar 파일을 만듦 (서비스가 여러개일 경우, 여러개의 jar 파일을 우선적으로 다 만든다)
(2) 빌드가 끝나서 모든 서비스의 jar 파일을 다 만들면 Deploy를 시작한다. 

(1)과 (2)가 분리된 상황이라는 것을 이때 인지했다. 
즉. build-jar 잡을 수행한 결과로 얻은 jar들은 Deploy job이 실행될 때의 

각각의 Job은 서로 다른 환경에서 실행되는 것이고 서로 다른 환경이니 실행의 결과를 당연히 공유할리 없었다.
실제로 두 잡을 실행하기전 각각의 잡을 실행하기전 Checkout을 진행하는데 이 때의 식별 값(id?)이 다르다는 것을 확인할 수 있다.

build.jar 잡
Deploly 잡

결과적으로 Job과 Job 사이에 data를 공유할 수 있는 방법이 필요한 듯 한데, 결과적으로 나는 cache를 쓰는 방식을 사용했다.
github action script에 각각의 서비스를 빌드해서 만든 jar 파일을 cache에 저장하고, docker image로 build할 때는 cache에 저장했던 jar 파일을 꺼내오는 부분을 추가했다. 

작업내용: https://github.com/Rok93/physical-therapy/commit/22f913e8ecf6dcc3335b43a673a04fd9ba658b2d

 

chore: github action 배포 스크립트 수정 · Rok93/physical-therapy@22f913e

kyeongrok-kim committed Aug 13, 2023

github.com

 

후기

아! 그래서 바꿨으니 이제 잘 동작하냐고 궁금한 분이 있을 수 있을 것 같다. 
잘 된다. docker image build까지는... 이제 push가 잘 안된다. 🫠 
이제는 이 부분을 해결해야한다. 

 

Cache 외에도 Artifacts를 활용한 방식도있는 듯 하니 궁금하다면 해당 포스팅을 참고해보면 좋을 것 같다!

참고자료: https://levelup.gitconnected.com/github-actions-how-to-share-data-between-jobs-fc1547defc3e

 

GitHub Actions — How To Share Data Between Jobs

Lately, I’ve got the chance to work with GitHub Actions, I had to share my front-end project build between multiple jobs, I did the…

levelup.gitconnected.com

 

728x90
각종 오류 및 해결 방법

gradle 환경에서 Spring REST Docs 문서가 서버에 띄어지지 않는 경우

728x90

필자는 백기선님의 '스프링 기반 REST API 개발' 강의를 들으면서 실습을 진행하던 중에 문제가 발생하였고 실습과 동일하게 설정하는 방법에 대해 기록한 내용입니다.

문제 발생 원인

실제 인프런 강의는 Maven Build Tool 환경에서 진행했으나, 필자는 철저한 Gradle 파이기 때문에, Gradle로 실습을 진행하였습니다. 문제는 Spring REST Docs 문서를 빌드할 때 발생했습니다. Maven의 경우 플러그인 설정을 추가하고 Spring REST Docs 공식 레퍼런스에 따라서 설정을 진행하고 IntelliJ IDE의 Build Tool 메뉴에서 package를 더블클릭하면, 설정했던 plugin들에 의해서 test에 설정했던 snippsets 들을 생성하여 문서를 만들고 문서를 SpringBoot가 기본적으로 지원하는 static directory에 들어가서 우리가 앱 서버를 띄우면 API 문서 페이지를 바로 View에서 확인할 수가 있다.
image

문서는 target 폴더에 generated-docs 폴더 아래에 index.html(Spring REST Docs 이름을 index.html으로 설정했다고 가정) 파일이 생성되는 것을 확인할 수 있다.

image

그와 더불어 아래와 같이 target/static/docs 폴더 아래에도 index.html이 생성된 것을 확인할 수 있다.
image

이렇게 생성이 된 것을 확인했으면, 웹 서버를 띄우고 URI에 docs/index.html을 입력하면, Spring REST Docs를 조회할 수 있다!

하지만 문제점은 Gradle로 Spring REST Docs을 사용하기 위한 설정을 하게 되면(레퍼런스 동일) target 폴더에 snippsets이라던지 docs가 생성되는 Maven과는 달리 아래와 같이 build directory에 snippsets와 docs가 생성된다.

image

예상했을지 모르겠지만, 이 docs의 생성경로가 Maven과 달라서인지, 웹 서버를 실행하여 uri에 doc/index.html을 입력해도 Spring REST Docs를 조회할 수 없다. 이런 경우에 웹 서버를 통해서 생성한 Spring REST Docs를 조회할 수 있도록 하려면 어떻게 해야할까? 이 방법이 최선인지는 확실치 않지만, 방법이 하나 있다. 간략히 설명하면 build에 생성된 Spring REST Docs 문서를 static/doc 경로에 복사해주면 된다. 단순히 build.gradle에 아래의 코드를 추가하면 된다.

task copyDocument(type: Copy) {
    dependsOn asciidoctor

    from file("build/asciidoc/html5/")
    into file("src/main/resources/static/doc")
}

build {
    dependsOn copyDocument
}

그 후 build를 실행하면 아래와 같이 static/doc 폴더에 index.html 파일이 복사된 것을 알 수 있다.

image

그 이후 uri에 doc/index.html을 입력하면

image

정상적으로 Spring REST Docs를 조회할 수 있다!


출처: API 문서 자동화 - Spring REST Docs 팔아보겠습니다
출처: Spring기반 REST API 개발 - 백기선님

728x90
각종 오류 및 해결 방법

Typo in static class property declaration react/no-typos 오류 해결법

728x90

니콜라스 쌤의 https://nomadcoders.co/react-fundamentals

 

ReactJS로 영화 웹 서비스 만들기 - Nomad Coders

React Fundamentals

nomadcoders.co

강의를 수강하는 도중에 문제가 발생했다. 

Props가 우리가 예상한 Props인지 확인하는 방법이었고, npm 명령어를 통해 prop-types 모듈(라이브러리라고 해야할까? 지적 해주시면 수정하도록 하겠다)을 다운받고, 해당 컴포넌트에 {컴포넌트명}.PropTypes function을 추가함으로써, 유효성 검사를 할 수 있는데 중요한건 아직 내용을 하나도 넣지 않았는데도 오류로그가 출력됐다(니콜라스 쌤은 잘 돼더라 ...). 바로 아래와 같은 오류 로그였다. 

npm install을 하고 서버를 껏다 키면, 웬만한 문제가 해결된다던 니콜라스 쌤의 말을 그대로 따라해봤지만 변함은 없었다. 오류로그를 그대로 검색보았다. 오류 해결법은 간단했다. 

해결법

더보기

(변경전) {컴포넌트명}.PropTypes  --> (변경후) {컴포넌트명}.propTypes 

귀신같이 해결된다. 

728x90
각종 오류 및 해결 방법

java.lang.Exception: No runnable method 오류 해결법

728x90

 

테스트 코드를 실행하는데 다른 테스트코드는 괜찮았는데 저 테스트 클래스만 JUnit Vintage라는 오류가 발생했다. 이 문제를 해결해보고자 구글링을 해보았다. 먼저 JUnit5를 잘못 사용한 것이 아닌가? 하는 의심이 들었다.(실은 크게 잘못 사용할만한 부분이 없었다) 테스트코드에 대한 기초지식도 없었을 때, Junit5는 JUnit Vintage + JUnit Jupiter이다라고 하면서 공부했던 적이있는데, 어쨋거나 jUnit5의 두 요소중 하나의 요소만 테스트에 성공했으니 junit Vintage의 요소를 제대로 못받아 온게 아닌가... 즉 의존성 설정부분에서 실수한 것이 있을 것 같다는 생각이 들었다. 아래의 링크를 참조해서 설정을 해보았다. 

https://flyburi.com/607 

 

[Junit5] SpringBoot2+Junit5 에서 TestEngine with ID 'junit-jupiter' failed to discover tests 오류 해결방법

Spring Boot 의 기본 junit4 대신 junit5(2019.7월 말 현재 최신버전인 junit 5.5.1 )를 쓰기 위해 설정하는 도중 만난 오류가 있어서 공유합니다. 개발 환경 IngelliJ IDEA gradle 5.0 Spring Boot 2.1.0.RELEASE..

flyburi.com

 

위와 같이 의존성을 추가적으로 설정했다. 윗 글의 필자에 의하면 어떤 오류가 발생한다고 하는데, 나는 이전에 발생했던 오류가 동일하게 발생했다. 분명 이전까지 잘 돌아가던 테스트 코드이기도 했고, 의존성 변경에도 변화가 없는걸로 보아, 이 방법은 해결방법이 아니라고 생각하였다. 

https://larva.tistory.com/entry/spring-boot-javalangException-No-runnable-methods

 

 

[spring boot] java.lang.Exception: No runnable methods

spring boot로 테스트 실행시 아래와 같이 예와가 난다. ---------------------------------------------------------------------------------------------------------------------------- java.lang.Exception:..

larva.tistory.com

위 글은 테스트 메서드에 @Test 를 안붙여서 나와 같은 오류가 발생했다고 하는데, 나는 분명 모든 테스트 메서드를 @Test 어노테이션을 붙였다. 이 오류 케이스도 나의 문제가 아니였다. 


의외로 이 오류가 발생한 원인은 간단했다. 제대로 동작하는 다른 테스트 코드들이랑 처음부터 비교해봤더니 오류가 발생하는 테스트 클래스에 'public' (접근자)이 붙어있다는 차이점이 있었다. 이유는 잘 모르겠지만 일단 다른 테스트 클래스처럼 public을 지우고 테스트를 실행해보았다. 

그랬더니 앞서 발생했던 오류도 없어졌다. (아예 테스트 실행창에 JUnit Vintage, JUnit Jupite로 구분되서 출력되지도 않는다)  https://effectiveprogramming.tistory.com/entry/package-private-class%EC%97%90-%EB%8C%80%ED%95%9C-unit-test-%EB%B0%A9%EB%B2%95

 

package private class에 대한 unit test 방법

설계를 하다보면 종종 상위의 인터페이스나 상위 클래스는 외부에 public으로 노출을 시키되, 하위의 구체 클래스들은 외부에 노출시키지 않아야 하는 경우가 있다. 이는 정보 은닉을 위한 좋은 설계 방법 중 하나..

effectiveprogramming.tistory.com

뭔가 직접적인 이유라고 하기에는 뭐하지만 위의 내용을 참조하면 좋은 것 같다. 내용을 조금 인용해보자면 다음과 같다. 

더보기

설계를 하다보면 종종 상위의 인터페이스나 상위 클래스는 외부에 public으로 노출을 시키되, 하위의 구체 클래스들은 외부에 노출시키지 않아야 하는 경우가 있다. 이는 정보 은닉을 위한 좋은 설계 방법 중 하나이다. 이를 위해서 보통 하위의 구체 클래스들을 package private class(JAVA에서 접근자 키워드가 없이 선언되는 클래스)로 선언한다. 이렇게 하면 같은 패키지 내에서는 해당 클래스에 접근할 수 있어도 다른 패키지에서는 package private 클래스를 접근할 수 없으므로 오직 인터페이스나 상위 클래스를 이용할 수 밖에 없다. 따라서 하위 클래스들의 변경이나 추가와 같은 유연성이 보장된다.

하지만 문제는 package private class에 대한 Unit Test를 작성하는 일은 쉽지 않다는 점이다. 일반적으로 제품 코드와 유닛 테스트 코드는 분리되는 것이 좋다. 제품 코드와 유닛 테스트 코드가 같은 곳, 즉 같은 패키지 내에 존재하면 제품의 출시를 위해서 유닛 테스트 코드를 골라내는 작업이 필요하다. 또 제품 코드를 이해하기 위해서 소스를 뒤적이다 보면 유닛 테스트 코드가 함께 섞여 있어서 불편함을 겪게 된다. 그래서 일반적으로 제품 코드와 유닛 테스트 코드를 분리하여 두는데, 이렇게 되면 제품 코드와 유닛 테스트 코드의 패키지가 분리되면서 package private인 요소들에 대해서는 유닛 테스트가 접근할 수 없게 된다. 이렇게 되면 간접적으로 해당 요소들을 테스트해야 하는데 이는 매우 번거로운 작업이 된다.

 


https://www.crocus.co.kr/1665

 

[JUnit] private 메서드, 변수 테스트 방법

JUnit으로 유닛 테스트를 하다보면 Method 테스트, Class 단위 테스트, 혹은 Module 단위의 테스트를 하게 된다. 하지만 Class, Module 단위처럼 큰 범위로 가게 되면 테스트하기 조금 어렵거나 까다로운 것들이..

www.crocus.co.kr

오류에 관련된 포스팅들을 보다가 제목이 눈에 끌려서 보게되었다. 테스트 코드를 작성할 때, 캡슐화를 하다보니면 private method, private variable이 존재하게 되는데... 이 메서드나 변수를 테스트할 때, 사용할 수 없어서 어떻게 테스트를 해야하는지에 대한 고민들이 많았다. (내가 조언받은 방법으로는 더 많이 코드를 쪼개서 역할을 분담하므로서 public 인자로 바꿔가는 것이었다) 

이 방법이 좋은지에 대해서는 확신은 못하겠지만, "이런 방법도 있구나?" 정도로 생각하면 좋을 것 같다. 

728x90