[GitHub] coverage badge
개요
GitHub Actions CI/CD 파이프라인에서 테스트 커버리지를 자동으로 계산하고, GitHub Gist와 Shields.io를 활용하여 README에 동적 커버리지 뱃지를 표시하는 방법을 소개합니다.
뱃지 예시
커버리지 비율에 따라 자동으로 색상이 변경됩니다:
- 🟢 초록색: 80% 이상
- 🟡 노란색: 60-80%
- 🟠 주황색: 40-60%
- 🔴 빨간색: 40% 미만
구현 아키텍처
GitHub Actions
↓
테스트 실행 & 커버리지 계산
↓
coverage.json 생성
↓
GitHub Gist API로 업데이트
↓
Shields.io가 Gist에서 읽음
↓
README 뱃지 표시
1. GitHub Gist 생성
1.1 Gist 생성
- https://gist.github.com/으로 이동
- 새로운 Gist 생성:
- 파일명:
coverage.json - 내용:
{ "schemaVersion": 1, "label": "coverage", "message": "0%", "color": "red" }
- 파일명:
- “Create public gist” 클릭
1.2 Gist ID 확인
생성된 Gist URL에서 ID를 복사합니다:
https://gist.github.com/USERNAME/a1b2c3d4e5f6g7h8i9j0
^^^^^^^^^^^^^^^^^^^^
이 부분이 Gist ID
2. Personal Access Token 생성
2.1 토큰 생성
- https://github.com/settings/tokens로 이동
- “Generate new token (classic)” 클릭
- 설정:
- Note:
coverage-badge-gist(또는 원하는 이름) - Expiration:
1 year(권장) - Scopes:
gist만 체크 ✓
- Note:
2.2 토큰 복사
생성된 토큰(ghp_로 시작)을 복사하여 안전하게 보관합니다.
3. Repository Secrets 설정
3.1 Secret 추가
Repository → Settings → Secrets and variables → Actions로 이동하여 다음 Secrets를 추가합니다:
Secret 1: GIST_SECRET
Name: GIST_SECRET
Value: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Secret 2: GIST_ID (선택사항)
Name: GIST_ID
Value: a1b2c3d4e5f6g7h8i9j0
참고:
GIST_ID는 CI 스크립트에 직접 하드코딩해도 됩니다 (공개 Gist이므로 보안 문제 없음).
4. GitHub Actions 워크플로우 설정
4.1 CI 파일 수정
.github/workflows/ci.yml 파일의 테스트 단계 이후에 다음을 추가합니다:
- name: Test
run: go test -race -timeout=300s -parallel=4 -coverprofile=coverage.out -cover -v ./...
- name: Update Coverage Badge
if: github.ref == 'refs/heads/main' && success()
env:
GIST_SECRET: $
GIST_ID: $
run: |
if [ ! -f coverage.out ]; then
echo "⚠️ No coverage data available, skipping badge update"
exit 0
fi
# Extract coverage percentage
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Coverage: ${COVERAGE}%"
# Determine badge color based on coverage
COLOR=$(awk -v cov="$COVERAGE" 'BEGIN {
if (cov >= 80) print "brightgreen"
else if (cov >= 60) print "yellow"
else if (cov >= 40) print "orange"
else print "red"
}')
echo "Badge color: $COLOR"
# Create JSON for Gist update
JSON=$(jq -n \
--arg coverage "${COVERAGE}%" \
--arg color "${COLOR}" \
'{
"description": "Code coverage badge for common-library/go",
"files": {
"coverage.json": {
"content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"\($coverage)\", \"color\": \"\($color)\"}"
}
}
}')
# Update Gist
RESPONSE=$(curl -s -X PATCH \
-H "Authorization: token ${GIST_SECRET}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$JSON" \
"https://api.github.com/gists/${GIST_ID}")
if echo "$RESPONSE" | jq -e '.id' > /dev/null; then
echo "✅ Coverage badge updated successfully"
echo "Coverage: ${COVERAGE}% (${COLOR})"
else
echo "❌ Failed to update coverage badge"
echo "$RESPONSE" | jq .
exit 1
fi
4.2 스크립트 주요 구성 요소
파일 존재 확인
if [ ! -f coverage.out ]; then
echo "⚠️ No coverage data available, skipping badge update"
exit 0
fi
테스트가 실패했거나 커버리지 파일이 생성되지 않은 경우를 안전하게 처리합니다.
커버리지 계산
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
go tool cover를 사용하여 전체 커버리지 비율을 추출하고 % 기호를 제거합니다.
색상 로직
COLOR=$(awk -v cov="$COVERAGE" 'BEGIN {
if (cov >= 80) print "brightgreen"
else if (cov >= 60) print "yellow"
else if (cov >= 40) print "orange"
else print "red"
}')
awk의 부동소수점 비교를 사용하여 커버리지 비율에 따라 4단계 색상을 결정합니다:
- 80% 이상:
brightgreen - 60-80%:
yellow - 40-60%:
orange - 40% 미만:
red
Gist 업데이트 JSON 생성
JSON=$(jq -n \
--arg coverage "${COVERAGE}%" \
--arg color "${COLOR}" \
'{
"description": "Code coverage badge for common-library/go",
"files": {
"coverage.json": {
"content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"\($coverage)\", \"color\": \"\($color)\"}"
}
}
}')
jq를 사용하여 Gist API 요청에 필요한 JSON을 생성합니다. content 필드 안에 Shields.io 엔드포인트 형식의 JSON이 문자열로 포함됩니다.
Shields.io 엔드포인트 형식
최종적으로 Gist에 저장되는 coverage.json 내용:
{
"schemaVersion": 1,
"label": "coverage",
"message": "85.3%",
"color": "brightgreen"
}
Gist API 호출 및 응답 검증
RESPONSE=$(curl -s -X PATCH \
-H "Authorization: token ${GIST_SECRET}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$JSON" \
"https://api.github.com/gists/${GIST_ID}")
if echo "$RESPONSE" | jq -e '.id' > /dev/null; then
echo "✅ Coverage badge updated successfully"
echo "Coverage: ${COVERAGE}% (${COLOR})"
else
echo "❌ Failed to update coverage badge"
echo "$RESPONSE" | jq .
exit 1
fi
PATCH메서드로 기존 Gist 파일 업데이트- Personal Access Token으로 인증
- 응답 JSON에서
.id필드 존재 여부로 성공 여부 확인 - 실패 시 상세 에러 메시지 출력
5. README에 뱃지 추가
5.1 기본 뱃지

5.2 클릭 가능한 뱃지
Gist 페이지로 링크하려면:
[](https://gist.github.com/USERNAME/GIST_ID)
5.3 스타일 커스터마이징
Shields.io 스타일 옵션:
<!-- flat-square 스타일 -->

<!-- for-the-badge 스타일 -->

<!-- plastic 스타일 -->

6. 테스트 및 검증
6.1 로컬 테스트
로컬에서 스크립트를 테스트하려면 다음 파일을 생성합니다:
test_coverage_update.sh:
#!/bin/bash
set -e
if [ ! -f coverage.out ]; then
echo "⚠️ No coverage data available, skipping badge update"
exit 0
fi
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Coverage: ${COVERAGE}%"
COLOR=$(awk -v cov="$COVERAGE" 'BEGIN {
if (cov >= 80) print "brightgreen"
else if (cov >= 60) print "yellow"
else if (cov >= 40) print "orange"
else print "red"
}')
echo "Badge color: $COLOR"
JSON=$(jq -n \
--arg coverage "${COVERAGE}%" \
--arg color "${COLOR}" \
'{
"description": "Code coverage badge",
"files": {
"coverage.json": {
"content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"\($coverage)\", \"color\": \"\($color)\"}"
}
}
}')
RESPONSE=$(curl -s -X PATCH \
-H "Authorization: token ${GIST_SECRET}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$JSON" \
"https://api.github.com/gists/${GIST_ID}")
if echo "$RESPONSE" | jq -e '.id' > /dev/null; then
echo "✅ Coverage badge updated successfully"
echo "Coverage: ${COVERAGE}% (${COLOR})"
else
echo "❌ Failed to update coverage badge"
echo "$RESPONSE" | jq .
exit 1
fi
실행 방법:
# 환경 변수 설정
export GIST_SECRET="ghp_your_token"
export GIST_ID="your_gist_id"
# 커버리지 생성
go test -coverprofile=coverage.out ./...
# 스크립트 실행
chmod +x test_coverage_update.sh
./test_coverage_update.sh
6.2 CI 동작 확인
- 코드 푸시: main 브랜치에 푸시
- Actions 확인: GitHub Actions 탭에서 워크플로우 실행 확인
- 로그 검증:
Coverage: 85.3% Badge color: brightgreen { "description": "Code coverage badge for common-library/go", "files": { "coverage.json": { "content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"85.3%\", \"color\": \"brightgreen\"}" } } } ✅ Coverage badge updated successfully Coverage: 85.3% (brightgreen)
6.3 뱃지 미리보기
브라우저에서 직접 확인:
https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/coverage.json
7. 트러블슈팅
7.1 Gist 업데이트 실패 (HTTP 404)
원인: GIST_ID가 잘못되었거나 Gist가 삭제됨
해결:
# Gist ID 확인
echo $GIST_ID
# Gist 존재 여부 확인
curl https://gist.github.com/USERNAME/$GIST_ID
7.2 권한 오류 (HTTP 401, 403)
원인: Personal Access Token이 만료되었거나 권한이 부족함
해결:
- Token 재생성 (gist scope 확인)
- Repository Secrets에서
GIST_SECRET업데이트
7.3 뱃지가 표시되지 않음
원인: Gist URL이 잘못되었거나 캐시 문제
해결:
<!-- 캐시 무시 (테스트용) -->

7.4 커버리지가 0%로 표시
원인: coverage.out 파일이 생성되지 않았거나 비어있음
해결:
# 테스트 단계에서 커버리지 파일 생성 확인
- name: Test
run: |
go test -coverprofile=coverage.out ./...
if [ ! -f coverage.out ]; then
echo "❌ coverage.out 파일이 생성되지 않았습니다"
exit 1
fi
echo "✅ coverage.out 파일 생성됨"
8. 고급 활용
8.1 다중 뱃지
여러 메트릭을 별도 Gist로 관리:



8.2 브랜치별 뱃지
# 브랜치 이름을 파일명에 포함
BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')
FILE_NAME="coverage-${BRANCH_NAME}.json"
# Gist 업데이트 시 동적 파일명 사용
-d "{\"files\":{\"${FILE_NAME}\":{\"content\":${JSON_CONTENT}}}}"
8.3 Python 프로젝트 적용
- name: Test with coverage
run: |
pytest --cov=src --cov-report=term-missing | tee coverage.txt
COVERAGE=$(grep "TOTAL" coverage.txt | awk '{print $4}' | sed 's/%//')
# 색상 결정
COLOR=$(awk -v cov="$COVERAGE" 'BEGIN {
if (cov >= 80) print "brightgreen"
else if (cov >= 60) print "yellow"
else if (cov >= 40) print "orange"
else print "red"
}')
# Gist 업데이트
JSON=$(jq -n \
--arg coverage "${COVERAGE}%" \
--arg color "${COLOR}" \
'{
"files": {
"coverage.json": {
"content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"\($coverage)\", \"color\": \"\($color)\"}"
}
}
}')
curl -s -X PATCH \
-H "Authorization: token ${GIST_SECRET}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$JSON" \
"https://api.github.com/gists/${GIST_ID}"
8.4 JavaScript/TypeScript 프로젝트
- name: Test with coverage
run: |
npm test -- --coverage --coverageReporters=text-summary | tee coverage.txt
COVERAGE=$(grep "Statements" coverage.txt | awk '{print $3}' | sed 's/%//')
# 색상 결정
COLOR=$(awk -v cov="$COVERAGE" 'BEGIN {
if (cov >= 80) print "brightgreen"
else if (cov >= 60) print "yellow"
else if (cov >= 40) print "orange"
else print "red"
}')
# Gist 업데이트
JSON=$(jq -n \
--arg coverage "${COVERAGE}%" \
--arg color "${COLOR}" \
'{
"files": {
"coverage.json": {
"content": "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"\($coverage)\", \"color\": \"\($color)\"}"
}
}
}')
curl -s -X PATCH \
-H "Authorization: token ${GIST_SECRET}" \
-H "Accept: application/vnd.github.v3+json" \
-d "$JSON" \
"https://api.github.com/gists/${GIST_ID}"
9. 장점
9.1 무료 솔루션
- GitHub Actions 포함 (월 2,000분 무료)
- GitHub Gist 무료
- Shields.io 무료
9.2 실시간 업데이트
- main 브랜치 푸시 시 자동 업데이트
- 별도 서비스 배포 불필요
9.3 유연성
- JSON 형식으로 완전한 제어
- 색상, 스타일, 로고 커스터마이징 가능
9.4 간단한 구성
- 외부 의존성 최소화
- GitHub 인프라만 활용
10. 대안 비교
| 솔루션 | 비용 | 설정 난이도 | 커스터마이징 |
|---|---|---|---|
| Gist + Shields.io | 무료 | 쉬움 | 높음 |
| Codecov | 무료/유료 | 중간 | 중간 |
| Coveralls | 무료/유료 | 중간 | 낮음 |
| 정적 뱃지 | 무료 | 매우 쉬움 | 낮음 (수동) |