三、Go 语言工程实践之测试
1、测试
- 回归测试
- 集成测试
- 单元测试
从上到下,覆盖率逐层变大,成本逐层降低
1.1 单元测试-规则
- 所以测试文件以_test.go结尾
func TestXxx(*testing.T)- 初始化逻辑放到TestMain中
1.2 单元测试-覆盖率
--cover
Tips:
- 一般覆盖率:50%~60%,较高覆盖率80%+
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一职责
1.3、单元测试-依赖
外部依赖 =>稳定&幂等
1.4、单元测试-Mock
monkey :github.com/bouk/monkey
Mock函数 不强依赖本地文件
- 为一个函数打桩
- 为一个方法打桩
1.5基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Select()
}
}
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}
二、项目实战
2.1、需求描述
社区话题页面
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面的实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
2.1.1 结构
Topic 话题
- id
- title
- content
- create_time
Post 帖子
- id
- topic_id
- content
- create_time
2.2 分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务的逻辑输出
- 视图层:视图view,处理和外部的交互逻辑
2.3、组件工具
- Gin高性能go web框架
- Go Mod
go mod init
go get gopkg.in/gin-gonic/gin.v1@v1.3.0
2.4 Repository_index
元数据数据行映射为Map实现内存索引
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
2.5 Service 实体:
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
流程: 参数校验->准备数据->组装实体
2.6 Controller
- 构建View对象
- 业务错误码
func QueryPageInfo(topicIdStr string) *PageData {
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
2.7 Router
- 初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
func main() {
if err := Init("./data/"); err != nil {
os.Exit(-1)
}
r := gin.Default()
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id")
data := cotroller.QueryPageInfo(topicId)
c.JSON(200, data)
})
err := r.Run()
if err != nil {
return
}
}