참조: https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/arrays-and-slices
- 개요
Go 에서 하나의 데이터 타입을 여러 개 저장할 수 있는 array 와 slice 에 대하여 알아본다. 또한 test coverage 와 array, slice 를 비교하는 reflect.DeepEqual 에 대해서도 알아본다.
- Array & Slice Example
array 의 수를 모두 더하면 얼마인지 구하는 기능을 구현해보자.
기능을 완성하고 나면 아래와 같이 array 를 인자로 받아서 단일값으로 반환하는 Sum 과 같은 함수를 호출할 것이다. test 값은 1,2,3,4,5 의 합이므로 기대값은 15이다.
package main
import "testing"
func TestSum(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
}
go test 를 실행하면 Sum 을 구현 하라고 오류가 날 것이다. 아래처럼 컴파일만 통과하도록 한 후 go test 로 기대값과 다를 때 오류를 잘 반환하는지 확인한다.
package main
func Sum(numbers [5]int) int {
return 0
}
이제 Test 를 통과하도록 내용을 제대로 구현해보자.
func Sum(numbers [5]int) int {
sum := 0
for i := 0; i < 5; i++ {
sum += numbers[i]
}
return sum
}
이제 go test 를 다시 수행해보면 결과가 올바르게 나올것이다. 이제 test 를 통과했으니 리팩토링할 사항이 있는지 살펴보자.
func Sum(numbers [5]int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
go 에서는 array 를 탐색할 수 있는 range 구문을 지원한다. range 는 2개 값을 반환하는데 (index, value) 형태를 반환한다. go 에서는 여러 개의 값을 반환할 수 있다. 우리는 array 의 값을 더하는데에만 관심이 있고 index 는 사용하지 않으므로 index 반환부는 _(blank identifier) 로 무시하면 된다.
위의 Sum 함수는 [5]int 형을 인자로 받고 있는데 만약 4개의 값만 더하고 싶어서 [4]int 형을 인자로 넘기면 컴파일이 되지 않는다. 유연성이 너무 떨어지므로 크기에 관계없이 값을 받아 합을 구하도록 함수를 수정해보자.
여태까지 Test 작성 -> 구현 -> 리팩토링을 수행하였다. 리팩토링중 문제가 있음을 인지하고 크기에 관계없이 값을 받을 수 있도록 하기 위해 코드를 변경하려고 한다. 이때 크기가 변경 되어도 코드가 잘 돌아가도록 Test 코드를 작성하는 단계로 회귀한다.
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
위의 코드에서 []int{1,2,3}은 크기를 지정하지 않은 collection 인데 Go 에서는 이를 slice 라고 한다. 컴파일이 되도록 Sum 함수를 수정해보자.
수정시에 2 가지 방법증 하나를 택해야 하는데 첫번째는 기존 함수를 고치는것이다. [5]int 형 인자를 []int 로 고치면 된다. 하지만 만약 이 함수를 다른 누군가가 사용한다면 해당 사용자의 코드는 작동하지 않게 된다. 2번째 방법은 새로운 함수를 만드는것이다.
현재는 우리만 테스트 코드를 작성하고 있으므로 기존 함수의 인자 형을 변경하도록 하자.
func Sum(numbers []int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
위와 같이 고쳐도 Test 코드 컴파일이 되지 않을 것이다. [5]int 와 []int는 다르기 때문인데 Test 코드를 slice 형으로 수정해준다.
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
여기서 한 가지 중요한점이 있는데 테스트 코드를 작성할 때, 테스트를 많이 하려고 작성하는게 아니라 코드가 돌아갈것이라는 자신감과 안정감을 위해 작성한다는 점이다. 현재 5개의 합과 3개의 합을 테스트하는 코드가 있다. 만약 [5]int array 형과 []int slice 형을 둘 다 테스트를 한다면 2 개의 테스트가 의미가 있지만 둘 다 slice 형이므로 하나가 동작하면 slice 형은 동작할것이라는 생각으로 하나를 없애야 한다. 작성한 테스트도 결국 유지보수를 해야하는 비용이라는 측면으로 접근해야 한다.
- Coverage tool
Go 는 자체적으로 coverage tool 을 제공해주는데 테스트에 의해 수행되지 않은 영역을 식별하는데 도움을 준다. go test -cover 를 수행해보자.
slices % go test -cover
PASS
coverage: 100.0% of statements
slice 의 크기가 0 일 때, 0을 반환하는 if 문을 Sum 함수에 추가하면 커버리지가 100%가 안되는것을 확인할 수 있다.
- 새로운 요구사항
slice 들을 인자로 받아 각 slice 의 합을 반환하는 기능을 추가해보자. 예를 들어 SumAll([]int{1,2}, []int{0,9}) 와 같이 넘기면 []int{3, 9} 를 반환한다. Test 코드를 먼저 작성하자.
func TestSumAll(t *testing.T) {
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
go test 를 수행하면 SumAll 함수를 구현하라고 나올것이다.
func SumAll(numbersToSum ...[]int) (sums []int) {
return
}
위와 같이 기본적인 구현만 하면 SumAll 에서는 문제가 없지만 컴파일 문제가 발생하는곳이 하나가 더 있다. got 과 want 를 비교하는 부분인데, Go 는 slice 비교시 equals 연산자 비교를 허용하지 않는다. reflect.DeepEqual 을 이용해야 한다.
func TestSumAll(t *testing.T) {
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
이제 테스트가 pass 하도록 SumAll 내부를 구현해보자.
func SumAll(numbersToSum ...[]int) []int {
lengthOfNumbers := len(numbersToSum)
sums := make([]int, lengthOfNumbers)
for i, numbers := range numbersToSum {
sums[i] = Sum(numbers)
}
return sums
}
slice 생성시 make 를 통해서 생성할 수 있는데 2번째 인자는 capacity 를 나타낸다.
이제 리팩토링을 해보자. make 의 2번째 인자는 capacity를 나타낸다고 했다. 만약 capacity 가 2인 slice 에 sums[10] 와 같이 초과한 index 에 값을 할당하면 error 가 발생한다. Go 는 slice 에 값을 추가한 slice 를 반환하는 append 함수를 제공하는데 이를 이용하면 좀 더 안전하게 slice 에 값을 추가할 수 있다.
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Sum(numbers))
}
return sums
}
'Language > Go' 카테고리의 다른 글
Go - pointers & errors (0) | 2021.12.26 |
---|---|
Go - struct, method, interface (0) | 2021.12.24 |
Go - Iteration & Benchmark (0) | 2021.12.04 |
Go - Go with TDD (0) | 2021.12.04 |
Go - Go 와 Gin 을 활용한 RESTful API 개발 (0) | 2021.11.28 |
댓글