Golangでテストコードを書いていた際に、地味に詰まったことがあったので共有。
サンプルコード
intのスライスを引数として受け取り、その中から正の整数だけを返す関数をサンプルとして用意しました。
package example
// Example は、テスト用に作成した正の整数だけを返す関数
func Example(nums []int) (ret []int) {
for _, num := range nums {
if num > 0 {
ret = append(ret, num)
}
}
return
}
この関数に対して、下記の様なテストコードを書きます。
(本当はもっとテストケースを用意するべきだと思います。)
package example
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExample(t *testing.T) {
tests := []struct {
input []int
expected []int
}{
{
input: []int{1, -2, 3},
expected: []int{1, 3},
},
{
input: []int{-1, -2, -3},
expected: []int{},
},
}
for _, test := range tests {
result := Example(test.input)
assert.Equal(t, result, test.expected)
}
}
そして、このテストを実行してみると、下記のログが表示されテストは失敗します。
$ go test --run TestExample
--- FAIL: TestExample (0.00s)
example_test.go:27:
Error Trace: example_test.go:27
Error: Not equal:
expected: []int(nil)
actual : []int{}
Diff:
--- Expected
+++ Actual
@@ -1,2 +1,3 @@
-([]int) <nil>
+([]int) {
+}
Test: TestExample
FAIL
exit status 1
どうやら、
期待している値(expected)が、([]int) <nil>
になっているのに対して、
実際に返される値(result)は、([]int) {}
となっているため、テストが失敗しているようです。
nil sliceとnon-nil slice
このテスト失敗の理由を説明するためには、lengthとcapacityが共に0のスライスとして、
nil slice
とnon-nil slice
というものが存在することを説明する必要があります。
この2つのスライスは、見た目上ほとんど同じ挙動なのですが、
その名の通りnilかnilでないかという違いがあります。
package main
import "fmt"
func main() {
// nil sliceになる
var nSlice []int
// non-nil sliceになる
eSlice := []int{}
eSlice2 := make([]int, 0)
fmt.Println(len(nSlice), cap(nSlice), nSlice == nil)
fmt.Println(len(eSlice), cap(eSlice), eSlice == nil)
fmt.Println(len(eSlice2), cap(eSlice2), eSlice2 == nil)
}
0 0 true
0 0 false
0 0 false
テストの失敗の原因
Goの名前付き戻り値にsliceを指定して、何も値を代入しないままreturnした場合、nil sliceを返します。
package main
import (
"fmt"
)
func main() {
slice := example()
fmt.Println(len(slice), cap(slice), slice == nil)
}
func example () (slice []int) {
return
}
最初にお見せしたテストでは、example関数はnil slice
を返すにもかかわらず、
non-nil slice
と比較するようなテストになってしまっていたため、テストが失敗してしまったのです。
シンプルなミスであるにもかかわらず、
nil sliceとnon-nil sliceのことを自分があまり意識していなかったために、
原因に気づくまでに少し時間がかかってしまいました。反省です。
まとめ
- lengthとcapacityが共に0であるsliceには、nilとnon-nilの2種類がある。
- 名前付き戻り値で、sliceをそのままreturnすると、nil sliceになる。