乐趣区

关于go:gozero微服务实战系列十一大结局

本篇是整个系列的最初一篇了,原本打算在系列的最初一两篇写一下对于 k8s 部署相干的内容,在构思的过程中感觉本人对 k8s 常识的把握还很有余,在本人没有了解把握的前提下我感觉也很难写出本人称心的文章,大家看了可能也会感觉内容没有干货。我最近也在学习 k8s 的一些最佳实际以及浏览 k8s 的源码,期待时机成熟的时候可能会思考独自写一个 k8s 实战系列文章。

内容回顾

上面列出了整个系列的每篇文章,这个系列文章的次要特点是贴近实在的开发场景,并针对高并发申请以及常见问题进行优化,文章的内容也是循序渐进的,先是介绍了我的项目的背景,接着进行服务的拆分,拆分完服务进行 API 的定义和表构造的设计,这和咱们理论在公司中的开发流程是相似的,紧接着就是做一些数据库的 CRUD 基本操作,前面用三篇文章来解说了缓存,因为缓存是高并发的根底,没有缓存高并发零碎就无从谈起,缓存次要是应答高并发的读,接下来又用两篇文章来对高并发的写进行优化,最初通过分布式事务保障为服务间的数据一致性。如果大家可能对每一篇文章都能了解透彻,我感觉对于工作中的绝大多数场景都能轻松应答。

对于文章配套的示例代码并没有写的很欠缺,有几点起因,一是商城的性能点十分多,很难把所有的逻辑都笼罩到;二是少数都是反复的业务逻辑,只有大家把握了外围的示例代码,其余的业务逻辑能够本人把代码 down 下来进行补充欠缺,这样我感觉才会提高。如果有不了解的中央大家能够在社区群中问我,每个社区群都能够找到我。

go-zero 微服务实战系列(一、开篇)

go-zero 微服务实战系列(二、服务拆分)

go-zero 微服务实战系列(三、API 定义和表结构设计)

go-zero 微服务实战系列(四、CRUD 热身)

go-zero 微服务实战系列(五、缓存代码怎么写)

go-zero 微服务实战系列(六、缓存一致性保障)

go-zero 微服务实战系列(七、申请量这么高该如何优化)

go-zero 微服务实战系列(八、如何解决每秒上万次的下单申请)

go-zero 微服务实战系列(九、极致优化秒杀性能)

go-zero 微服务实战系列(十、分布式事务如何实现)

单元测试

软件测试由单元测试开始(unit test)。更简单的测试都是在单元测试之上进行的。如下所示测试的层级模型:

单元测试(unit test)是最小、最简略的软件测试模式、这些测试用来评估某一个独立的软件单元,比方一个类,或者一个函数的正确性。这些测试不思考蕴含该软件单元的整体零碎的正确定。单元测试同时也是一种标准,用来保障某个函数或者模块完全符合系统对其的行为要求。单元测试常常被用来引入测试驱动开发的概念。

go test 工具

go 语言的测试依赖 go test 工具,它是一个依照肯定约定和组织的测试代码的驱动程序。在包目录内,所有以 _test.go 为后缀的源代码文件都是 go test 测试的一部分,不会被 go build 编译到最终的可执行文件。

*_test.go 文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数:

类型 格局 作用
测试复数 函数名前缀为 Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为 Benchmark 测试函数的性能
示例函数 函数名前缀为 Example 提供示例

go test会遍历所有 *_test.go 文件中合乎上述命名规定的函数,而后生成一个长期的 main 包用于调用相应的测试函数。

单测格局

每个测试函数必须导入 testing 包,测试函数的根本格局如下:

func TestName(t *testing.T) {// ......}

测试函数的名字必须以 Test 结尾,可选的后缀名必须以大写字母结尾,示例如下:

func TestDo(t *testing.T) {//......}
func TestWrite(t *testing.T) {// ......}

testing.T 用于报告测试失败和附加的日志信息,领有的次要办法如下:

Name() string
Fail()
Failed() bool
FailNow()
logDepth(s string, depth int)
Log(args ...any)
Logf(format string, args ...any)
Error(args ...any)
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
Skip(args ...any)
Skipf(format string, args ...any)
SkipNow()
Skipped() bool
Helper()
Cleanup(f func())
Setenv(key string, value string)

简略示例

在这个门路下 lebron/apps/order/rpc/internal/logic/createorderlogic.go:44 有一个生成订单 id 的函数,函数如下:

func genOrderID(t time.Time) string {s := t.Format("20060102150405")
    m := t.UnixNano()/1e6 - t.UnixNano()/1e9*1e3
    ms := sup(m, 3)
    p := os.Getpid() % 1000
    ps := sup(int64(p), 3)
    i := atomic.AddInt64(&num, 1)
    r := i % 10000
    rs := sup(r, 4)
    n := fmt.Sprintf("%s%s%s%s", s, ms, ps, rs)
    return n
}

咱们创立 createorderlogic_test.go 文件并为该办法编写对应的单元测试函数,生成的订单 id 长度为 24,单元测试函数如下:

func TestGenOrderID(t *testing.T) {oid := genOrderID(time.Now())
    if len(oid) != 24 {t.Errorf("oid len expected 24, got: %d", len(oid))
    }
}

在以后门路下执行 go test 命令,能够看到输入后果如下:

PASS
ok      github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic    1.395s

还能够加上 -v 输入更残缺的后果,go test -v 输入后果如下:

=== RUN   TestGenOrderID
--- PASS: TestGenOrderID (0.00s)
PASS
ok      github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic    1.305s

go test -run

在执行 go test 命令的时候能够增加 -run 参数,它对应一个正则表达式,又有函数名匹配上的测试函数才会被 go test 命令执行,例如咱们能够应用 go test -run=TestGenOrderID 来值运行 TestGenOrderID 这个单测。

表格驱动测试

表格驱动测试不是工具,它只是编写更清晰测试的一种形式和视角。编写好的测试并不是一件容易的事件,但在很多状况下,表格驱动测试能够涵盖很多方面,表格里的每一个条目都是一个残缺的测试用例,它蕴含输出和预期的后果,有时还蕴含测试名称等附加信息,以使测试输入易于浏览。应用表格测试可能很不便的保护多个测试用例,防止在编写单元测试时频繁的复制粘贴。

lebron/apps/product/rpc/internal/logic/checkandupdatestocklogic.go:53 咱们能够编写如下表格驱动测试:

func TestStockKey(t *testing.T) {tests := []struct {
        name   string
        input  int64
        output string
    }{{"test one", 1, "stock:1"},
        {"test two", 2, "stock:2"},
        {"test three", 3, "stock:3"},
    }

    for _, ts := range tests {t.Run(ts.name, func(t *testing.T) {ret := stockKey(ts.input)
            if ret != ts.output {t.Errorf("input: %d expectd: %s got: %s", ts.input, ts.output, ret)
            }
        })
    }
}

执行命令 go test -run=TestStockKey -v 输入如下:

=== RUN   TestStockKey
=== RUN   TestStockKey/test_one
=== RUN   TestStockKey/test_two
=== RUN   TestStockKey/test_three
--- PASS: TestStockKey (0.00s)
    --- PASS: TestStockKey/test_one (0.00s)
    --- PASS: TestStockKey/test_two (0.00s)
    --- PASS: TestStockKey/test_three (0.00s)
PASS
ok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.353s

并行测试

表格驱动测试中通常会定义比拟多的测试用例,而 go 语言又天生反对并发,所以很容易施展本身劣势将表格驱动测试并行化,能够通过t.Parallel() 来实现:

func TestStockKeyParallel(t *testing.T) {t.Parallel()
    tests := []struct {
        name   string
        input  int64
        output string
    }{{"test one", 1, "stock:1"},
        {"test two", 2, "stock:2"},
        {"test three", 3, "stock:3"},
    }

    for _, ts := range tests {
        ts := ts
        t.Run(ts.name, func(t *testing.T) {t.Parallel()
            ret := stockKey(ts.input)
            if ret != ts.output {t.Errorf("input: %d expectd: %s got: %s", ts.input, ts.output, ret)
            }
        })
    }
}

测试覆盖率

测试覆盖率是指代码被测试套件笼罩的百分比。通常咱们应用的都是语句的覆盖率,也就是在测试中至多被运行一次的代码占总的代码的比例。go 提供内置的性能来查看代码覆盖率,即应用 go test -cover 来查看测试覆盖率:

PASS
coverage: 0.6% of statements
ok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.381s

能够看到咱们的覆盖率只有 0.6%,哈哈,这是十分不合格滴,大大的不合格。go 还提供了一个 -coverprofile 参数,用来将覆盖率相干的记录输入到文件 go test -cover -coverprofile=cover.out

PASS
coverage: 0.6% of statements
ok      github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic    1.459s

而后执行 go tool cover -html=cover.out,应用 cover 工具来解决生成的记录信息,该命令会关上本地的浏览器窗口生成测试报告

解决依赖

对于单测中的依赖,咱们个别采纳 mock 的形式进行解决,gomock 是 Go 官网提供的测试框架,它在内置的 testing 包或其余环境中都可能很不便的应用。咱们应用它对代码中的那些接口类型进行 mock,不便编写单元测试。对于 gomock 的应用请参考 gomock 文档

mock 依赖 interface,对于非 interface 场景下的依赖咱们能够采纳打桩的形式进行 mock 数据,monkey 是一个 Go 单元测试中非常罕用的打桩工具,它在运行时通过汇编语言重写可执行文件,将指标函数或办法的实现跳转到桩实现,其原理相似于热补丁。monkey 库很弱小,然而应用时需注意以下事项:

  • monkey 不反对内联函数,在测试的时候须要通过命令行参数 -gcflags=-l 敞开 Go 语言的内联优化。
  • monkey 不是线程平安的,所以不要把它用到并发的单元测试中。

其余

画图工具

社区中常常有人问画图用的是什么工具,本系列文章中的插图工具次要是如下两个

https://www.onemodel.app/

https://whimsical.com/

代码标准

代码不光是要实现性能,很重要的一点是代码是写给他人看的,所以咱们对代码的品质要有肯定的要求,要遵循标准,能够参考 go 官网的代码 review 倡议

https://github.com/golang/go/…

谈谈感触

工夫过得贼快,人不知; 鬼不觉间这个系列曾经写到十一篇了。依照每周更新两篇的速度也写了一个多月了。写文章是个体力活且十分的耗时,又惟恐有写的不对的中央,对大家产生误导,所以还须要重复的检查和查阅相干材料。均匀一篇文章要写一天左右,平时工作日比较忙,根本都是周六日来写,因而最近一个月周六日根本没有劳动过。

但我感觉播种也十分大,在写文章的过程中,对于本人把握的知识点,是一个温习的过程,能够让本人加深对知识点的了解,对于本人没有把握的知识点就又是一个学习新常识的过程,让本人把握了新的常识,所以我和读者也是一起在学习提高呢。大家都晓得,对于本人了解的常识,想要说进去或者写进去让他人也了解也是不容易的,因而写文章对本人的软实力也是有很大的晋升。

所以,我还是会持续保持写文章,保持输入,和大家一起学习成长。同时,我也欢送大家来 “ 微服务实际 ” 公众号来投稿。可能有些人感觉本人的程度不行,放心写的内容不高端,没有逼格,我感觉大可不必,只有能把知识点讲明确就十分棒了,能够是基础知识,也能够是最佳实际等等。kevin 会对投稿的每一篇文章都认真审核,写的不对的中央他都会指出来,所有还有和 kevin 一对一交流学习的机会,小伙伴们放松口头起来呀。

结束语

非常感谢大家这一个多月以来的反对。看到每篇文章有那么多的点赞,我非常的开心,也更加的有能源,所以,也在打算写下个系列的文章,目前有两个待选的主题,别离是《go-zero 源码系列》和《gRPC 实战源码系列》,欢送小伙伴们在评论区留下你的评论,说出你更期待哪个系列,如果本篇文章点赞数超过 66 的话,咱就持续开整。

代码仓库: https://github.com/zhoushuguang/lebron

我的项目地址

https://github.com/zeromicro/go-zero

欢送应用 go-zerostar 反对咱们!

微信交换群

关注『微服务实际 』公众号并点击 交换群 获取社区群二维码。

退出移动版