"Go testing 패키지"의 두 판 사이의 차이

228번째 줄: 228번째 줄:
==서브테스트/서브벤치마크==
==서브테스트/서브벤치마크==
T와 B의 Run 메서드를 사용하면, 각각에 대해 개별적으로 함수를 정의할 필요 없이 서브테스트와 서브벤치마크를 정의할 수 있다. 이를 통해 테이블 ​​기반 벤치마크 및 계층적 테스트 생성으로 활용이 가능하다. 또한 공통 셋업과 해체 코드를 공유하는 방법을 제공한다.
T와 B의 Run 메서드를 사용하면, 각각에 대해 개별적으로 함수를 정의할 필요 없이 서브테스트와 서브벤치마크를 정의할 수 있다. 이를 통해 테이블 ​​기반 벤치마크 및 계층적 테스트 생성으로 활용이 가능하다. 또한 공통 셋업과 해체 코드를 공유하는 방법을 제공한다.
<syntaxhighlight lang='go'>
func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}
</syntaxhighlight>


==Main==
==Main==

2023년 6월 6일 (화) 00:02 판

1 개요

Go testing 패키지
  • Go 표준 라이브러리의 하나
  • 자동 테스트를 지원하는 Go 패키지
  • 모든 기능 실행을 자동화하는 "go test" 명령과 함께 사용하기 위한 것이다.
func TestXxx(*testing.T)
  • 여기서 Xxx는 소문자로 시작하지 않는다. 함수 이름은 테스트 루틴을 식별하는 역할을 한다.
  • 이러한 함수 내에서 Error, Fail 또는 관련 메서드를 사용하여 오류를 알린다.
  • 새 테스트 도구 모음을 작성하려면 여기에 설명된 대로 TestXxx 함수가 포함된 파일을 만들고 해당 파일에 "_test.go"로 끝나는 이름을 지정한다. 일반 패키지 빌드에서는 제외되지만 "go test" 명령어가 실행될 때는 포함된다.
  • 테스트 파일은 테스트 중인 것과 동일한 패키지에 있거나 접미사가 "_test"인 해당 패키지에 있을 수 있다.
  • 테스트 파일이 동일한 패키지에 있는 경우 다음 예제와 같이 패키지 내에서 unexported 식별자를 참조할 수 있다.
package abs

import "testing"

func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}
  • 파일이 별도의 "_test" 패키지에 있는 경우 테스트 중인 패키지를 명시적으로 가져와야 하며 exported 식별자만 사용할 수 있다. 이것은 "블랙박스" 테스트로 알려져 있다.
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)
    }
}

2 Benchmark

함수 형식

func BenchmarkXxx(*testing.B)

위와 같은 형식은 벤치마크로 간주되며 -bench 플래그가 입력되면 "go test" 명령어에 의해 실행된다. 벤치마크는 순차적으로 실행된다.

테스트 플래그에 대한 설명은 https://golang.org/cmd/go/#hdr-Testing_flags 을 참조하자.

아래는 벤치마크 함수 예시이다.

func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        rand.Int()
    }
}

벤치마크 함수는 대상 코드를 b.N 번 실행된다. 벤치마크 실행 중에 b.N은, 시간을 안정적으로 측정할 수 있을 만큼 벤치마크 함수가 충분히 오래 지속되도록 조정된다.

BenchmarkRandInt-8   	68453040	        17.8 ns/op

위 결과는 루프당 17.8ns의 속도로 68453040회 실행되었음을 의미한다.

벤치마크를 실행하기 전에 비용이 많이 드는 설정이 필요한 경우에는 타이머를 리셋할 수 있다.

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

벤치마크가 병렬 세팅에서 성능을 테스트해야 하는 경우 RunParallel 헬퍼 함수를 사용할 수 있다. 이러한 벤치마크는 go test -cpu 플래그와 함께 사용하기 위한 것이다.

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

벤치마크 결과 형식에 대한 자세한 사양은 https://golang.org/design/14313-benchmark-format 에 나와 있다 .

https://golang.org/x/perf/cmd 에는 벤치마크 결과 처리를 위한 표준 도구가 있다 . 특히 https://golang.org/x/perf/cmd/benchstat 는 통계적으로 강건(robust)한 A/B 비교를 수행한다.

3 Example

이 패키지는 또한 Example 코드를 실행하고 검증한다. 예시 함수에는 "Output:"으로 시작하고 테스트가 실행될 때 함수의 표준 출력과 비교되는 결론 라인 주석이 포함될 수 있다. (비교시에 앞뒤의 공백은 무시한다.) 다음은 Example의 예시이다.

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

주석 접두어 "Unordered output:"은 "Output:"과 유사하지만 아무 줄이나 매치된다.

func ExamplePerm() {
    for _, value := range Perm(5) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

출력 주석이 없는 Example 함수는 컴파일되지만 실행되지는 않는다.

패키지, 함수 F, 타입 T, 타입 T의 메소드 M에 대한 Example을 선언하는 명명 규칙은 다음과 같다.

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

패키지/유형/함수/메서드에 대한 여러 Example 함수에는 그 이름에 별도의 접미사를 붙일 수 있다. 접미사는 소문자로 시작해야 한다.

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

테스트 파일 내에 Example 함수 1개와 1개 이상의 다른 함수/타입/변수/상수 선언이 있으면서 테스트/벤치마크 함수는 없다면 Example로서 표시된다.

4 Fuzz

형식

func FuzzXxx(*testing.F)

위와 같은 형식은 퍼지 테스트로 간주된다.

예시

func FuzzHex(f *testing.F) {
  for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} {
    f.Add(seed)
  }
  f.Fuzz(func(t *testing.T, in []byte) {
    enc := hex.EncodeToString(in)
    out, err := hex.DecodeString(enc)
    if err != nil {
      t.Fatalf("%v: decode: %v", in, err)
    }
    if !bytes.Equal(in, out) {
      t.Fatalf("%v: not equal after round trip: %v", in, out)
    }
  })
}

fuzz 테스트는 시드 코퍼스 또는 기본으로 실행되는 입력 세트를 유지관리하며, 입력 생성을 시드할 수 있다. 시드 입력은 (*F).Add를 호출하거나 fuzz 테스트가 포함된 패키지 내의 testdata/fuzz/<Name> (여기서 <Name>은 fuzz 테스트의 이름) 디렉토리에 파일을 저장하여 등록할 수 있다. 시드 입력은 선택사항이지만, 코드 커버리지가 좋은 작은 시드 입력 세트가 제공되면 퍼징 엔진이 버그를 더 효율적으로 찾을 수 있다. 이러한 시드 입력은 퍼징을 통해 확인된 버그에 대한 회귀 테스트가 될 수 있다.

fuzz 테스트 내에서 (*F).Fuzz에 전달된 함수는 fuzz 대상으로 간주된다. fuzz 대상은 랜덤 입력에 대한 1개 이상의 매개변수가 붙는 *T 매개변수를 받아야 한다. (*F).Add에 전달되는 인수의 타입은 이러한 매개변수의 타입과 동일해야 한다. fuzz 대상은 테스트와 동일한 방식으로 문제를 발견했다는 신호를 보낼 수 있다. 즉, T.Fail(또는 T.Error 또는 T.Fatal과 같이 그것을 호출하는 모든 메서드)을 호출하거나 패닉 상태에 빠짐으로서 가능하다.

fuzzing이 활성화되면(-fuzz 플래그를 특정 fuzz 테스트와 일치하는 정규식으로 설정하여) 시드 입력을 반복적으로 랜덤으로 변경하여 생성된 인수를 가지고 fuzz 대상을 호출한다. 지원되는 플랫폼에서, 'go test'는 퍼징 커버리지 장치를 가지고 테스트 실행파일을 컴파일한다. 퍼징 엔진은 해당 장치를 사용하여 커버리지를 확장하는 입력을 찾고 캐시하여 버그를 찾을 가능성을 높인다. 주어진 입력에 대해 fuzz 대상이 실패하면 퍼징 엔진은 패키지 디렉토리 내의 testdata/fuzz/<Name> 디렉토리에 있는 파일에 실패를 유발한 입력을 기록한다. 이 파일은 나중에 시드 입력으로 활용된다. 해당 위치에 파일을 쓸 수 없는 경우(예: 디렉터리가 읽기 전용인 경우), fuzzing 엔진은 그 대신에 빌드 캐시 내의 fuzz 캐시 디렉토리에 파일을 기록한다.

퍼징이 비활성화되면, F.Add에 등록된 시드 입력과 testdata/fuzz/<Name>의 시드 입력으로 fuzz 대상이 호출된다. 이 모드에서, fuzz 테스트는 서브테스트가 T.Run 대신 F.Fuzz로 시작되는 일반적인 테스트와 매우 유사하게 작동한다.

퍼징에 대한 문서는 https://go.dev/doc/fuzz 를 참조하자.

5 Skipping

테스트 또는 벤치마크는 *T 또는 *B의 Skip 메서드를 호출하여 런타임에서 skip할 수 있다.

func TestTimeConsuming(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping test in short mode.")
    }
    ...
}
  • *T의 Skip 메서드는 입력이 유효하지 않은 경우 fuzz 대상에서 사용할 수 있지만, 실패하는 입력으로 간주해서는 안 된다. 아래는 그 예시이다.
func FuzzJSONMarshaling(f *testing.F) {
    f.Fuzz(func(t *testing.T, b []byte) {
        var v interface{}
        if err := json.Unmarshal(b, &v); err != nil {
            t.Skip()
        }
        if _, err := json.Marshal(v); err != nil {
            t.Errorf("Marshal: %v", err)
        }
    })
}

6 서브테스트/서브벤치마크

T와 B의 Run 메서드를 사용하면, 각각에 대해 개별적으로 함수를 정의할 필요 없이 서브테스트와 서브벤치마크를 정의할 수 있다. 이를 통해 테이블 ​​기반 벤치마크 및 계층적 테스트 생성으로 활용이 가능하다. 또한 공통 셋업과 해체 코드를 공유하는 방법을 제공한다.

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

7 Main

8 같이 보기

9 참고

문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}