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
常量,mapapierroCodeMessages
包含一个条目。如何在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)}