Go-单元测试&项目实战| 青训营笔记

104 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

03测试

  • 回归测试 回归测试是测试已经通过测试的软件在更改后是否仍能正常工作。
  • 继承测试 集成测试是在将多个模块组合成系统后进行的测试。
  • 单元测试 单元测试是对软件中的独立单元进行的测试。

3.1 单元测试

3.1.1 单元测试-规则

  • 所有测试文件以_test.go结尾。
  • func TestXxx(*testing.T)。
  • 初始化逻辑放到TestMain中。
func TestMain(m *testing.M) {

    //测试前:数据装载、配置初始化等前置工作
    code := m.Run()
    //测试后:释放资源等收尾工作
}

3.1.2 单元测试-例子

func HelloTom() string {
    return "Jerry"
}
func TestHelloTom(t *testing.T) {
    output := HelloTom()
    expectOutput := "Tom"
    if output != expectOutput {
        t.Errorf("Expected %s do not match actual %s", expectOutput, output)
    }
}

3.1.3 单元测试-运行

go test [flags][packages]

3.1.4 单元测试-assert

"github.com/stretchr/testify/assert"

3.1.5 单元测试-覆盖率

  • 衡量代码是否经过了足够的测试。
  • 评价项目的测试水准
  • 评估项目达到的水准测试等级
go test project_name_test.go --cover
// 返回时间、覆盖率(代码行数百分比)
- 一般覆盖率:50%~60%,较高覆盖率80%。
- 测试分支相互独立、全面覆盖。
- 测试单元粒度足够小,函数单一职责。

3.2 单元测试-依赖

外部依赖=>稳定&幂等 幂等:输入一个值,每次返回的值都一样。 稳定:独立,解耦。

3.3 单元测试-文件处理

为了防止测试函数时,对本地文件进行破坏/更改。 使用下面的mock函数进行测试。

3.4 单元测试-Mock

快速Mock函数

monkey:https://github.com/bouk/monkey
  • 为一个函数打桩
  • 为一个方法打桩

3.5 基准测试

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

3.5.1 基准测试-例子

下面是一个简单的 Go 基准测试示例:

package main

import (
    "fmt"
    "testing"
)

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

运行这个基准测试,可以使用 go test -bench=. 命令,它会运行所有的基准测试,输出的每一行都是一个测试的结果。

这个例子中,我们测试了 fmt.Sprintf("hello") 的执行效率。可以看到,b.N 会被设置为一个足够大的值,使得整个函数执行足够长的时间,以便于得出合理的结果。

3.5.2 基准测试-运行

3.5.3 基准测试-优化

fastrand在高并发场景更快

04 项目实践

  • 需求设计
  • 代码开发
  • 测试运行

4.1 需求描述

社区话题页面

  • 展示话题(标题,文字描述)和回帖列表
  • 暂不考虑前端页面实现,仅仅实现一个本地web服务
  • 话题和回帖数据用文件储存

4.2 需求用例

浏览消费用户

  • User → Topic
  • User → PostList

4.3 ER图-Entity Relationship Diagram

Topic

  • id
  • title
  • content
  • create_time Post
  • id
  • topic_id
  • content
  • create_time

4.4 分层结构

  • 数据层:数据Model,外部数据的增删改查。
  • 逻辑层:业务Entity,处理核心业务逻辑输出。
  • 视图层:视图view,处理和外部的交互逻辑。
    File → Repository → Service → Controller → Client

4.5 组件工具

  • Gin高性能go web框架
https://github.com/gin-gonic/gin#installation
  • Go mod
//  初始化gomod文件
go mod init
// 添加gin依赖
go get gopkg.in/gin-gonic/gin.v1@v1.3.0

4.6 Repository-index

将数据行定义为内存的map,实现快速索引。

var (
    topicIndexMap map[int64]*Topic
    postIndexMap map[int64][]*Post
)
//初始化话题数据索引
func initTopicIndexMap(filePath string) error {
    open, err :=os.Open(filePath + "topic")
    if err != nil {
        return err
    }
    scanner := bufio.NewScanner(open)
    topicTmMap := make(map[int64]*Topic)
    for scanner.Scan() {
        text := scanner.Text()
        var topic Topic
        if err := json.Unmarshal([]byte(text), &topic); err != nil {
        }
        topicTmpMap[topic.Id] = &topic
    }
    topicTmpMap[topic.Id] = topicTmpMap
    return nil
}

读取文件并解析成 Topic 结构体对象的。它会打开一个文件,读取文件中的每一行,将每一行解析成 Topic 结构体对象,并将其存入一个 map 中。这个 map 以 topic.Id 为 key,topic 结构体指针为 value。最后,它将该 map 赋值给 topicTmpMap 变量。

4.6.1 Repository-查询

// 适合高并发模式下,只执行一次的场景。 即单立模式。
var topicOnce sync.Once

4.7 Service

逻辑流程 参数校验→准备数据→组装实体

4.8 Controller

  • 构建View对象
  • 业务错误码

4.9 Router

  • 初始化数据索引
  • 初始化引擎配置
  • 构建路由
  • 启动服务

4.10 运行