# 3B.5 Unit Test及AST可视化

在了解了语法树的数据结构与基本解析思路之后，就没有必要继续钻入各个代码角落去学习解析细节了。此时更好的方式是反向学习：通过 UT 来调用解析函数，查看各种代码块的解析结果。

&#x20;语法分析器很容易进行单元测试，因为其本质上只需要一段代码作为输入，对运行环境没有额外的要求，因此我们不需要做太多的初始化工作。测试文件 parsertest.go 中已经包含了很多测试用例可供参考，可以直接运行。但在运行 UT 或者构建自己的 UT 之前，我们先来看一下如何更好的查看语法树的内容，毕竟其内嵌结构可能很深。

&#x20;文件 dumper.go 中的函数 `Fdump` 可以将一个 `Node` 的结构 dump 出来，而包括 `File` 在内，所有前面介绍过得结构体都实现了 `Node` 接口，所以该函数是我们可视化语法树的好工具。

&#x20;下面我们通过一些 UT 来进一步学习一下语法解析的功能。

&#x20;在 `parser_test.go` 内创建如下 UT

```go
func TestParser(t *testing.T) {
    code := `
package main

import (
"fmt"
"net/http"
)

const （a, b)                    
var names []string = make([]int, 10)

func iterate[T any](list []T, f func(T))  {
    for _, t  := range list {
        f(t)
    }
}
`

    ast, _ := Parse(NewFileBase("dump_source.go"), bytes.NewBuffer([]byte(code)), func(err error) {
        fmt.Printf("Parsing Error: %v\n", err)
    }, nil, CheckBranches|AllowGenerics) // 开启泛型支持，以便解析出类型参数

    if ast != nil {
        Fdump(os.Stdout, ast)
    }
}
```

&#x20;执行该 UT 得到输出结果：

> ```
> 1  *syntax.File {
> 2  .  Pragma: nil
> 3  .  PkgName: main @ dumpsource.go:2:9
> 4  .  DeclList: []syntax.Decl (5 entries) {
> 5  .  .  0: *syntax.ImportDecl {
> 6  .  .  .  Group: *syntax.Group {}
> 7  .  .  .  Pragma: nil
> 8  .  .  .  LocalPkgName: nil
> 9  .  .  .  Path: *syntax.BasicLit {
> 10  .  .  .  .  Value: “\”fmt\“”
> 11  .  .  .  .  Kind: 4
> 12  .  .  .  .  Bad: false
> 13  .  .  .  }
> 14  .  .  }
> 15  .  .  1: *syntax.ImportDecl {
> 16  .  .  .  Group: *syntax.Group {}
> 17  .  .  .  Pragma: nil
> 18  .  .  .  LocalPkgName: nil
> 19  .  .  .  Path: *syntax.BasicLit {
> 20  .  .  .  .  Value: “\”net/http\“”
> 21  .  .  .  .  Kind: 4
> 22  .  .  .  .  Bad: false
> 23  .  .  .  }
> 24  .  .  }
> 25  .  .  2: *syntax.ConstDecl {
> 26  .  .  .  Group: *syntax.Group {}
> 27  .  .  .  Pragma: nil
> 28  .  .  .  NameList: []*syntax.Name (2 entries) {
> 29  .  .  .  .  0: a @ dumpsource.go:9:8
> 30  .  .  .  .  1: b @ dumpsource.go:9:11
> 31  .  .  .  }
> 32  .  .  .  Type: nil
> 33  .  .  .  Values: nil
> 34  .  .  }
> 35  .  .  3: *syntax.VarDecl {
> 36  .  .  .  Group: nil
> 37  .  .  .  Pragma: nil
> 38  .  .  .  NameList: []*syntax.Name (1 entries) {
> 39  .  .  .  .  0: names @ dumpsource.go:10:5
> 40  .  .  .  }
> 41  .  .  .  Type: *syntax.SliceType {
> 42  .  .  .  .  Elem: string @ dumpsource.go:10:13
> 43  .  .  .  }
> 44  .  .  .  Values: *syntax.CallExpr {
> 45  .  .  .  .  Fun: make @ dumpsource.go:10:22
> 46  .  .  .  .  ArgList: []syntax.Expr (2 entries) {
> 47  .  .  .  .  .  0: *syntax.SliceType {
> 48  .  .  .  .  .  .  Elem: int @ dumpsource.go:10:29
> 49  .  .  .  .  .  }
> 50  .  .  .  .  .  1: *syntax.BasicLit {
> 51  .  .  .  .  .  .  Value: “10”
> 52  .  .  .  .  .  .  Kind: 0
> 53  .  .  .  .  .  .  Bad: false
> 54  .  .  .  .  .  }
> 55  .  .  .  .  }
> 56  .  .  .  .  HasDots: false
> 57  .  .  .  }
> 58  .  .  }
> 59  .  .  4: *syntax.FuncDecl {
> 60  .  .  .  Pragma: nil
> 61  .  .  .  Recv: nil
> 62  .  .  .  Name: iterate @ dumpsource.go:12:6
> 63  .  .  .  TParamList: []*syntax.Field (1 entries) {
> 64  .  .  .  .  0: *syntax.Field {
> 65  .  .  .  .  .  Name: T @ dumpsource.go:12:14
> 66  .  .  .  .  .  Type: any @ dumpsource.go:12:16
> 67  .  .  .  .  }
> 68  .  .  .  }
> 69  .  .  .  Type: *syntax.FuncType {
> 70  .  .  .  .  ParamList: []*syntax.Field (2 entries) {
> 71  .  .  .  .  .  0: *syntax.Field {
> 72  .  .  .  .  .  .  Name: list @ dumpsource.go:12:21
> 73  .  .  .  .  .  .  Type: *syntax.SliceType {
> 74  .  .  .  .  .  .  .  Elem: T @ dumpsource.go:12:28
> 75  .  .  .  .  .  .  }
> 76  .  .  .  .  .  }
> 77  .  .  .  .  .  1: *syntax.Field {
> 78  .  .  .  .  .  .  Name: f @ dumpsource.go:12:31
> 79  .  .  .  .  .  .  Type: *syntax.FuncType {
> 80  .  .  .  .  .  .  .  ParamList: []*syntax.Field (1 entries) {
> 81  .  .  .  .  .  .  .  .  0: *syntax.Field {
> 82  .  .  .  .  .  .  .  .  .  Name: nil
> 83  .  .  .  .  .  .  .  .  .  Type: T @ dumpsource.go:12:38
> 84  .  .  .  .  .  .  .  .  }
> 85  .  .  .  .  .  .  .  }
> 86  .  .  .  .  .  .  .  ResultList: nil
> 87  .  .  .  .  .  .  }
> 88  .  .  .  .  .  }
> 89  .  .  .  .  }
> 90  .  .  .  .  ResultList: nil
> 91  .  .  .  }
> 92  .  .  .  Body: *syntax.BlockStmt {
> 93  .  .  .  .  List: []syntax.Stmt (2 entries) {
> 94  .  .  .  .  .  0: *syntax.ForStmt {
> 95  .  .  .  .  .  .  Init: *syntax.RangeClause {
> 96  .  .  .  .  .  .  .  Lhs: *syntax.ListExpr {
> 97  .  .  .  .  .  .  .  .  ElemList: []syntax.Expr (2 entries) {
> 98  .  .  .  .  .  .  .  .  .  0: _ @ dumpsource.go:13:6
> 99  .  .  .  .  .  .  .  .  .  1: t @ dumpsource.go:13:9
> 100  .  .  .  .  .  .  .  .  }
> 101  .  .  .  .  .  .  .  }
> 102  .  .  .  .  .  .  .  Def: true
> 103  .  .  .  .  .  .  .  X: list @ dumpsource.go:13:21
> 104  .  .  .  .  .  .  }
> 105  .  .  .  .  .  .  Cond: nil
> 106  .  .  .  .  .  .  Post: nil
> 107  .  .  .  .  .  .  Body: *syntax.BlockStmt {
> 108  .  .  .  .  .  .  .  List: []syntax.Stmt (1 entries) {
> 109  .  .  .  .  .  .  .  .  0: *syntax.ExprStmt {
> 110  .  .  .  .  .  .  .  .  .  X: *syntax.CallExpr {
> 111  .  .  .  .  .  .  .  .  .  .  Fun: f @ dumpsource.go:14:3
> 112  .  .  .  .  .  .  .  .  .  .  ArgList: []syntax.Expr (1 entries) {
> 113  .  .  .  .  .  .  .  .  .  .  .  0: t @ dumpsource.go:14:5
> 114  .  .  .  .  .  .  .  .  .  .  }
> 115  .  .  .  .  .  .  .  .  .  .  HasDots: false
> 116  .  .  .  .  .  .  .  .  .  }
> 117  .  .  .  .  .  .  .  .  }
> 118  .  .  .  .  .  .  .  }
> 119  .  .  .  .  .  .  .  Rbrace: syntax.Pos {}
> 120  .  .  .  .  .  .  }
> 121  .  .  .  .  .  }
> 122  .  .  .  .  .  1: *syntax.ReturnStmt {
> 123  .  .  .  .  .  .  Results: *syntax.BasicLit {
> 124  .  .  .  .  .  .  .  Value: “\”unexpected\“”
> 125  .  .  .  .  .  .  .  Kind: 4
> 126  .  .  .  .  .  .  .  Bad: false
> 127  .  .  .  .  .  .  }
> 128  .  .  .  .  .  }
> 129  .  .  .  .  }
> 130  .  .  .  .  Rbrace: syntax.Pos {}
> 131  .  .  .  }
> 132  .  .  }
> 133  .  }
> 134  .  EOF: syntax.Pos {}
> 135  }
> ```

&#x20;但仔细查看我们所解析的代码，会发现其存在如下问题：

1. import 的库未使用
2. 常量 a, b 没有初始化
3. 变量 names 声明的类型与 make 中指定的类型不一致
4. iterate 声明中不需要返回值，但该函数却返回了一个字符串

&#x20;但解析器依然成功完成了解析，这是因为此时解析器只是负责做语法分析，只要程序符合语言的文法规范，解析器就能够顺利完成；而上述问题是语义问题，或者是代码规范问题（import 的库未使用），不属于语法解析器的管理范围。想要处理这些问题，编译器还需要将语法树继续往后传递并进行进一步处理。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gocompiler.shizhz.me/3.-golang-bian-yi-qi-yu-fa-fen-xi/3.5-unit-test-ji-ast-ke-shi-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
