
안녕하세요 저는 오픈 강의 플랫폼 회사에 재직중인 3년차 iOS 개발자 제이븐입니다!
오늘 포스팅할 주제는 GithubActions를 활용하여서 테스트 자동화 워크플로우를 구축하고
테스트 결과, 커버리지를 PR의 코멘트로 리포트해주는 봇을 만드는 과정을 소개하려고 합니다.
추가적으로 테스트 코드를 작성하고 자동화하면서 느낀점과 개인적인 생각들을 조금 적어보겠습니다
테스트 자동화란 소프트웨어의 기능과 품질을 검증하기 위해 개발자가 작성한 테스트 코드가 올바르게 동작하는지 검증하는 프로세스를 사람이 매번 수동으로 하는 것이 아닌 자동화 도구를 활용하여 실행하는 것을 말합니다.
테스트 자동화를 도입해야 하는 이유는 소프트웨어 개발 과정에서 발생하는 반복적인 테스트 작업을 효율적으로 처리하고, 코드 변경에 따른 오류를 조기에 발견하여 품질을 보장하기 위해서입니다.
소프트웨어는 복잡해지고 배포 주기는 짧아서, 기능 추가나 수정으로 인한 기존 기능에 미치는 사이드 이펙트를 빠르게 확인하는 것이 중요합니다. 테스트 자동화는 사람이 직접 실행하지 않아도 일정한 조건에서 테스트를 반복적으로 수행할 수 있어, 시간과 비용을 절감할 수 있습니다.
또한, CI/CD 파이프라인에 테스트 자동화를 통합하면 코드가 병합되거나 배포되기 전에 자동으로 테스트 코드들을 실행하고 검증할 수 있어서 코드의 병합과 배포 과정에 안정성을 확보할 수 있습니다.
결론적으로, 테스트 자동화는 개발 효율성을 높이고 소프트웨어의 품질을 지속적으로 유지하기 위한 프로세스라고 생각합니다.
본격적으로 테스트 자동화 코드 작성에 앞서, 테스트 코드 작성과 부산물로서의 코드 커버리지에 대해 말씀드리려 합니다. 또한, 테스트 자동화와 관련된 제 개인적인 생각도 함께 공유해보겠습니다.
테스트 코드를 작성하는 과정은 단순히 버그를 방지하는 것을 넘어, 개발 과정에 여러 긍정적인 효과를 가져옵니다. 테스트 코드를 작성하며 요구사항을 체계적으로 정리하고 엣지 케이스를 발견할 수 있어, 안정적인 코드를 작성할 수 있는 기반이 됩니다. 또한, 테스트 코드는 코드의 의도를 설명하는 문서로서의 역할을 하여, 다른 개발자들이 코드를 이해하거나 유지보수하는 데 도움을 줍니다.
리팩토링 과정에서도 테스트 코드는 중요한 역할을 합니다. 기존 테스트가 정상적으로 통과하면, 리팩토링 후에도 시스템이 올바르게 동작한다는 신뢰를 제공합니다. 이는 버그를 예방하고 개발자가 개선 작업을 더 적극적으로 시도할 수 있도록 합니다.
테스트 코드를 작성하는 과정에서 결합도가 높은 코드를 느슨하게 만들 필요성을 인식할 수 있습니다. 테스트하기 어려운 코드는 구조적으로 개선이 필요할 가능성이 높기 때문입니다. 이를 개선하는 과정에서 코드베이스의 품질이 향상되고, 유지보수하기 쉬운 구조를 만들 수 있습니다.
코드 커버리지란 테스트 코드가 기존 코드를 얼마나 테스트하고 있는지를 나타내는 지표입니다. 커버리지가 높다는 것은 테스트 코드가 기존 코드를 더 많이 검증하고 있다는 의미이며, 반대로 커버리지가 낮다면 테스트가 검증하지 않는 코드 영역이 많다는 것을 의미합니다. 이는 소프트웨어의 테스트 범위를 평가하고, 테스트의 부족한 부분을 확인하는 데 사용됩니다.
코드 커버리지는 테스트의 범위를 나타내지만, 테스트의 품질이나 적절성은 보장하지 않습니다. 즉, 커버리지가 높은 것이 높은 품질을 보장할 수는 없다는 말입니다.
그래서 이어서 테스트 코드와 커버리지에 대한 개인적인 생각은 끄적여보겠습니다.
테스트 커버리지는 코드 품질과 버그 감소에 어느 정도 영향을 미치지만, 100% 직접적인 상관관계가 있다고 보기는 어렵습니다. 꼼꼼한 테스트 코드 작성으로 많은 버그를 예방할 수 있지만, 모든 엣지 케이스를 완벽히 커버하는 것은 쉽지 않습니다.
예를 들어, “코드 커버리지가 100%라면 버그가 발생하지 않느냐”는 질문에 “예”라고 답하기는 어렵습니다. 커버리지가 높다고 해서 모든 엣지 케이스를 포함한다고 보장할 수 없기 때문입니다. 모든 경로를 실행하는 테스트가 존재하더라도, 예상치 못한 입력이나 환경적 요인으로 인해 발생하는 버그는 여전히 놓칠 가능성이 있습니다.
결국, 커버리지는 중요한 참고 지표이지만, 그 자체가 목표가 되어서는 안 됩니다. 수치에 집착하기보다는, 의미 있고 신뢰할 수 있는 테스트 코드를 작성하는 데 집중하는 것이 코드 품질과 안정성을 높이는 데 더 효과적입니다.
그래서 저희 팀에서는 커버리지를 중요한 지표로 여기지만, 그것을 목표로 삼기보다는 좋은 테스트 코드 작성의 부산물로 간주하고 있습니다. 우리가 진짜 목표로 삼고 있는 것은 철저하고 의미 있는 테스트 코드를 작성해 가능한 많은 시나리오를 효과적으로 커버하는 것입니다. 그렇게 하면 커버리지는 자연스럽게 따라오게 됩니다. 반대로, 커버리지 수치만을 목표로 삼아 테스트 코드를 작성하다 보면 오히려 그 의미가 퇴색될 수 있다고 생각합니다.
물론, 커버리지가 중요하지 않다는 뜻은 아닙니다. 커버리지는 우리가 테스트의 범위를 확인하고, 개선 방향을 찾는 데 유용한 지표입니다. 다만, 정말로 의미 있는 커버리지는 좋은 테스트 코드에서 비롯된 것일 때 진정한 가치를 발휘한다고 생각합니다.
GitHub Repository -> Actions -> Swift -> Configuration

위에 경로대로 따라와서 Configuration을 클릭하면 아래와 같이 workflow를 만들 수 있는 에디터가 나옵니다.

코드를 전부 지우고 처음부터 하나씩 만들어가 보도록 하겠습니다.
GitHub Actions에서 워크플로 이름을 지정하는 경우, name 필드에 작성하는 값은 워크플로가 실행될 때 Actions 탭에 표시됩니다. 따라서 이 이름은 워크플로의 목적과 실행 의도를 명확히 드러내는 것이 좋습니다. 작성하지 않는다면 파일 이름이 노출됩니다.

name: Auto Run TestCode
on 키워드는 워크플로를 트리거(실행)하는 이벤트를 정의하는 데 사용됩니다. 이 필드를 통해 워크플로가 실행될 조건을 설정할 수 있습니다.
이벤트 이름도 여러가지만 있지만 중요한 몇가지만 살펴 보겠습니다.
# Pull Request가 생성, 업데이트 된 경우 실행됩니다.
on:
pull_request:
# push가 된 경우 실행됩니다.
on:
push:
# 수동으로 워크플로를 실행할 수 있도록 합니다.
# workflow_dispatch를 사용하면 버튼이 생기고 클릭을 통해 실행할 수 있습니다.
# inputs를 통해서 변수를 입력 받을 수도 있습니다.
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
GitHub Actions에서 jobs는 워크플로 내에서 실행되는 작업 단위를 정의하는 필드입니다. 각 job은 단일 작업의 집합을 표현하며, 서로 독립적으로 실행되거나 특정 조건에 따라 연결될 수 있습니다. 워크플로는 하나 이상의 jobs로 구성됩니다.
jobs:
run-test: # job_id
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: <step_name>
run: <command_or_action>
job_id
각 작업을 식별하는 고유 ID
runs-on
steps
uses
name
run
GitHub Actions에서 runs-on에 macos를 설정하면 GitHub에서 제공하는 가상 macOS 환경에서 워크플로가 실행됩니다. 이 경우, 프로젝트에 tuist, fastlane 등의 같은 도구를 매번 설치가 필요하여 실행 시간이 늘어납니다.
하지만 self hosted runner를 사용하면, 회사에서 제공하는 Mac Mini와 같은 장비에 필요한 도구들을 미리 설치해둘 수 있습니다. 이를 통해 워크플로 실행 시 초기 설치 과정이 생략되어, 실행 시간이 단축되고 효율성이 향상됩니다.
저희 회사의 경우, 빌드용 Mac Mini가 따로 존재했기 때문에 시간 단축과 비용 절감을 위해 self hosted runner 방식을 선택해 사용했습니다. 특히, Apple의 macOS 환경은 다른 플랫폼에 비해 하드웨어 및 라이선스 비용이 높아 GitHub에서 제공하는 가상 macOS 환경의 실행 요금이 상대적으로 더 비쌌습니다.
사실 처음에 macOS 환경에서 돌리다가 해당 달에 청구요금이 높게 책정되어 원인을 찾아보니 macOS가 돌아간 이후부터 가격이 확 올랐었습니다..
생각보다 차이가 많이 나기 때문에 꼭 유의하셔서 사용하기 바랍니다

레포지토리의 코드를 클론, 체크아웃.
- uses: actions/checkout@v4
- name: Run Unit Tests
id: unit-tests
run : |
xcodebuild test \
-workspace Project.xcworkspace \
-scheme Domain \
-destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' \
-enableCodeCoverage YES \
-resultBundlePath ./TestResults.xcresult\
| xcpretty --report html --output TestResults.html 2>&1
continue-on-error: true
id
이 단계의 ID를 정의합니다. 이후 워크 플로우에서 결과를 참조하는 경우 사용되는데 현재 워크플로우의 결과를 steps.{id}.outcom으로 사용하여 이 step이 성공 했는지 실패했는지에 따라서 추후 코멘트로 남기기 위해서 사용합니다.
xcodebuild test
테스트를 실행하기 위한 명령어 입니다.
역슬래시(\)
명령어가 여러 줄로 이어질 때 줄바꿈을 처리하기 위해 사용됩니다. 이 방법은 명령어가 너무 길어 한 줄에 모두 작성하기 어려운 경우 사용되며, 다음 줄도 현재 명령어의 일부임을 나타냅니다.
workspace Project.xcworkspace / project Project.xcodeproj
테스트를 실행할 워크스페이스 파일(.xcworkspace)을 지정합니다. 이 워크스페이스는 Xcode 프로젝트가 포함된 파일입니다.
xcodeproject 파일이라면 -project Project.xcodeproj 이렇게 작성하시면 됩니다.
scheme
실행할 테스트 타겟의 스키마를 지정합니다. 저 같은 경우에는 CleanArchitecture 기반으로 모듈화를 지행하여서 Domain 모듈만을 테스트하려고 스키마를 Domain으로 지정했습니다.
모놀리식 아키텍처를 사용하신다면 프로젝트 스키마를 넣으시면 됩니다.
destination
테스트를 실행할 시뮬레이터 환경을 설정합니다.
enableCodeCoverage
코드 커버리지 생성을 활성화합니다.
resultBundlePath
xcresult 파일을 저장할 경로를 지정합니다.
저는 프로젝트 파일에 바로 TestResult라는 이름으로 xcreslut 파일을 생성했습니다.
xcpretty
xcpretty는 테스트 출력 결과물을 포메팅해주는 도구입니다.
테스트 결과를 HTML파일로 저장합니다.

- name: Upload XCResult
uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: TestResult
path: ./TestResults.html
continue-on-error: true
- name: Output artifact URL
run: |
ARTIFACT_URL=${{ steps.artifact-upload-step.outputs.artifact-url }}
echo "ARTIFACT_URL=$ARTIFACT_URL" >> $GITHUB_ENV
upload-artifact
GitHub Actions에서 워크플로 실행 중 생성된 파일이나 디렉토리를 저장하여 업로드하는 GitHub 제공 공식 액션입니다.
저는 xcpretty로 만든 html 테스트 파일을 업로드합니다.
그리고 아래 Output artifact URL은 해당 파일의 주소를 ARTIFACT_URL 환경 변수에 저장하여 마지막 코멘트 봇에 html파일의 다운로드 링크를 걸기 위함입니다.
- name: Extract Test Result and Coverage
run: |
xcrun xccov view --report --json TestResults.xcresult > test_coverage.json
COVERAGE=$(jq '.lineCoverage * 100' test_coverage.json || echo "0")
COVERAGE=$(printf "%.2f" "$COVERAGE")
echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV
xcrun xccov view 명령어를 사용하여 xcresult 파일의 코드 커버리지 데이터를 JSON 형식으로 변환합니다.
변환된 JSON 데이터에서 lineCoverage 값을 추출한 후, 100을 곱해 퍼센트(%) 값으로 변환합니다. 만약 값이 없거나 실패한 경우에는 기본값으로 0을 출력하도록 처리합니다.
이후, 계산된 커버리지를 소수점 둘째 자리까지 포맷하여 가독성을 높이고, 이를 COVERAGE라는 환경 변수에 저장합니다. 이 환경 변수는 최종적으로 GitHub Comment Bot에서 활용되며, PR(풀 리퀘스트)에 테스트 커버리지 정보를 출력하는 데 사용됩니다.
jq는 JSON 데이터를 필터링, 변환, 처리하기 위한 도구입니다. 사용하려면 설치가 필요합니다.
- name: Post Test Results to PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERAGE: ${{ env.COVERAGE }}
ARTIFACT_URL: ${{ env.ARTIFACT_URL }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
if [ ${{ steps.unit-tests.outcome }} == "success" ]; then
COMMENT_BODY="### ✅ Tests Passed\n"
COMMENT_BODY+="==================================\n"
COMMENT_BODY+="#### 📊 Test Coverage: ${COVERAGE}%\n"
else
COMMENT_BODY="### ❌ Tests Failed\n"
COMMENT_BODY+="==================================\n"
fi
COMMENT_BODY+="[Action 결과 확인](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n"
COMMENT_BODY+="[테스트 결과 확인](${ARTIFACT_URL})\n"
# PR 코멘트 생성
curl -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\": \"$COMMENT_BODY\"}" \
"https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments"
env
PR_NUMBER
현재 워크플로를 실행 중인 Pull Request의 번호를 가져옵니다.
if [ ${{ steps.unit-tests.outcome }} == "success" ]
이전 스탭인 unit-tests가 성공 했다면 else 까지 동작을 실행합니다.
만약 실패했다면 else 부터 fi 까지의 동작을 실행합니다.
COMMENT_BODY
코멘트를 출력할 변수
curl -X POST
코멘트를 생성하는 부분입니다.
body 부분은 아까 작성한 COMMENT_BODY 변수를 넣어주고
url 부분은 코멘트를 남길 PR의 주소입니다.
name: Auto Run TestCode
on:
pull_request:
branches:
- develop
jobs:
run-unitTest:
runs-on: macos-latest
# 체크아웃
steps:
- uses: actions/checkout@v4
# Domain Test 실행
- name: Run Unit Tests
id: unit-tests
run : |
xcodebuild test \
-workspace Project.xcworkspace \
-scheme Domain \
-destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' \
-enableCodeCoverage YES \
-resultBundlePath ./TestResults.xcresult\
| xcpretty --report html --output TestResults.html 2>&1
continue-on-error: true
# 파일 업로드
- name: Upload XCResult
uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: TestResult
path: ./TestResults.html
continue-on-error: true
# Artifact URL
- name: Output artifact URL
run: |
ARTIFACT_URL=${{ steps.artifact-upload-step.outputs.artifact-url }}
echo "ARTIFACT_URL=$ARTIFACT_URL" >> $GITHUB_ENV
continue-on-error: true
# 테스트 커버리지 파싱
- name: Extract Test Result and Coverage
run: |
xcrun xccov view --report --json TestResults.xcresult > test_coverage.json
COVERAGE=$(jq '.lineCoverage * 100' test_coverage.json || echo "0")
COVERAGE=$(printf "%.2f" "$COVERAGE")
echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV
continue-on-error: true
# PR 코멘트 봇
- name: Post Test Results to PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERAGE: ${{ env.COVERAGE }}
ARTIFACT_URL: ${{ env.ARTIFACT_URL }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
ARTIFACT_LINK=""
# 성공 여부 확인
if [ ${{ steps.unit-tests.outcome }} == "success" ]; then
COMMENT_BODY="### ✅ Tests Passed\n"
COMMENT_BODY+="==================================\n"
COMMENT_BODY+="#### 📊 Test Coverage: ${COVERAGE}%\n"
else
COMMENT_BODY="### ❌ Tests Failed\n"
COMMENT_BODY+="==================================\n"
fi
COMMENT_BODY+="[Action 결과 확인](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\n"
COMMENT_BODY+="[테스트 결과 확인](${ARTIFACT_URL})\n"
# PR 코멘트 생성
curl -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\": \"$COMMENT_BODY\"}" \
"https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments"

커버리지는 90%라고 생각해주시면 감사하겠습니다.. 🤣
workflow 작성시 들여쓰기에 신경쓰셔야됩니다. 만약 들여쓰기가 잘못된다면 workflow가 제대로 동작하지 않을 수 있습니다.
추가적으로 위에 yml에는 없지만
self hosted runner를 사용하지 않는다면 매번 xcpretty, jq 같은 도구들을 설치해야됩니다.
Codecov는 위에와 같은 파싱하고 깃허브 봇을 생성하는 작업을 귀찮게 설정할 필요없이 비교적 적은 workflow 작성으로 테스트 자동화 결과를 분석하고 리포트해주는 툴입니다.
전체적인 Codecoverage 측정과 차트 등을 시각적으로 확인할 수 있습니다.

테스트코드가 커버하지 못한 코드 또한 볼 수 있습니다.

PR에 자동으로 결과를 리포트해줍니다.

하지만 유료이기 때문에 회사에서는 사용하기 힘들 수 있습니다.
저는 개인 프로젝트에 적용해 보았는데 설정도 쉽고 시각적으로 보여주는 것이 많아서 좋았습니다
테스트 자동화 그리고 배포 자동화 같은 이런 자동화 프로세스는 프로젝트 마무리 단계 보다는 시작단계에 적용하는 것이 더 큰 이점을 가져온다고 생각합니다.
이유는 자동화는 초반에 도입해야 프로젝트 진행 중 테스트 검증을 쉽게 수행하고, 내부 테스트 배포를 자동화하여 반복 작업을 줄일 수 있기 때문입니다. 반면, 후반부에 도입하면 자동화 프로세스의 장점을 충분히 누리기 어렵고, 도입에 따른 시간 투자 대비 효율성이 떨어질 가능성이 큽니다.
하지만 자동화가 적용되지 않은 프로젝트라고 해서 너무 늦었다고 생각할 필요는 없습니다. 비록 초기에 도입하지 못했더라도, 언제든 도입할 수 있으며, 빠르면 빠를수록 그 효과를 더 잘 누릴 수 있다는 점이 중요합니다.
질문과 피드백은 언제나 환영입니다 🙇🙇