내 맘대로 계산기 프로그램을 구현하던 중
병렬 처리를 해보고 싶어
병렬 처리의 기준을 세우기 위해 함수의 성능(소요 시간) 평가를 하는 코드를 직접 짜고 있었습니다.
이렇게 직접 구현한 모듈은 일단 유지보수하기도 어렵고,
다른 프로젝트에도 편하게 사용할 수 없다고 생각했습니다.
Go 언어에서 제공하는 표준 test 및 benchmark는 위의 단점을
'표준'이라는 성격으로 상쇄한다고 생각해
testing package의 test 및 benchmark를 찾아보게 되었습니다.
본 포스트는 Go의 test 와 benchmark에 대해 공부한 내용을 담고 있습니다.
‘Testing’ package
go에서 자동화된 테스트 기능을 제공하는 패키지입니다.
아래 ‘go test’ 명령으로 모든 테스트를 자동화하여 수행하도록 설계되었습니다.
$ go test
테스트 실행 시 자동으로 실행하는 대상 함수는 다음과 같습니다.
아래처럼 인자에 *testing.T 를 가지고 있습니다.
func TestXxx(*testing.T)
함수의 이름은 Test로 시작하고, 그 뒤 테스트 대상 함수가 되는 함수의 이름은
대문자로 시작해야 합니다.
그래야 test routine이 함수의 이름을 식별할 수 있습니다.
이러한 테스트 함수 내에서
Error 나 Fail 혹은 실패를 알리는 여타 방법을 사용해
테스트 성공 유무를 알립니다.
아래처럼 TestXxx 함수를 만들면, 해당 Go 파일의 이름은 ‘_test.go’ 로 끝나야 합니다.
이러한 테스트 파일은 빌드 단계에서 제외되지만
go test 명령에서 실행에 포함됩니다.
테스트 파일은 보통 테스트 대상 go파일과 함께 있거나
‘_test’ 로 끝나는 패키지에 존재합니다.
테스트 파일이 동일한 패키지에 있다면,
패키지 내의 unexported 변수, 함수를 테스트에 포함할 수 있습니다.
// Abs_test.go
package abs
import "testing"
func TestAbs(t *testing.T) {
got := Abs(-1)
if got != 1 {
t.Errorf("Abs(-1) = %d; want 1", got)
}
}
그러나 테스트 함수가 아래처럼 분리된 ‘_test’ 패키지에 존재한다면,
테스트 대상 함수는 대상 함수가 존재하는 패키지와 함께
명시적으로 import 해야 합니다.
또한 대상 함수에서 exported 된 identifiers(e.g. 변수, 함수) 만 사용할 수 있습니다.
이러한 테스트를 **black box 테스팅**이라고 합니다.
package abs_test
import (
"testing"
"path_to_pkg/abs"
)
func TestAbs(t *testing.T) {
got := abs.Abs(-1)
if got != 1 {
t.Errorf("Abs(-1) = %d; want 1", got)
}
}
Benchmarks
testing package의 type 중 하나입니다.
아래처럼 사용합니다.
func BenchmarkRandInt(b *testing.B) {
for range b.N {
rand.Int()
}
}
go test 명령에 -bench 플래그를 줄 때 실행됩니다.
$ go test -bench
벤치마크 기능은 벤치마크 대상 함수를 b.N번 실행시켜
실행 당 평균 수행시간을 나타냅니다.
벤치마크 기능이 수행시간의 평균을 안정적으로 낼 수 있을 만큼
벤치마크 엔진이 충분한 반복 횟수(b.N)를 자동으로 지정합니다.
보통 1초에 가까운 시간동안 실행되도록 b.N을 조정하려 한다고 합니다.
수행 시 출력의 예시는 아래와 같습니다.
BenchmarkRandInt-8 68453040 17.8 ns/op
8개의 병렬 고루틴으로 68453040 번 동안 RandInt 함수를 수행하였으며,
각 수행마다 평균적으로 17.8ns가 소요되었다는 의미입니다.
벤치마크는 함수의 성능을 측정하기 위해 실행 시간을 기록합니다(제 계산기 함수 성능 측정에 걸맞는 기능인 것 같습니다).
그런데 벤치마크 준비 과정에서
테스트 실행 전 준비 작업(초기화 및 객체 생성 등)이 오래 걸릴 때가 있습니다.
이 준비 작업이 함수의 성능 측정에 포함되지 않도록 해야할 때가 있는데,
이럴 때 b.ResetTimer( ) 를 통해 실제로 성능을 측정할 loop의 시작 바로 전부터
타이머를 다시 시작할 수 있습니다.
func BenchmarkBigLen(b *testing.B) {
big := NewBig() // 이게 실행 과정에 포함되지 않도록
b.ResetTimer() // 아래 loop전에 타이머 초기화
for range b.N {
big.Len() // 따라서 여기만 성능 측정의 대상으로 삼는다.
}
}
etc. of benchmark
- 벤치마크 결과 형식에 대한 자세한 사양은
- https://golang.org/design/14313-benchmark-format 에서 확인할 수 있습니다.
- https://golang.org/x/perf/cmd 에 벤치마크 결과를 다루는 표준 도구가 있습니다.
- 특히 benchstat은 통계적으로 강건한(robust) A/B 테스트를 수행합니다.
- 재밌어 보이네요!
정리
testing
testing 패키지 내부에 test 및 benchmark type이 존재합니다.
각각 T, B로 지정되어있습니다.
test type 사용법
- (테스트 대상 파일과 동일한 패키지) 이 디렉토리에 이름이 ‘_test.go’로 끝나는 파일을 생성합니다.
- 테스트 파일의 package name은 테스트 대상 파일의 package name과 동일합니다.
- (테스트 대상 파일과 다른 패키지) 명시적으로 대상 패키지를 import 합니다.
- “testing” package도 import 합니다.
- 각 테스트 함수는 Test로 시작하며 TestXxx 처럼 대상 함수를 지칭하는 이름은 대문자로 시작해야 합니다.
- 인자로 t *testing.T type을 받습니다.
- t.Error 혹은 t.Fail 같은 함수를 통해 성공과 실패를 관리합니다.
benchmark type 사용법
- 위의 1,2 과정은 동일
- 각 벤치마크 함수는 Benchmark~ 로 시작하며, 이 역시 대상 함수를 지칭하는 이름은 대문자로 시작해야 합니다.
- 인자로 b *testing.B type을 받습니다.
- b.N 이 loop 횟수가 될 것입니다.
Ref.
testing package - testing - Go Packages
Discover Packages Standard library testing Version: go1.23.2 Opens a new window with list of versions in this module. Published: Oct 1, 2024 License: BSD-3-Clause Opens a new window with license information. Imports: 25 Opens a new window with list of impo
pkg.go.dev
'프로그래밍 언어 > Go' 카테고리의 다른 글
[Go] 내 맘대로 계산기 프로그램 만들기 [4] - goroutines 이용 병렬 처리 (1) | 2024.10.05 |
---|---|
[Go] 내맘대로 계산기 프로그램 만들기 [3] - testing package 적용 테스트 (1) | 2024.10.05 |
[Go] 네트워크 프로그래밍 (0) | 2024.10.04 |
[Go] 내 맘대로 계산기 프로그램 만들기 [2] - 사칙연산 및 소요시간 테스트 (1) | 2024.10.04 |
[Go] 내맘대로 계산기 프로그램 만들기 [1] - ASCII ART (0) | 2024.10.04 |