Fuzz test
Fuzz 테스팅은 무작위 데이터를 반복적으로 입력하여 동작의 실패를 유도하여 취약점을 찾아내는 테스트 기법입니다. Go 언어에서는 1.18 버전 부터 언어 차원에서 Fuzz 테스팅을 지원하고있습니다.
Fuzz test 작성 규칙
- Fuzz 테스트 함수명은
Fuzz로 시작합니다.
- 테스트 함수는
*testing.F 만을 매개변수로 받습니다.
예시
Fuzz 테스팅을 통해 함수를 개선하는 과정을 진행 해 보겠습니다.
최초 작성된 함수
1
2
3
4
5
6
7
8
9
10
|
func Equal(a, b string) bool {
ab, bb := []byte(a), []byte(b)
for i, v := range ab {
if bb[i] != v {
return false
}
}
return true
}
|
위 함수는 쉬운 길을 내버려두고 두 string을 []byte 로 변환하여 비교하는 함수입니다.
이 함수를 테스트 해 봅시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func TestEqual(t *testing.T) {
tests := []struct {
a, b string
result bool
}{
{"abc", "abc", true},
{"abc", "abd", false},
}
for _, test := range tests {
result := Equal(test.a, test.b)
if result != test.result {
t.Errorf("Equal(%v, %v) = %v, want %v", test.a, test.b, result, test.result)
}
}
}
|
이상적인 값이 들어가는 두 개의 케이스를 검증 하는 테스트를 작성했습니다.

테스트도 통과 했네요! 하지만 우리는 예외적인 입력값을 테스트 해보기 위해 Fuzz 테스트를 추가 해 보기로 했습니다.
Fuzz 테스트
1
2
3
4
5
|
func FuzzEqual(f *testing.F) {
f.Fuzz(func(t *testing.T, a, b string) {
Equal(a, b)
})
}
|
Fuzz 테스트는 아래의 명령어로 쉽게 실행할 수 있습니다.
실행 결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
❯ go test -fuzz .
fuzz: elapsed: 0s, gathering baseline coverage: 0/4 completed
fuzz: elapsed: 0s, gathering baseline coverage: 4/4 completed, now fuzzing with 10 workers
fuzz: minimizing 56-byte failing input file
fuzz: elapsed: 0s, minimizing
--- FAIL: FuzzEqual (0.04s)
--- FAIL: FuzzEqual (0.00s)
testing.go:1485: panic: runtime error: index out of range [1] with length 1
...(생략)
Failing input written to testdata/fuzz/FuzzEqual/469ce7fee3611e98
To re-run:
go test -run=FuzzEqual/469ce7fee3611e98
|
Fuzz 테스트에 실패 했네요! Fuzz 테스트에 실패 한 경우 실행결과 하단에 나오는 testdata 폴더 아래에 저장된 파일에 실패했을 때 사용된 값이 기록됩니다.
1
2
3
|
go test fuzz v1
string("a0")
string("a")
|
testdata에 기록된 실패한 케이스와 에러메시지를 보니 매개변수로 들어오는 두 string 의 길이가 다를 경우 panic이 발생하는걸 확인 할 수 있었습니다.
함수 개선
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func Equal(a, b string) bool {
if len(a) != len(b) {
return false
}
ab, bb := []byte(a), []byte(b)
for i, v := range ab {
if bb[i] != v {
return false
}
}
return true
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func TestEqual(t *testing.T) {
tests := []struct {
a, b string
result bool
}{
{"abc", "abc", true},
{"abc", "abd", false},
{"a0", "a", false},
}
for _, test := range tests {
result := Equal(test.a, test.b)
if result != test.result {
t.Errorf("Equal(%v, %v) = %v, want %v", test.a, test.b, result, test.result)
}
}
}
|
이번 Fuzz 테스트로 발견된 이슈로 매개변수로 들어온 두 string의 길이가 같은지 검증하는 코드가 추가되고, 이에 대응하는 Test Case 도 추가하여 코드를 개선하였습니다.
정리
Fuzz 테스트는 예외처리가 부족하여 나타날 수 있는 Panic 을 방지하는데 유용하여 더 튼튼한 코드를 작성하는데에 도움을 줍니다.
예를들어 SQL을 변수와 함께 사용하는 경우가 있습니다.
다만 Fuzz 테스트는 오류가 나타날 때 까지 랜덤한 값을 무작위로 넣어본다는 테스트 특성상 계속 늘어질 수 있으므로 생산성을 위해 적절한 시간을 넣는 것이 중요합니다.
또한 컴퓨팅 파워를 많이 사용하므로 CI/CD 같은 자동화 프로세스에 넣기 전에는 주의 하여야 합니다.