4.6 如何测试

类型检查的测试比语法分析稍稍复杂一些,因为首先类型检查要依赖语法分析的输出,其次需要对类型检查器及整个编译环境进行初始化。

可以从check_test.go文件切入学习现有的测试代码, examples, fixedbugs以及testdata三个目录下包含大量的测试用例,对应的测试入口函数如下:

func TestTestdata(t *testing.T)  { DefPredeclaredTestFuncs(); testDir(t, 75, "testdata") }
func TestExamples(t *testing.T)  { testDir(t, 0, "examples") }
func TestFixedbugs(t *testing.T) { testDir(t, 0, "fixedbugs") }

Go 支持两种测试策略:黑盒测试与白盒测试。黑盒测试关注点在功能层面,测试代码仅允许访问被测试包的 Exported API ;而白盒测试关注点在实现层面,测试代码需要访问被测试包的所有内容。

Go 规范中约定测试文件与被测试文件在同一目录下,那么如何限制测试代码的访问权限呢?答案是将测试文件申明为不同的包。我们知道 Go 要求同一个目录下所有文件只能隶属于同一个包,但测试文件例外,测试文件的包名可以申明为package xxx_test, 其中 xxx 是被测试文件的包名,这样测试文件就处在单独的包中,以此达到黑盒测试的目的。

类型检查器采用的测试策略是黑盒测试,为了能够完成初始化,测试代码中已经实现了很多必要的辅助模块,例如defaultImporter, 同时语法解析也单独进行了封装。我们可以在check_test.go自定义如下方法来做测试:

func TestTypecheck(t *testing.T) {
	code := `
package p

type A struct {}

func (this *A) name() {}

type B *A

func main() {
   var b B

  b.name()
}
`
	f, err := parseSrc("testTypecheckConst", code)

	if err != nil {
		panic(err)
	}

	// Dump 出语法树
	syntax.Fdump(os.Stdout, f)

	var conf Config
	conf.Trace = true
	conf.Importer = defaultImporter()
	conf.Error = func(err error) {
		fmt.Printf("Typecheck Error: %v\n", err)
	}

	info := Info{
		Types:      make(map[syntax.Expr]TypeAndValue),
		Defs:       make(map[*syntax.Name]Object),
		Uses:       make(map[*syntax.Name]Object),
		Selections: make(map[*syntax.SelectorExpr]*Selection),
		Implicits:  make(map[syntax.Node]Object),
		Scopes:     make(map[syntax.Node]*Scope),
		Inferred:   make(map[syntax.Expr]Inferred),
	}

	conf.Check("<no package>", []*syntax.File{f}, &info)
}

因为设置了conf.Trace = true, 我们将看到非常详细的检查结果,这里仅仅截取 main 函数的函数体的检查结果作为演示:

== processDelayed ==
testTypecheckConst:7:23:	--- name: func()
testTypecheckConst:8:1:	--- <end>
testTypecheckConst:12:13:	--- main: func()
testTypecheckConst:13:10:	type B
testTypecheckConst:13:10:	=> B (under = *A) // *types2.Named
testTypecheckConst:15:7:	expr b.name()
testTypecheckConst:15:2:	.  expr b.name
testTypecheckConst:15:1:	.  .  expr b
testTypecheckConst:15:1:	.  .  => b (variable of type B)
testTypecheckConst:15:3:	.  .  ERROR: b.name undefined (type B has no field or method name)
Typecheck Error: testTypecheckConst:15:3: b.name undefined (type B has no field or method name)
testTypecheckConst:15:2:	.  => b.name (invalid operand)
testTypecheckConst:15:7:	=> b.name() (invalid operand)
testTypecheckConst:16:1:	--- <end>

对于方法调用b.name(), 类型检查器合理地报告了错误。

这样我们就可以任意修改源代码,并通过 UT 的方式查看效果,或者通过调试器查看每个细节是如何工作的了。

最后更新于