119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
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
|
|
} |