# 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 的库未使用），不属于语法解析器的管理范围。想要处理这些问题，编译器还需要将语法树继续往后传递并进行进一步处理。
