为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. 修改childOf()方法
  • PART2. 编写测试用例
  • 2.1 测试普通节点的通配符子节点
  • 2.2 测试普通节点下普通子节点和通配符子节点共存
  • PART3. 测试Server的通配符匹配
  1. PART03.路由树

3.08 路由树-通配符匹配之路由查找与测试

本节课工程结构如下:

(base) yanglei@yuanhong 13-findWildcard % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── node.go
├── router.go
├── router_test.go
└── serverInterface.go

0 directories, 8 files

PART1. 修改childOf()方法

之前的childOf()方法是仅在当前节点的子节点映射中查找子节点,但很明显现在需要再查找当前节点的通配符子节点.

node.go:

// childOf 根据给定的path在当前节点的子节点映射中查找对应的子节点(即匹配到了静态路由)
// 若未在子节点映射中找到对应子节点 则尝试返回当前节点的通配符子节点
func (n *node) childOf(path string) (child *node, found bool) {
	// 当前节点的子节点映射为空 则有可能匹配到通配符节点
	if n.children == nil {
		return n.wildcardChild, n.wildcardChild != nil
	}

	// 在子当前节点的节点映射中查找对应的子节点 若未找到同样尝试返回当前节点的通配符子节点
	child, found = n.children[path]
	if !found {
		return n.wildcardChild, n.wildcardChild != nil
	}

	// 找到了对应的子节点 则返回该子节点
	return child, found
}

PART2. 编写测试用例

这里还是在router_test.go新建一个单独测试查找通配符路由的函数.

2.1 测试普通节点的通配符子节点

router_test.go:

// TestRouter_findRoute_wildcard 测试针对通配符的路由查找功能
func TestRouter_findRoute_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 构造测试用例
	testCases := []struct {
		name     string
		method   string
		path     string
		isFound  bool
		wantNode *node
	}{
		// 普通节点的通配符子节点测试用例
		{
			name:    "order wildcard",
			method:  http.MethodGet,
			path:    "/order/abc",
			isFound: true,
			wantNode: &node{
				path:          "*",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandleFunc,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)

			if !found {
				return
			}

			msg, found := testCase.wantNode.equal(foundNode)
			assert.True(t, found, msg)
		})
	}
}

测试顺利通过

2.2 测试普通节点下普通子节点和通配符子节点共存

router_test.go:

// TestRouter_findRoute_wildcard 测试针对通配符的路由查找功能
func TestRouter_findRoute_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 构造测试用例
	testCases := []struct {
		name     string
		method   string
		path     string
		isFound  bool
		wantNode *node
	}{
		// 普通节点的通配符子节点测试用例
		{
			name:    "order wildcard",
			method:  http.MethodGet,
			path:    "/order/abc",
			isFound: true,
			wantNode: &node{
				path:          "*",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandleFunc,
			},
		},
		// 普通节点下普通子节点和通配符子节点共存的测试用例
		{
			name:    "order detail",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:          "detail",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandleFunc,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)

			if !found {
				return
			}

			msg, found := testCase.wantNode.equal(foundNode)
			assert.True(t, found, msg)
		})
	}
}

测试顺利通过

PART3. 测试Server的通配符匹配

httpServer_test.go:

func TestServer_serve(t *testing.T) {
	s := NewHTTPServer()
	handleFunc := func(ctx *Context) {
		// 直接调用http.ResponseWriter的Write方法时 默认响应码为200
		ctx.Resp.Write([]byte("hello order detail"))
	}
	s.GET("/order/detail", handleFunc)

	wildcardHandleFunc := func(ctx *Context) {
		respPath := ""

		pathSegments := strings.Split(ctx.Req.URL.Path, "/")
		for _, pathSegment := range pathSegments {
			if pathSegment == "" {
				continue
			}
			respPath += pathSegment
			respPath += " "
		}

		respPath = strings.TrimRight(respPath, " ")
		respMsg := fmt.Sprintf("hello %s", respPath)

		ctx.Resp.Write([]byte(respMsg))
	}
	s.GET("/order/*", wildcardHandleFunc)

	_ = s.Start(":8081")
}

Last updated 9 months ago