我希望通过测试确保对于每个APIErrorCode
常量定义如下,地图APIErrorCodeMessages
包含一个条目。如何在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
但未成功。
答案 0 :(得分:1)
reflect
包不提供对导出标识符的访问,因为不能保证它们将链接到可执行二进制文件(因此在运行时可用);更多内容:Splitting client/server code;和How to remove unused code at compile time?
这是源代码级别检查。我要做的是编写一个测试,检查错误代码常量的数量是否与地图长度匹配。以下解决方案仅检查地图长度。改进版本(见下文)也可以检查地图中的键是否也与常量声明的值匹配。
您可以使用go/parser
来解析包含错误代码常量的Go文件,它会为您提供描述文件的ast.File
,其中包含常量声明。您只需要遍历它,并计算错误代码常量声明。
假设您的原始文件名为"errcodes.go"
,请写一个名为"errcodes_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 test
将导致:
--- FAIL: TestMap (0.00s)
errcodes_test.go:39: Expected 2 err codes, got: 1
测试正确显示有2个常量错误代码声明,但APIErrorCodeMessages
映射只包含1个条目。
如果我们现在“完成”地图:
var APIErrorCodeMessages = map[APIErrorCode]string{
APIErrorCodeInternalError: "Internal Error",
APIErrorCodeAuthentication: "asdf",
}
再次运行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)
}
}
}
使用“不完整”go test
地图运行APIErrorCodeMessages
,我们得到以下输出:
--- FAIL: TestMap (0.00s)
errcodes_test.go:58: Could not found key in map: 1000
答案 1 :(得分:0)
缺少静态代码分析,无法生成测试,您可以
您只需要在某处维护已知类型的列表。最明显的地方可能是你的考试:
func TestAPICodes(t *testing.T) {
for _, code := range []APIErrorCode{APIErrorCodeAuthentication, ...} {
// Do your test here
}
}
如果您希望列表更接近代码定义,您也可以将它放在主包中:
// 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, ...}
或者,如果您确信自己的APIErrorCodeMessages
地图会保持最新状态,那么您已经拥有了解决方案。只需在测试中循环遍历该地图:
func TestAPICodes(t *testing.T) {
for code := range APIErrorCodeMessages {
// Do your tests...
}
}