프로그래밍 언어/Go

[Go] Testing package를 이용한 Test 및 Benchmark

:) :) 2024. 10. 5. 00:17

내 맘대로 계산기 프로그램을 구현하던 중

병렬 처리를 해보고 싶어

병렬 처리의 기준을 세우기 위해 함수의 성능(소요 시간) 평가를 하는 코드를 직접 짜고 있었습니다.

 

이렇게 직접 구현한 모듈은 일단 유지보수하기도 어렵고,

다른 프로젝트에도 편하게 사용할 수 없다고 생각했습니다.

 

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

 

 

 

정리

testing

testing 패키지 내부에 test 및 benchmark type이 존재합니다.

각각 T, B로 지정되어있습니다.

 

 

test type 사용법

  1. (테스트 대상 파일과 동일한 패키지) 이 디렉토리에 이름이 ‘_test.go’로 끝나는 파일을 생성합니다.
    1. 테스트 파일의 package name은 테스트 대상 파일의 package name과 동일합니다.
  2. (테스트 대상 파일과 다른 패키지) 명시적으로 대상 패키지를 import 합니다.
  3. “testing” package도 import 합니다.
  4. 각 테스트 함수는 Test로 시작하며 TestXxx 처럼 대상 함수를 지칭하는 이름은 대문자로 시작해야 합니다.
  5. 인자로 t *testing.T type을 받습니다.
  6. t.Error 혹은 t.Fail 같은 함수를 통해 성공과 실패를 관리합니다.

 

 

benchmark type 사용법

  1. 위의 1,2 과정은 동일
  2. 각 벤치마크 함수는 Benchmark~ 로 시작하며, 이 역시 대상 함수를 지칭하는 이름은 대문자로 시작해야 합니다.
  3. 인자로 b *testing.B type을 받습니다.
  4. 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