회고 & 후기

[회고] 테스트 코드 문화 도입기

키태 2024. 3. 21. 05:26
728x90

최근 어느정도 업무에 익숙해지기도 했고 스스로 컴포트존(Comfort Zone)에 들어왔다고 생각했다. 집을 회사 근처로 옮긴 후에는 출근 전, 퇴근 후 조금이지만 체력과 시간이 남아있었다. 이 컴포트존에서 벗어나기 위해 해보지 않은 기술 공부, 공부를 하더라도 프로젝트에 옮길 수 있는 환경이 필요하다고 생각했었다. 운이 좋게도 학교 후배가 좋은 의미의 사이드 프로젝트를 같이하자고 연락을 줘서 선뜻 수락을 했다. 

 

https://causw.co.kr/

 

CAUSW

 

causw.co.kr

"동문 네트워크'라는 프로젝트이며 중앙대 소프트웨어학부 동문만 사용할 수 있는 커뮤니티 서비스로, 단순 소셜 네트워크 기능 뿐만 아니라 사물함 신청, 동아리 신청 및 학생회 사업/행사 신청 등 전반적으로 소프트 학생 사회를 하나로 묶어주며, 학생들의 편의를 증진시켜주는 서비스라고 한다. 이미 재학생 때부터 이 서비스를 알고 있었으며 잘하는 선배들이 모여 만든 서비스고 몇번의 오픈을 했지만 유지보수의 인력이 부족해 그만둔 서비스이다. 

 

'되도록이면 내가 해보지 못한, 공부해보고 싶은 기술을 쓸 수 있게 이 프로젝트를 시작하자' 라는 마음을 가지고 프로젝트를 시작했다. 아무래도 직장인이기에 재학생들보다 급한 이슈를 쳐내는 것은 한계가 있기에 오픈이 얼마 안남은 시점에서 비즈니스 로직을 건드리는 건 무리라 판단했고 나는 고민 끝에 테스트 코드 쪽을 완전 갈아엎기로 결정했다.

 

해당 프로젝트는 약간의 테스트 코드가 있었지만 groovy 언어로 되어있었고 테스트 코드만으로 해당 로직을 대표하는 문서가 되거나 신뢰성을 보장하기에는 부족해보인다고 판단했다. 아마 인력이 부족해 담당자가 자꾸 바뀌면서 흐지부지 된 것 처럼 보인다. 나는 groovy도 좋지만 Junit이 아무래도 커뮤니티와 생태계가 크고 여러 레이어단과 클래스와 메소드 단에서 테스트가 용이하다고 생각해 Junit으로 바꾸기로 결정했다.

 

추가로 2월에 프로젝트를 시작해서 급하게 3월 초(개강 시즌이라)에 배포하지만 이후 싹 리팩토링을 진행할 계획이었다. 리팩토링을 진행하면서 코드 디렉토리 구조부터 로직까지 많이 바뀔 것인데 이때 테스트 코드 문화를 잘 도입해놓으면 과감하고 안전한 리팩토링이 가능할 것이라 믿었다.

 

우선 리팩토링 시즌 전까지 나는 "Junit을 활용해 팀 전체에서 테스트 코드 문화를 쉽게 접근하고 사용할 수 있도록 문화를 만들자" 를 할 일을 계획하고 시작했다. 그러기 위해선 테스트 케이스를 우선 구현하는 것보다 어떻게 팀원들이 테스트 케이스를 짤 수 밖에 없을 지 고민을 했다.

 

https://www.youtube.com/watch?v=jdlBu2vFv58

 

이에 관해 구글링하다 토스 SLASH 21에서 발표한 "테스트 커버리지 100%"에 대한 영상을 보게 됐고 처음으로 '테스트 커버리지'라는 개념에 대해 알게 됐다. '테스트 커버리지'란 시스템 및 소프트웨어에 대해 충분히 테스트가 되었는지를 나타내는 정도이며 수행한 테스트가 얼마나 테스트 대상을 커버했는지 나타내는 지표라고 한다. 즉 내가 짠 테스트 코드가 실제 프로덕션 코드를 얼마나 커버할 수 있는지 나타내는 것이다. 이 수치가 높으면 높을수록 내 프로덕션 코드는 신뢰성이 높은 것이고 예기치 못한 버그가 터질 확률이 적어진다. 영상을 보니 테스트 커버리지를 측정해 배포 단계에서 일정 퍼센티지를 못넘기면 배포를 실패하게 만드셨길래 비슷한 제도를 우리 프로젝트에도 도입하면 되겠다라고 느꼈다. 그러면 팀원들이 자신이 짠 프로덕션 코드에 대해 테스트 코드를 짤 수 밖에 없을 것이고 처음엔 힘들겠지만 추후 인력이 교체되어도 유지 보수에 훨씬 용이할 것이라고 판단했다.

 

나는 jacoco라는 기술을 도입해 팀 코드의 테스트 커버리지를 계산하고 이를 develop 브랜치에 PR을 날리는 과정에서 github action을 통해 일정 수준의 테스트 커버리지를 넘겼는지 코멘트로 남기며 동시에 기존 테스트 코드가 전부 pass 했는지 코멘트를 남기는 기능을 구현하기로 결정했다.

 

name: 동문네트워크 백엔드 CI 테스트 자동화

on:
  pull_request:
    branches: [ develop ]

permissions: write-all

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'corretto'

      - name: Set YML
        run: |
          mkdir -p src/main/resources
          echo "${{ secrets.APPLICATION_YML }}" | base64 --decode > src/main/resources/application.yml
          find src

      - name: Build with Gradle
        run: ./gradlew build

      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: '**/test-results/**/*.xml'

      - name: Test Coverage Report
        id: jacoco
        uses: madrapps/jacoco-report@v1.6
        with:
          title: Test Coverage Report
          paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
          token: ${{ secrets.GITHUB_TOKEN }}
          min-coverage-overall: 30
          min-coverage-changed-files: 50

 

./github/workflows에 pr.yml 이라는 파일을 통해 "develop 브랜치에 PR을 날릴 때 해당 action이 발생하도록" 스크립트를 작성했다. 우리 팀의 git 전략은 기능 구현(feat/{기능} 브랜치) -> 개발계(develop 브랜치) -> 운영계(main 브랜치) 를 따르고 있었기에 우선 feat 브랜치에서 develop 브랜치에 합쳐지기 전 PR 단계에서 테스트 관련 액션이 동작하도록 설계했다.

 

주요 단계들을 살펴보자면

- Set YML : 해당 작업은 gitignore 때문에 추가해준 단계이다. 각종 설정 파일(DB, 서버 등)은 gitignore로 레포지토리에 올라갈 때 제외시키고 올라간다. 그래서 막상 로컬에서는 빌드가 잘되지만 action을 돌 때는 해당 설정값을 찾지 못해 에러가 뜬다. 이 해결방법으로 해당 application.yml 파일을 base64로 인코딩해 repository secret key에 등록을 해놓고 위와 같이 환경변수로 디코딩 설정을 걸어두면 action을 돌 때 설정값을 찾아서 빌드한다.

 

- Publish Test Results : 테스트 결과를 게시하는 단계이다. 테스트 커버리지와는 관련이 없지만 나도 모르게 내가 짠 프로덕션 코드가 다른 비즈니스 로직을 건드릴 수 있고 해당 케이스를 방지하기 위해 모든 테스트 케이스를 돌리는 액션이다. 내가 짠 프로덕션 코드가 다른 비즈니스 로직을 건드렸을 때 테스트 코드가 잘 짜여있다면 당연히 에러가 날 것이고 이것이 테스트 케이스의 순기능이라 생각했기에 넣었다.

 

- Test Coverage Report : 테스트 커버리지 보고서를 생성하는 단계이다. madrapps/jacoco-report 액션을 사용하여 jacoco를 통해 테스트 커버리지를 측정하고, 설정된 임계값을 기준으로 보고서를 생성한다. 우선 지금은 설계 및 기능 도입 단계이기에 임계값은 임의로 넣어줬고 추후 팀원들이랑 상의 후 바꿀 예정이다.

 

 

우선 'Publish Test Results'에 대한 결과의 코멘트이다. groovy로 된 테스트 케이스들은 모두 지우고 Junit으로 예시 테스트 케이스를 2개 작성하고 PR을 날린 상태이다. 예상한 결과와 똑같이 2 tests에 체크 표시가 된 것을 볼 수 있다. 만약 내가 짠 코드가 나도 모르게 다른 로직에 영향을 줬을 경우 해당 단계에서 영향을 준 로직의 테스트 케이스가 실패하기를 예상한다.

 

 

다음은 'Test Coverage Report'의 결과에 대한 코멘트이다. 지금 우리 프로젝트의 테스트 커버리지 값은 0.35%이고 내가 임의로 설정한 임계값을 못넘었기에 X 표시가 뜨는 것을 볼 수 있다. (해당 PR - https://github.com/CAUCSE/CAUSW_backend/pull/537)

 

이렇게 테스트 문화를 활성화하기 위해 여러 기능을 도입해봤는데 사실 나도 테스트 케이스를 이때까지 소홀히 해오며 시간이 부족해 넘긴 경우가 대부분이다. Junit 그리고 테스트 그 자체에 대해 개념이 부족하다고 느껴 요즘 틈틈히 https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard

 

Practical Testing: 실용적인 테스트 가이드 강의 - 인프런

이 강의를 통해 실무에서 개발하는 방식 그대로, 깔끔하고 명료한 테스트 코드를 작성할 수 있게 됩니다. 테스트 코드가 왜 필요한지, 좋은 테스트 코드란 무엇인지 궁금하신 모든 분을 위한 강

www.inflearn.com

Practical Testing이라는 강의를 듣고 있다. 강의를 들으며 테스트 케이스에 대해 소홀했던 점을 반성하고 이런 좋은 문화를 팀에 도입하려 적극 노력하고 있다. 테스트 문화를 도입하고 개발 자체에서 테스트 코드로 시작할 수 있게 TDD 문화를 도입할 생각인데 어떻게 할지 요즘 고민 중이다. 

 

TDD 방식의 대표적인 방법 중 하나인 RED GREEN REFACTOR 방식을 따르며 개발을 진행하는게 최종 목표인데 이는 우선 테스트 문화가 팀에 자연스럽게 정착될 때까지 보류하려고 한다.

728x90