Reorganize code into individual tool files
This commit is contained in:
119
tool_analyze_test_quality.go
Normal file
119
tool_analyze_test_quality.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Test quality types
|
||||
type TestQualityInfo struct {
|
||||
File string `json:"file"`
|
||||
TestMetrics TestMetrics `json:"metrics"`
|
||||
Issues []TestIssue `json:"issues,omitempty"`
|
||||
Suggestions []string `json:"suggestions,omitempty"`
|
||||
}
|
||||
|
||||
type TestMetrics struct {
|
||||
TotalTests int `json:"total_tests"`
|
||||
TableDriven int `json:"table_driven"`
|
||||
Benchmarks int `json:"benchmarks"`
|
||||
Examples int `json:"examples"`
|
||||
Coverage float64 `json:"estimated_coverage"`
|
||||
}
|
||||
|
||||
type TestIssue struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Severity string `json:"severity"`
|
||||
Position Position `json:"position"`
|
||||
}
|
||||
|
||||
func analyzeTestQuality(dir string) ([]TestQualityInfo, error) {
|
||||
var testQuality []TestQualityInfo
|
||||
|
||||
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
|
||||
if !strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := TestQualityInfo{
|
||||
File: path,
|
||||
}
|
||||
|
||||
for _, decl := range file.Decls {
|
||||
if fn, ok := decl.(*ast.FuncDecl); ok {
|
||||
name := fn.Name.Name
|
||||
if strings.HasPrefix(name, "Test") {
|
||||
info.TestMetrics.TotalTests++
|
||||
|
||||
// Check for table-driven tests
|
||||
if hasTableDrivenPattern(fn) {
|
||||
info.TestMetrics.TableDriven++
|
||||
}
|
||||
|
||||
// Check for proper assertions
|
||||
if !hasProperAssertions(fn) {
|
||||
pos := fset.Position(fn.Pos())
|
||||
info.Issues = append(info.Issues, TestIssue{
|
||||
Type: "weak_assertions",
|
||||
Description: "Test lacks proper assertions",
|
||||
Severity: "medium",
|
||||
Position: newPosition(pos),
|
||||
})
|
||||
}
|
||||
} else if strings.HasPrefix(name, "Benchmark") {
|
||||
info.TestMetrics.Benchmarks++
|
||||
} else if strings.HasPrefix(name, "Example") {
|
||||
info.TestMetrics.Examples++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if info.TestMetrics.TotalTests > 0 {
|
||||
testQuality = append(testQuality, info)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return testQuality, err
|
||||
}
|
||||
|
||||
func hasTableDrivenPattern(fn *ast.FuncDecl) bool {
|
||||
// Look for table-driven test patterns
|
||||
found := false
|
||||
ast.Inspect(fn, func(n ast.Node) bool {
|
||||
if genDecl, ok := n.(*ast.GenDecl); ok {
|
||||
for _, spec := range genDecl.Specs {
|
||||
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
||||
for _, name := range valueSpec.Names {
|
||||
if strings.Contains(name.Name, "test") || strings.Contains(name.Name, "case") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func hasProperAssertions(fn *ast.FuncDecl) bool {
|
||||
// Look for testing.T calls
|
||||
found := false
|
||||
ast.Inspect(fn, func(n ast.Node) bool {
|
||||
if callExpr, ok := n.(*ast.CallExpr); ok {
|
||||
if selExpr, ok := callExpr.Fun.(*ast.SelectorExpr); ok {
|
||||
if ident, ok := selExpr.X.(*ast.Ident); ok && ident.Name == "t" {
|
||||
if selExpr.Sel.Name == "Error" || selExpr.Sel.Name == "Fatal" ||
|
||||
selExpr.Sel.Name == "Fail" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
Reference in New Issue
Block a user