Testing 如何枚举某一类型的常量

Testing 如何枚举某一类型的常量,testing,go,reflection,Testing,Go,Reflection,我想通过测试确保,对于下面定义的每个apierrocode常量,mapapierroCodeMessages包含一个条目。如何在Go中枚举特定类型的所有常量 // APIErrorCode represents the API error code type APIErrorCode int const ( // APIErrorCodeAuthentication represents an authentication error and corresponds with HTTP

我想通过测试确保,对于下面定义的每个
apierrocode
常量,map
apierroCodeMessages
包含一个条目。如何在Go中枚举特定类型的所有常量

// APIErrorCode represents the API error code
type APIErrorCode int

const (
    // APIErrorCodeAuthentication represents an authentication error and corresponds with HTTP 401
    APIErrorCodeAuthentication APIErrorCode = 1000
    // APIErrorCodeInternalError represents an unknown internal error and corresponds with HTTP 500
    APIErrorCodeInternalError APIErrorCode = 1001
)

// APIErrorCodeMessages holds all error messages for APIErrorCodes
var APIErrorCodeMessages = map[APIErrorCode]string{
    APIErrorCodeInternalError: "Internal Error",
}

我研究了
reflect
go/importer
,并尝试了
tools/cmd/stringer
,但没有成功。

除了生成测试的静态代码分析之外,你不能

您只需要在某处维护一个已知类型的列表。最明显的地方可能在您的测试中:

func TestAPICodes(t *testing.T) {
    for _, code := range []APIErrorCode{APIErrorCodeAuthentication, ...} {
        // Do your test here
    }
}
func TestAPICodes(t *testing.T) {
    for code := range APIErrorCodeMessages {
        // Do your tests...
    }
}
如果希望列表的定义更接近代码定义,还可以将其放在主包中:

// APIErrorCode represents the API error code
type APIErrorCode int

const (
    // APIErrorCodeAuthentication represents an authentication error and corresponds with HTTP 401
    APIErrorCodeAuthentication APIErrorCode = 1000
    // APIErrorCodeInternalError represents an unknown internal error and corresponds with HTTP 500
    APIErrorCodeInternalError APIErrorCode = 1001
)

var allCodes = []APIErrorCode{APIErrorCodeAuthentication, ...}
或者,如果您确信您的
apErrorCodeMessages
地图将保持最新,那么您已经有了解决方案。只需在测试中循环该地图:

func TestAPICodes(t *testing.T) {
    for _, code := range []APIErrorCode{APIErrorCodeAuthentication, ...} {
        // Do your test here
    }
}
func TestAPICodes(t *testing.T) {
    for code := range APIErrorCodeMessages {
        // Do your tests...
    }
}
基本概念 该包不提供对导出标识符的访问,因为不能保证它们将链接到可执行二进制文件(因此在运行时可用);更多关于这个:;及

这是一个源代码级别检查。我要做的是编写一个测试,检查错误代码常量的数量是否与映射长度匹配。下面的解决方案将只检查贴图长度。改进的版本(见下文)还可以检查映射中的键是否也与常量声明的值匹配

您可以使用来解析包含错误代码常量的Go文件,这将为您提供一个描述该文件的方法,其中包含常量声明。您只需要遍历它,并计算错误代码常量声明

假设您的原始文件名为
“errcodes.go”
,编写一个名为
“errcodes\u test.go”
的测试文件

这就是测试函数的外观:

func TestMap(t *testing.T) {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "errcodes.go", nil, 0)
    if err != nil {
        t.Errorf("Failed to parse file: %v", err)
        return
    }

    errCodeCount := 0
    // Range through declarations:
    for _, dd := range f.Decls {
        if gd, ok := dd.(*ast.GenDecl); ok {
            // Find constant declrations:
            if gd.Tok == token.CONST {
                for _, sp := range gd.Specs {
                    if valSp, ok := sp.(*ast.ValueSpec); ok {
                        for _, name := range valSp.Names {
                            // Count those that start with "APIErrorCode"
                            if strings.HasPrefix(name.Name, "APIErrorCode") {
                                errCodeCount++
                            }
                        }
                    }
                }
            }
        }
    }
    if exp, got := errCodeCount, len(APIErrorCodeMessages); exp != got {
        t.Errorf("Expected %d err codes, got: %d", exp, got)
    }
}
运行
go测试
将导致:

--- FAIL: TestMap (0.00s)
    errcodes_test.go:39: Expected 2 err codes, got: 1
测试正确地揭示了有2个常量错误代码声明,但是
apierrordecomessages
map只包含1个条目

如果我们现在“完成”地图:

然后再次运行
go test

PASS
注意:这是一个风格问题,但大循环可以这样编写以降低嵌套级别:

// Range through declarations:
for _, dd := range f.Decls {
    gd, ok := dd.(*ast.GenDecl)
    if !ok {
        continue
    }
    // Find constant declrations:
    if gd.Tok != token.CONST {
        continue
    }
    for _, sp := range gd.Specs {
        valSp, ok := sp.(*ast.ValueSpec)
        if !ok {
            continue
        }
        for _, name := range valSp.Names {
            // Count those that start with "APIErrorCode"
            if strings.HasPrefix(name.Name, "APIErrorCode") {
                errCodeCount++
            }
        }
    }
}
全面、改进的检测 这次我们将检查常量的确切类型,而不是它们的名称。我们还将收集所有常量值,最后我们将检查每个常量值是否在映射中。如果缺少某些内容,我们将打印缺少代码的确切值

这就是:

func TestMap(t *testing.T) {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "errcodes.go", nil, 0)
    if err != nil {
        t.Errorf("Failed to parse file: %v", err)
        return
    }

    var keys []APIErrorCode
    // Range through declarations:
    for _, dd := range f.Decls {
        gd, ok := dd.(*ast.GenDecl)
        if !ok {
            continue
        }
        // Find constant declrations:
        if gd.Tok != token.CONST {
            continue
        }
        for _, sp := range gd.Specs {
            // Filter by APIErrorCode type:
            valSp, ok := sp.(*ast.ValueSpec)
            if !ok {
                continue
            }
            if id, ok2 := valSp.Type.(*ast.Ident); !ok2 ||
                id.Name != "APIErrorCode" {
                continue
            }
            // And gather the constant values in keys:
            for _, value := range valSp.Values {
                bslit, ok := value.(*ast.BasicLit)
                if !ok {
                    continue
                }
                keyValue, err := strconv.Atoi(bslit.Value)
                if err != nil {
                    t.Errorf("Could not parse value from %v: %v",
                        bslit.Value, err)
                }
                keys = append(keys, APIErrorCode(keyValue))
            }
        }
    }

    for _, key := range keys {
        if _, found := APIErrorCodeMessages[key]; !found {
            t.Errorf("Could not found key in map: %v", key)
        }
    }
}
使用“不完整”的APIRErrorCodeMessages映射运行
go test
,我们得到以下输出:

--- FAIL: TestMap (0.00s)
    errcodes_test.go:58: Could not found key in map: 1000

理想情况下,已知列表将是单个常量值的定义,因此不必保留冗余信息。关于您最后的评论,测试的整个想法是确保
apierrordecomessages
是最新的:)@fhe:我理解您的愿望!遗憾的是,除了源代码分析之外,没有办法做到这一点。(尽管有大量的Go源代码分析工具,因此创建一个工具来实现这一点并不超出可能的范围。但这必须是一个预编译步骤)谢谢,@icza–这是我一直在寻找的!仅供参考,基于类型名的完整检测要求使用类型指定常量,而不是使用简写符号,在第一次声明后可以省略类型。基本检测不需要这样做,因为它只查看名称。@fhe如果您在
apierroCodeInternalError=1001
中从
const
声明中省略类型,它将不是
apierrocode
类型,而是一个非类型化的整数常量,这就是为什么完整版本不会选择它的原因(因为它检查类型为
apierrocode
)的常量。请参见示例以验证:这不是您想要的吗?
对于k,v:=范围APIErrorCodeMessages{fmt.Printf(“%T%T”,k,v)}