You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确在结构体切片中使用Go泛型?单元测试问题求解

问题:Go泛型函数单元测试中多类型测试表的实现问题

我正在开发一个Go项目,这是首次在项目中使用泛型。目前LinearSearch泛型函数本身运行正常,但在创建使用结构体作为测试表的单元测试时遇到问题。

类型定义及待测试函数

type AllowedTypes interface {
  int | int8 | int16 | int32 | int64 | float32 | float64 | string
}

func LinearSearch[T AllowedTypes](value T, array []T) bool {
    for _, e := range array {
        if e == value {
            return true
        }
    }

    return false
}

尝试的测试代码及错误

编写的测试代码如下:

func TestLinearSearch(t *testing.T) {
    type tableProps[T AllowedTypes] struct {
        Name           string
        Value          T
        Array          []T
        ExpectedResult bool
    }

    tables := []tableProps{
        {
            Name:           "Success: Value Exists (int)",
            Value:          5,
            Array:          []interface{}{1, 3, 5, 6, 8},
            ExpectedResult: true,
        },
        {
            Name:           "Success: Value Exists (string)",
            Value:          "y",
            Array:          []interface{}{"a", "c", "f", "g", "y"},
            ExpectedResult: true,
        },
    }

    for _, tc := range tables {
        t.Run(tc.Name, func(t *testing.T) {
            actualResult := LinearSearch(tc.Value, tc.Array)

            require.Equal(t, tc.ExpectedResult, actualResult)
        })
    }
}

运行时出现错误:

cannot use generic type tableProps[T AllowedTypes] without instantiation

尝试将测试表实例化为[]tableProps[AllowedTypes]后,又出现新错误:

cannot use type AllowedTypes outside a type constraint: interface contains type constraints

已知单独实例化特定类型(如[]tableProps[int])可运行,但无法同时支持AllowedTypes下的多种类型测试用例。请问如何在结构体切片中正确使用自定义泛型,以同时测试多种类型?


解决方案

Go中泛型结构体无法直接将不同类型的实例存入同一个切片(切片要求元素类型统一),你可以通过以下两种方式实现多类型测试:

方法一:泛型测试子函数(推荐)

定义一个泛型子函数复用测试逻辑,针对每种AllowedTypes类型单独创建测试表并调用该函数,既能复用代码,又能覆盖所有目标类型。

示例代码:

import (
    "testing"
    "github.com/stretchr/testify/require"
)

func TestLinearSearch(t *testing.T) {
    // 泛型测试子函数,处理单一类型的测试用例
    runTypeTests := func[T AllowedTypes](t *testing.T, cases []struct {
        Name           string
        Value          T
        Array          []T
        ExpectedResult bool
    }) {
        for _, tc := range cases {
            t.Run(tc.Name, func(t *testing.T) {
                actual := LinearSearch(tc.Value, tc.Array)
                require.Equal(t, tc.ExpectedResult, actual)
            })
        }
    }

    // 测试int类型
    t.Run("int类型测试", func(t *testing.T) {
        intCases := []struct {
            Name           string
            Value          int
            Array          []int
            ExpectedResult bool
        }{
            {"存在目标值", 5, []int{1, 3, 5, 6, 8}, true},
            {"不存在目标值", 2, []int{1, 3, 5, 6, 8}, false},
        }
        runTypeTests[int](t, intCases)
    })

    // 测试string类型
    t.Run("string类型测试", func(t *testing.T) {
        strCases := []struct {
            Name           string
            Value          string
            Array          []string
            ExpectedResult bool
        }{
            {"存在目标值", "y", []string{"a", "c", "f", "g", "y"}, true},
            {"不存在目标值", "z", []string{"a", "c", "f", "g", "y"}, false},
        }
        runTypeTests[string](t, strCases)
    })

    // 按需添加其他类型测试,比如float64、int8等
    t.Run("float64类型测试", func(t *testing.T) {
        floatCases := []struct {
            Name           string
            Value          float64
            Array          []float64
            ExpectedResult bool
        }{
            {"存在目标值", 3.14, []float64{1.0, 2.5, 3.14, 4.2}, true},
            {"不存在目标值", 5.0, []float64{1.0, 2.5, 3.14, 4.2}, false},
        }
        runTypeTests[float64](t, floatCases)
    })
}

方法二:接口封装测试逻辑

如果不想为每种类型单独写测试表,可以定义统一的测试用例接口,让不同类型的测试用例实现该接口,再将所有用例存入接口切片执行。不过这种方式需要手动处理类型断言,代码相对繁琐:

示例代码:

import (
    "testing"
    "github.com/stretchr/testify/require"
)

func TestLinearSearch(t *testing.T) {
    type testCase interface {
        run(t *testing.T)
    }

    // int类型测试用例
    type intTestCase struct {
        Name           string
        Value          int
        Array          []int
        ExpectedResult bool
    }

    func (tc intTestCase) run(t *testing.T) {
        actual := LinearSearch(tc.Value, tc.Array)
        require.Equal(t, tc.ExpectedResult, actual)
    }

    // string类型测试用例
    type stringTestCase struct {
        Name           string
        Value          string
        Array          []string
        ExpectedResult bool
    }

    func (tc stringTestCase) run(t *testing.T) {
        actual := LinearSearch(tc.Value, tc.Array)
        require.Equal(t, tc.ExpectedResult, actual)
    }

    // 收集所有测试用例
    allCases := []testCase{
        intTestCase{"存在目标值(int)", 5, []int{1, 3, 5, 6, 8}, true},
        intTestCase{"不存在目标值(int)", 2, []int{1, 3, 5, 6, 8}, false},
        stringTestCase{"存在目标值(string)", "y", []string{"a", "c", "f", "g", "y"}, true},
        stringTestCase{"不存在目标值(string)", "z", []string{"a", "c", "f", "g", "y"}, false},
    }

    // 执行所有测试
    for _, tc := range allCases {
        switch v := tc.(type) {
        case intTestCase:
            t.Run(v.Name, func(t *testing.T) { v.run(t) })
        case stringTestCase:
            t.Run(v.Name, func(t *testing.T) { v.run(t) })
        }
    }
}

方案对比

方法一更贴合Go泛型的设计思路,代码结构清晰,无需额外类型断言,复用性强,适合绝大多数场景。只需按照AllowedTypes中定义的类型逐一添加测试块即可。


内容的提问来源于stack exchange,提问作者new line

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 11:10:25