Home [Go/testing] golang에서 test code를 짜보자
Post
Cancel

[Go/testing] golang에서 test code를 짜보자

golang에서는 테스트 코드를 짤 수 있도록 testing이란 패키지를 지원해 준다.
오늘은 이 testing패키지를 이용해서 테스트 코드를 짜고 각 함수의 성능을 비교해보는 benchmark까지 다뤄보겠다.

testing

testing패키지는 고언어의 표준 패키지이기 때문에 따로 받아올 필요가 없다. 그저 testing패키지를 임포트 해주고 사용하면 된다. 먼저 테스트 코드의 예시를 보겠다.

fibo_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package fibo_test

import (
	"testing"

	"github.com/j1mmyson/testing/fibo"
)

func TestFibo(t *testing.T) {
	ans := fibo.GetFibo(50)

	if ans != 12586269025 {
		t.Errorf("Fibo(50) = %d; want 12586269025\n", ans)
	}
}

Go에서 testing패키지를 이용할 때는 몇가지 규칙을 지켜야 한다.

  1. 파일명을 *_test.go로 지을 것.
  2. 패키지명을 *_test로 지을 것.
  3. 테스트 함수명을 func TestXxx(t *testing.T)의 형태로 지을 것. (UpperCamelCase)

테스트 코드를 짜보았다. GetFibo(50)의 결괏값을 받아와서 그 값이 12586269025가 아니면 에러를 반환하는 코드이다. 이제 테스트 코드 속에 보이는 GetFibo() 함수를 짜보자. 테스트 코드를 통해 우리가 원하는 GetFibo()함수의 형태는 정수형 변수를 인자로 받아 정수를 반환해 주는 함수란 것을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
package fibo

func GetFibo(n int) int {
	n1, n2 := 1, 1

	for i := 0; i < n-2; i++ {
		n1, n2 = n2, n1+n2
	}

	return n2
}

GetFibo()함수도 완성시켰으니 이제 테스트를 시작해보자.
테스트는 테스트 코드가 존재하는 디렉토리에 들어가서 $ go test 명령을 실행시켜주면 된다. 실행시켜보면 아래와 같은 결과를 얻을 수 있다.

1
2
3
$ go test
PASS
ok      github.com/j1mmyson/testing/fibo        0.010s

-v 태그를 붙여서 세부사항까지 확인할 수 있다.

1
2
3
4
5
$ go test -v
=== RUN   TestFibo
--- PASS: TestFibo (0.00s)
PASS
ok      github.com/j1mmyson/testing/fibo        0.009

코드를 잘못 짜 테스트가 실패하면 어떻게 될까? 원하는 출력값이 나오지 않도록 임의로 조정한 후 테스트 코드를 돌리면 아래와 같은 결과를 출력해 준다.

1
2
3
4
5
6
$ go test
--- FAIL: TestFibo (0.00s)
    fibo_test.go:13: Fibo(50) = 12586269025; want 1258626902
FAIL
exit status 1
FAIL    github.com/j1mmyson/testing/fibo        0.006s

-cover플래그를 통해 테스트 코드의 coverage까지 구할 수 있다. coverrage란 이 프로그램에서 내가 어느 정도까지 테스트 코드를 짰느냐를 나타내는 수치이다. 현재 코드에서 $ go test -cover를 실행시키면 아래처럼 커버리지가 100%라고 나온다.

1
2
3
4
$ go test -cover
PASS
coverage: 100.0% of statements
ok      github.com/j1mmyson/testing/fibo        0.007s

그렇다면 fibo패키지에 임의의 함수를 하나 추가해보자.

1
2
3
4
5
6
7
8
9
package fibo

import "fmt"

func GetFibo(n int) int {...}

func PrintHello() {
	fmt.Println("Hello world!")
}

그러고 다시 테스트 코드를 돌려보면 coverage가 떨어진 것을 확인할 수 있다. PrintHello()함수에 대한 테스트 코드가 짜여있지 않기 때문이다.

1
2
3
4
$ go test -cover
PASS
coverage: 80.0% of statements
ok      github.com/j1mmyson/testing/fibo        0.006s

benchmark

testing패키지에서는 함수의 성능을 측정할 수 있는 벤치마킹 기능도 제공을 해준다. 앞선 코드에서 n 번 째 피보나치의 수를 구해주는 GetFibo()함수를 구현했었는데, 조금 다른 방법으로 같은 기능을 구현하는 함수를 하나 더 짜보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func GetFibo(n int) int {
	n1, n2 := 1, 1

	for i := 0; i < n-2; i++ {
		n1, n2 = n2, n1+n2
	}

	return n2
}

func GetFibo2(n int) int {
	var slice []int
	slice = []int{1, 1}

	for i := 0; i < n-2; i++ {
		slice = append(slice, slice[i]+slice[i+1])
	}

	return slice[len(slice)-1]
}

자, 첫 번째 함수의 경우에는 두 변수를 선언하고 루프를 돌면서 재할당을 반복하는 방식으로 피보나치 수를 구하게끔 구현을 하였고
두 번째 함수에서는 slice를 선언하여 dp 알고리즘으로 슬라이스의 뒤쪽에 계속해서 다음 피보나치 수를 붙여가는 방법을 구현해보았다.
1번 함수의 경우

1
두 변수 선언 + n * (합연산 + 재할당)

2번 함수의 경우

1
슬라이스 선언 + n * (슬라이스에 접근 + 합연산 + 할당) + (중간중간 슬라이스의 용량 늘리기)

과 같은 진행 구조를 가질 것 같다. 이렇게 보면 당연히 1번 함수가 더 빠를 것이라고 예상할 수는 있지만 정확히 얼마나 빠른지 알기는 어려울 것 같다. 이렇게 함수를 구현했으니 벤치마킹 코드도 짜보자.

fibo_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
...

func BenchmarkFibo(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fibo.GetFibo(50)
	}
}

func BenchmarkFibo2(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fibo.GetFibo2(50)
	}
}

각 함수의 인자에 50을 넣은 연산을 testing패키지에서 제공하는 임의의 수 b.N만큼 반복을 돌려 함수의 성능을 측정해 준다는 의미이다. 이제 벤치마킹을 진행해보자.
벤치마킹을 해주는 명령은 go test -bench=.이다. 명령을 실행해보면

1
2
3
4
5
6
7
8
9
$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/j1mmyson/testing/fibo
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
BenchmarkFibo-8         60288513                20.00 ns/op
BenchmarkFibo2-8         2552655               885.9 ns/op
PASS
ok      github.com/j1mmyson/testing/fibo        4.978s

이렇게 출력을 해준다. 보면 테스트를 돌린 환경이 나오고 중간쯤 보면 BenchmarkFibo-8 ~~~, BenchmarkFibo2-8 ~~~ 부분을 확인할 수 있는데 해당 부분에서 각 함수의 성능을 알 수 있다. 보시다시피 첫 번째 함수의 벤치마킹은 20.00 ns/op, 두 번째 함수는 885. ns/op가 나온 것을 확인할 수 있고 이를 보고 확실하게 “아! 이 기능은 첫 번째 함수 방식으로 짜는 게 더 성능이 좋구나!”라고 결론을 내릴 수 있게 되는 것이다.
속도뿐만 아니라 메모리 사용량에 대해서도 벤치마킹해보고 싶다면 아래처럼 명령을 내리면 확인할 수 있다.

1
2
3
4
5
6
7
8
9
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: github.com/j1mmyson/testing/fibo
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
BenchmarkFibo-8         59181552                19.45 ns/op            0 B/op          0 allocs/op
BenchmarkFibo2-8         2588463               838.1 ns/op           992 B/op          5 allocs/op
PASS
ok      github.com/j1mmyson/testing/fibo        4.631s
This post is licensed under CC BY 4.0 by the author.

[Go/gin/embed] gin 프레임워크에서 embed패키지 사용해서 static file serving하기

[vuejs/golang] When the syntax of golang template and vue.js overlaps