2023. 6. 22. 08:22ㆍProgramming/JAVA, C++, Go, Rust
- 목차
우리는 test driven test를 위해 Unit test 코드를 작성합니다. 이 때 하나의 unit test function을 여러가지 다양한 방식으로 test 하기 위해 여러 parameter들을 table로 만들어 사용합니다. 다음은 그 예입니다.
func TestA(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
value int
}{
{
name: "case 1",
value: 1,
},
{
name: "case 2",
value: 2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// test code here
t.Log(tc.value)
})
}
}
t.Run을 통해 test function에 대한 nested test case들을 정의할 수 있습니다.
위와 같이 작성된 코드를 실행하게 되면 다음과 같은 출력을 얻을 수 있습니다.
~$ go test -v ./...
=== RUN TestA
=== RUN TestA/case_1
=== RUN TestA/case_2
--- PASS: TestA (0.00s)
--- PASS: TestA/case_1 (0.00s)
test_a.go: 12: 1
--- PASS: TestA/case_2 (0.00s(
test_a.go: 12: 2
PASS
...
특정 test case만 구동하고자 한다면, 예를들어 case 2만 구동하려면 다음과 같이 실행합니다.
~$ go test -v ./testA/case_2
이번에는 각 test case들이 병렬적으로 수행될 수 있도록 sub test case을 위한 lambda function 내에 t.Parallel()을 호출해 보겠습니다.
...
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel() // <- 추가
// test code here
t.Log(tc.value)
})
}
}
이제 test를 수행하게 되면 보다 빠르게 (물론 이 test case 자체가 뭔가 하는게 없어서 이를 적용하나 안하나 0.00s의 시간이 걸립니다만) 동작하게 됩니다. (더 heavy한 test case에서 효과를 보실 수 있습니다)
그런데, 이번에는 출력 결과가 조금 이상합니다.
~$ go test -v ./...
=== RUN TestA
=== RUN TestA/case_1
=== RUN TestA/case_2
--- PASS: TestA (0.00s)
--- PASS: TestA/case_1 (0.00s)
test_a.go: 12: 2
--- PASS: TestA/case_2 (0.00s(
test_a.go: 12: 2
PASS
...
각 sub test case들에 사용된 value가 모두 2로 출력됩니다.
만약 위와 같이 값을 check 하지 않았다던가 assert 문을 넣지 않았다면, 모든 sub test case들이 정상적으로 수행 되었다고 '착각' 할 수 있습니다.
매우 위험한 상황입니다.
이는 잘 알려진 'Go gotcha' 입니다. 'Go gotcha'란 Go로 프로그래밍 할때 겪을 수 있는 매우 tricky(까다로운) 것을 의미합니다.
ref. https://autumnrain.tistory.com/entry/Go-gotcha-examples
병렬 수행의 원리를 생각해보면, OpenMPI 등의 library를 사용하는 경우에도 특정 code section을 여러 execution context로 분리하여 실행하는 작업을 하게 됩니다. 즉, t.Paralle()이 선언된 scope는 Go에서는 Go routine으로 수행될 것으로 예상됩니다.
그렇기에 동일한 변수에 대한 reference를 여러 go routine의 코드들이 사용하게 되므로, 결과적으로 같은 하나의 test case만 수행하게 됩니다. 이를 막기 위해서는 tc 값을 복사해서 각각의 go routine들이 동작할 수 있도록 하면 됩니다.
...
for _, tc := range testCases {
tc := tc // 새로운 변수에 값을 복사합니다. (기존 tc 변수를 shadowing 합니다)
t.Run(tc.name, func(t *testing.T) {
t.Parallel() // <- 추가
// test code here
t.Log(tc.value)
})
}
}
'Programming > JAVA, C++, Go, Rust' 카테고리의 다른 글
go profiler pprof 사용법 (0) | 2024.02.06 |
---|---|
Study stuff for Golang (0) | 2023.07.24 |
Go gotcha examples (0) | 2023.06.22 |
Go에서 time의 ticker 사용 (0) | 2023.06.21 |
Go에서 CPU 사용량 측정 (0) | 2023.06.21 |