CodeGym /행동 /Go SELF /go tool cover -func/-html

go tool cover -func/-html — 보고서를 해석하는 방법

Go SELF
레벨 31, 레슨 2
사용 가능

1. go tool cover가 필요한 이유: 퍼센트를 지도로 바꾸기

콘솔에서 coverage: 42.9% of statements 같은 문구를 보면, 뇌는 기쁘게 결론을 내리려 합니다. “거의 절반이네, 괜찮다!” 하지만 이것은 책의 무게로 내용을 평가하는 것과 비슷합니다. 숫자는 있지만 의미는 별로 없습니다. go tool cover는 퍼센트를 지도로 바꾸기 위해 필요합니다. 어떤 함수는 테스트가 건드리지 않았는지, 어떤 if 분기가 빨간색인지, 어디에서 “행복한 경로”만 확인하고 오류를 놓쳤는지를 보여 줍니다. 이것은 더 이상 추상적인 메트릭이 아니라 내비게이션입니다.

Coverage 프로파일(예: coverage.out)은 데이터입니다. go tool cover는 그 데이터를 보는 “뷰어”입니다. 그리고 두 가지 핵심 “렌즈”가 있습니다. -func (함수별 표)와 -html (소스 강조 표시)입니다.

작동 구조는 간단합니다:

flowchart LR
    A["go test -coverprofile=coverage.out"] --> B["coverage.out (profile)"]
    B --> C["go tool cover -func=coverage.out"]
    B --> D["go tool cover -html=coverage.out"]
    C --> E["Table: file/function/percentage"]
    D --> F["HTML: green/red/gray"]

2. coverage.out는 어디서 오고, 왜 필요한가

보고서를 해석하기 전에, 데이터의 출처를 이해하는 것이 중요합니다. go tool cover는 테스트를 직접 실행하지 않습니다. 어떤 입력을 썼는지, 어떤 테스트를 실행했는지, 무슨 일이 일어났는지 알지 못합니다. 대신 커버리지 프로파일 파일을 읽을 수 있습니다.

따라서 첫 단계는 항상 같습니다. go test에 -coverprofile 플래그를 붙여 coverage.out를 생성하는 것입니다. Go에서는 이것이 표준 방식입니다. -coverprofile이 자동으로 coverage 수집을 켜 주므로, -cover를 따로 추가할 필요는 없습니다.

작은 학습용 패키지 calc가 있다고 해 봅시다. 우리는 이것을 테스트와 예시를 위한 샌드박스로 사용합니다:

package calc

// Abs는 숫자의 절댓값을 반환한다.
func Abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

그리고 “양수” 시나리오만 커버하는 테스트가 있다고 합시다:

package calc

import "testing"

func TestAbs_Positive(t *testing.T) {
	if Abs(5) != 5 {
		t.Fatalf("Abs(5) must be 5")
	}
}

이제 커버리지 프로파일을 생성합니다. 아래는 IDE 터미널에서 실행하는 명령 예시입니다:

go test -coverprofile=coverage.out

이후에는 전체 퍼센트와 성공적으로 실행되었는지 여부를 볼 수 있고, 무엇보다도 coverage.out 파일이 생깁니다. 이 파일을 이제 go tool cover가 읽습니다.

3. go tool cover -func: 함수별 X-ray

-func 모드는 어디부터 봐야 할지 가장 빠르게 알려 주는 방법입니다. 함수별 커버리지와 전체 결과를 보여 줍니다. “용의자 목록”처럼 생각하면 편합니다. 어디가 0%인지, 어디가 12%인지, 어디가 갑자기 100%인지 확인할 수 있습니다.

중요한 점은 -func가 요약이라는 것입니다. 왜 어떤 줄이 커버되지 않았는지 설명하지도 않고, 어떤 if 분기가 실행되지 않았는지도 보여 주지 않습니다. 대신 초동 진단에는 매우 효율적입니다.

명령은 다음과 같습니다:

go tool cover -func=coverage.out

출력은 대략 이런 형태입니다. 여러분의 수치는 다를 수 있지만, 형식은 같습니다:

abs.go:  Abs    50.0%
total:   (statements) 50.0%

여기서는 각 줄을 계약처럼 읽을 수 있어야 합니다:

  • 왼쪽에는 보통 파일 이름(abs.go)이 옵니다;
  • 그다음은 함수 이름(Abs)입니다;
  • 그다음은 그 함수 내부의 문장(statement) 커버리지 퍼센트입니다.

그리고 total: (statements) 줄은 패키지/프로파일 전체의 합계입니다.

왜 Abs가 50%였을까요? 함수 안에 두 가지 분기, 즉 x < 0인 경우와 “그 외”가 있기 때문입니다. 당신의 테스트는 “그 외”만 통과했습니다. return -x 분기는 아직 건드려지지 않았습니다.

빠른 이해를 위한 표

-func에서 보이는 것 실무적으로 의미하는 것
함수에 0.0% 이 함수는 테스트에서 전혀 실행되지 않았습니다. 또는 커버되지 않는 구간에서만 실행되었습니다. 다만 기본 수준에서는 드문 경우입니다.
함수에 100.0% 함수 내부의 모든 문장이 적어도 한 번 실행되었습니다. 그렇다고 테스트가 좋다는 뜻은 아닙니다.
“중간” 퍼센트 대개 놓친 if 분기, 조기 return, 오류 검사를 의미합니다.
total: (statements) 전체 프로파일의 요약입니다. “전/후” 비교에는 유용하지만, 유일한 기준으로 삼기에는 부족합니다.

4. go tool cover -html: 소스 강조 표시

-func로 의심스러운 함수를 찾은 뒤에는, 어떤 줄이 실행되지 않았는지 구체적으로 알고 싶어집니다. 이때 -html은 소스 코드의 강조 표시가 포함된 HTML 페이지를 생성합니다. 커버된 부분은 보통 초록색, 커버되지 않은 부분은 빨간색으로 표시되고, 때로는 회색의(계측되지 않은) 구간도 보입니다.

이 방식은 테스트가 부족한 지점을 시각적으로 보여 줍니다. 어떤 분기, 어떤 return, 어떤 오류 처리에서 부족한지 알 수 있습니다.

실행 방법은 다음과 같습니다:

go tool cover -html=coverage.out

실행 후에는 보통 브라우저가 열리거나, 파일을 직접 열게 됩니다. 그러면 신호등처럼 색칠된 코드를 보게 됩니다.

HTML 보고서를 차분하게 읽는 법

중요한 점: 빨간색이 “코드가 나쁘다”는 뜻은 아닙니다. 빨간색은 “현재 테스트 세트에서 이 코드가 실행되지 않았다”는 뜻입니다. 이는 다음 중 하나일 수 있습니다:

  • 정말로 놓친 분기;
  • 계약상 실행할 수 없는 분기이므로, 오히려 코드를 단순화해야 하는 경우;
  • 입력/IO 오류에서만 실행되는 분기인데, 아직 테스트하지 않은 경우.

Abs 예시에서 HTML은 return -x 줄을 빨간색으로 보여 줄 것이고, 이것은 매우 구체적인 신호입니다. “음수에 대한 테스트를 추가해라.”

5. 엔지니어링 관점에서 coverage 해석하기: 위험을 닫는 방법

초보자가 가장 자주 빠지는 함정은 퍼센트를 올리기 위해 퍼센트 자체를 쌓는 것입니다. 이렇게 하면 “함수를 호출하고, 안 죽으면 통과”하는 식의 테스트가 생기고, 퍼센트는 예쁘지만 버그는 여전히 잘 살아 있습니다.

올바른 해석은 다릅니다. coverage 보고서는 우리가 놓친 맹점, 특히 분기와 조기 반환 지점을 알려 주는 힌트로 써야 합니다. Coverage는 정답을 증명하지는 못하지만, 무엇을 아예 확인조차 하지 않았는지는 아주 잘 보여 줍니다.

HTML에서 빨간색이 거의 항상 실제 테스트 공백을 의미하는 전형적인 지점들이 있습니다.

if/switch 분기와 “논리의 다른 절반”

if에서 한쪽 분기만 초록색이라면, 이것은 곧바로 “다른 경우도 테스트해라”라는 초대장입니다. 때로는 대안이 음수 시나리오이고, 때로는 경계값입니다.

조기 return과 “오류 검사”

아래와 같은 분기가 빨간색으로 남는 경우가 매우 많습니다:

if err != nil {
	return 0, err
}

부끄러운 일이 아닙니다. 정상입니다. 단지 오류를 한 번도 모델링하지 않았다는 뜻일 뿐입니다. 우리 과정 수준에서는 아직 복잡한 목(mock)을 만들지 않지만, 간단한 함수에서도 오류로 이어지는 테스트 입력을 만들 수 있습니다. 예를 들어 파싱용으로 나쁜 문자열을 넣는 식입니다.

“혹시 몰라서” 넣어 둔 코드

때로는 coverage가 실제로는 절대 실행되면 안 되는 코드 조각을 빨간색으로 보여 줍니다. 예를 들어 “임시 코드”나 “아직 안 지운 부분”입니다. 이 경우 coverage는 “테스트를 작성하라”고 말하는 것이 아니라, “결정하라”고 말합니다. 이 코드가 필요하다면 테스트해야 하고, 필요 없다면 제거해야 합니다.

그리고 코드 삭제도 일종의 최적화입니다. 다만 뇌를 위한 최적화죠.

실행 빈도와 커버리지 모드

때로는 “실행됐는가 아닌가”뿐 아니라 “몇 번 실행됐는가”도 중요합니다. 특히 count 모드로 프로파일을 수집한 뒤 HTML을 열면 더 잘 보입니다. 어디서는 코드가 많이 실행되고, 어디서는 적게 실행되었는지 거의 “열화상 카메라”처럼 볼 수 있습니다.

이 단계에서 기억해야 할 핵심은 이것입니다. -func는 요약을 제공하고, -html은 시각화를 제공하며, 커버리지 모드는 프로파일에 어떤 데이터가 들어가는지를 결정합니다.

하지만 coverage를 성능 프로파일링과 혼동하면 안 됩니다. “자주 실행되었다”는 것은 “느리다”는 뜻이 아닙니다. 그것은 단지 테스트 범위에서 연산이 얼마나 자주 실행되었는지를 뜻합니다.

6. 예제: 보고서 → 테스트 → 커버리지 개선

우리의 calc.Abs로 “전/후” 예제를 만들어, “보고서 → 행동”의 연결이 이론이 아니라 실제라는 것을 보겠습니다.

1단계. 양수 테스트만 있던 상태

함수 코드:

package calc

func Abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

테스트는 이것뿐이었습니다:

package calc

import "testing"

func TestAbs_Positive(t *testing.T) {
	if Abs(5) != 5 {
		t.Fatalf("Abs(5) must be 5")
	}
}

프로파일을 만들고 요약을 봅니다:

go test -coverprofile=coverage.out
go tool cover -func=coverage.out

예상되는 보고서의 논리는 이렇습니다. Abs는 부분적으로만 커버됩니다. 왜냐하면 x < 0 분기가 실행되지 않았기 때문입니다.

2단계. 음수 테스트 추가

두 번째 테스트를 추가합니다:

package calc

import "testing"

func TestAbs_Negative(t *testing.T) {
	if Abs(-7) != 7 {
		t.Fatalf("Abs(-7) must be 7")
	}
}

이제 다시:

go test -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out

눈으로 확인해야 할 것은 다음과 같습니다:

  • -func에서 Abs의 퍼센트가 100%가 되거나 그에 가까워집니다. 파일에 다른 코드가 있으면 약간 달라질 수 있습니다;
  • -html에서 두 분기 모두 초록색이 됩니다.

이것이 바로 올바른 루틴입니다. “퍼센트 경쟁”이 아니라 “빠진 분기를 발견 → 그 분기를 실제로 통과하는 테스트 추가”입니다.

7. go tool cover 해석 시 흔한 실수

실수 1: 전체 total 퍼센트만 보고 세부를 무시하기.
전체 퍼센트는 “전/후” 비교에는 유용하지만, “무엇을 테스트하지 않았는가”라는 질문에는 거의 쓸모가 없습니다. 좋은 습관은 먼저 -func를 빠르게 보고 취약점을 찾은 뒤, -html을 열어 구체적인 빨간 줄을 보는 것입니다.

실수 2: go tool cover를 coverage.out 없이 실행하려고 하기.
go tool cover는 프로파일을 읽는 도구이지 테스트를 실행하는 도구가 아닙니다. 파일이 없거나 오래된 파일이면 오류가 나거나, 어제의 세계를 분석하게 됩니다. 규칙은 간단합니다. 먼저 go test -coverprofile=coverage.out, 그다음에 go tool cover를 사용합니다…

실수 3: 빨간 줄을 “전부 다시 써야 한다”는 신호로 받아들이기.
빨간색은 “테스트에서 실행되지 않았다”는 뜻이지, “망가졌다”는 뜻이 아닙니다. 때로는 경계값 테스트 하나만 추가하면 끝입니다. 때로는 그 분기 자체가 필요 없어서 삭제해야 한다는 뜻입니다.

실수 4: 어떤 대가를 치르더라도 100% coverage를 만들고 의미 없는 테스트를 쓰기.
함수를 호출만 하고 아무것도 검증하지 않는 테스트를 작성해도 종종 퍼센트는 올라갑니다. 하지만 그런 테스트는 버그를 거의 잡지 못하고, 잘못된 안도감을 줍니다. Coverage는 “어디를 볼지”에 대한 힌트이고, 테스트의 품질은 여러분의 검증(want/got)과 명확한 시나리오에 달려 있습니다.

실수 5: 오류 분기와 조기 반환을 잊기.
자주 커버되지 않는 것은 바로 오류 검사입니다. 그 결과 “행복한 경로”는 초록색인데, 어떤 실패라도 프로그램을 놀라게 만들어 버립니다. HTML에서 if err != nil { return … }가 빨간색이라면, 이것은 보통 오류 입력을 만들고 오류가 기대대로 처리되는지 확인하는 별도 테스트의 좋은 후보입니다.

1
과제
Go SELF, 레벨 31, 레슨 2
잠금
함수 요약
함수 요약
1
과제
Go SELF, 레벨 31, 레슨 2
잠금
누락된 분기
누락된 분기
1
과제
Go SELF, 레벨 31, 레슨 2
잠금
목표 커버리지
목표 커버리지
1
과제
Go SELF, 레벨 31, 레슨 2
잠금
Set과 Count
Set과 Count
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION