Go 语言工程实践之测试 | 青训营笔记

106 阅读6分钟

0004.Go 语言工程实践之测试 | 青训营笔记

[TOC]

一、测试概念

1.事故

  • 营销配置错误,导致非预期用户享受权益,资金损失10+
  • 用户提现,冥等失效,短时间可以多次提现,资金损失20w+
  • 代码逻辑错误,广告位被占,无法出广告,收入损失500w+
  • 代码指针使用错误,导致APP不可用,损失上kw+

==测试是避免事故的最后一道屏障==

2.测试分类

  • 回归测试
  • 集成测试
  • 单元测试

==从上到下,覆盖率逐层变大,成本却逐层降低==

二、单元测试

1.单元测试的定义

是指在软件中最小的可测试单元与其他部分隔离的情况下进行的验证工作,这里的最小测试单元是指函数或者类。

2.单元测试的用例设计

单元测试的用例其实是一个“输入数据”和“预计输出”的集合。你需要跟你输入数据,根据逻辑功能给出预计输出,这里所说的根据逻辑功能是指,通过需求文档就能给出的预计输出。而非我们通过已经实现的代码去推导出的预计输出。这也是最容易被忽视的一点。你要去做单元测试,然后还要通过代码去推断出预计输出,如果你的代码逻辑本来就实现错了,给出的预计输出也是错的,那么你的单元测试将没有意义。

3.单元测试规则

  • 所有测试文件以 test.go 结尾
  • fune TestXxx(*testing.T)
  • 初始化逻辑放到 TestMain中

4.覆盖率

  • 如何衡量代码是否经过了足够的测试?
  • 如何评价项目的测试水准?
  • 如何评估项目是否达到了高水准测试等级?

单元测试覆盖率是指测试用例对代码中被测对象(比如函数、类等)的执行路径覆盖情况的度量。它可以帮助我们评估测试用例的充分性,即能否检测到代码中存在的错误。

通常情况下,一个较高的单元测试覆盖率表明测试用例可以有效地检测出代码中存在的错误,但并不代表所有错误都被检测出来了。因此,单元测试覆盖率只是测试用例充分性的一个参考指标,而不是唯一的衡量标准。要构建高质量的软件系统,我们还需要进行其他类型的测试,如集成测试、验收测试等。

5.单元测试-Tips

  • 一般覆盖率:50%-60%,较高覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

6.提高单元测试覆盖率的方法

  • 编写更多的测试用例:编写更多的测试用例可以帮助发现更多的边界情况和异常情况,从而提高测试用例的充分性。
  • 使用各种类型的测试数据:在测试用例中使用各种类型的不同数据可以提高测试用例的覆盖率。比如,使用不同大小的输入数据、使用不同形式的特殊字符等。
  • 使用代码分析工具:使用代码分析工具可以帮助发现代码中未覆盖的执行路径,从而开发更多的测试用例。
  • 针对复杂度较高的代码编写更多测试用例:发现包含更多分支和嵌套结构的代码,编写更多的测试用例来测试它们。
  • 使用Mock对象:使用Mock对象可以模拟一些外部依赖,例如数据库或网络,从而在不需要实际执行这些依赖的情况下测试代码。

7.测试依赖

在进行 Go 单元测试时,测试依赖是一个需要考虑的问题。测试依赖可以是外部库、网络连接、文件系统等。

为了避免测试时的依赖,可以采用以下方法之一:

  • Mock(模拟):使用 Mock 对象模拟依赖项,这样测试用例就可以在不依赖实际对象的情况下运行。这种方法需要使用第三方库支持如mockery、gomock 。
  • 接口抽象:尽可能将程序的依赖项抽象到接口中,便于在测试时进行模拟操作。

对于一些无法通过 Mock 或者接口抽象的测试依赖项,可以在测试文件中使用特殊标记来判断是否需要进行特定的依赖项测试。

三、Mock 测试

monkey : github.com/bouk/monkey

快速 Mock函数

  • 为一个函数打桩
  • 为一个方法打桩

在 Go 中编写 Mock 测试是很重要的技能,因为它可以方便地对代码进行测试,而不会受到代码的依赖项影响。在 Mock 测试中,我们会将被测试的方法依赖项(例如 DB、网络调用等)替换成一个虚拟的、预先设置好行为的对象,这样我们就可以快速来测试被测方法的行为,而不需要真实连接到实际依赖的对象。

下面是 Go 语言中编写 Mock 测试时需要考虑的一些关键点:

  • 创建 Mock 类型的结构体或接口:在进行 Mock 测试时,我们需要创建实现被测试方法依赖的结构体或接口,这样我们就可以将其嵌入测试中,并替换掉实际的依赖项。
  • 设置依赖的预期行为:在进行 Mock 测试时,我们需要通过代码设置依赖项的预期行为。在设置行为之后,被测代码会直接调用 Mock 对象,这样就可以在不依赖实际依赖项的情况下测试代码。
  • 调用被测试的代码:Mock 对象设置好之后,我们需要根据被测试代码的行为来手动触发 Mock 对象的某些行为,可以使用 assert 等库来验证结果是否正确。

总而言之,Mock 测试对于测试复杂的代码非常有用。在进行 Mock 测试时,首先需要确定代码中需要进行测试的依赖项,然后创建 Mock 对象并设置预期行为。最后需要调用被测试的代码并对其输出结果进行测试断言。

四、基准测试

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

Go 语言的基准测试(benchmark testing)是 Go 语言自带的性能测试工具,用于测试代码的性能。使用基准测试可以检测代码执行的时间和内存占用情况,以及找出代码中的性能瓶颈。

基准测试的写法如下:

func BenchmarkXxx(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // 要测试的代码
    }
}

其中,函数名必须以 Benchmark 开头并且至少有一个可导出参数 *testing.B。而要测试的代码就放在 for 循环中,因为测试需要运行多次以获取结果的平均值。循环次数在 b.N 中自动设置,测试前 Go 会运行函数几次以确定循环次数。

在进行基准测试时,我们可以操作一些变量或者使用某些库来比较它们之间的性能差异。我们可以使用 testing 包中提供的各种断言,例如 assert 函数,来进行测试结果的比较。

通常进行基准测试时还需要使用计时工具。Go语言自带了一个计时工具 time 包,我们可以使用它来进行基准测试中耗时的计算。

下面是一个基准测试的示例代码:

import "testing"

// 测试用的函数
func testSlice() {
    var mySlice []string

    for i := 0; i < 1000; i++ {
        mySlice = append(mySlice, "test")
    }
}

func BenchmarkSlice(b *testing.B) {
    for i := 0; i < b.N; i++ {
        testSlice()
    }
}

// 输出结果
// BenchmarkSlice-4    1000000      1851 ns/op 

上面的示例代码测试了一个空切片追加 1000 个 "test" 的函数 testSlice() 的性能。通过多次执行 testSlice() 并记录平均值,可以获取函数 testSlice() 的平均执行时间。从结果可以看出,testSlice() 函数平均执行时间为 1851 纳秒。

总之,基准测试可以帮助我们对代码的性能进行精细的评估,找出代码中的瓶颈并进行优化。在进行基准测试时,我们必须确保测试环境相对稳定,测试时间不宜太短,否则得到的数据会相对不准确。

五、项目实践

1.需求描述

社区话题页面:

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

2.需求用例

浏览消费用户

  • User →Topic
  • User→PostList

3.ER 图-Entity Relationship Diagram

TopicPost
idid
titletopic_id
contentcontent
create_timecreate_time

4.分层结构

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

5.组件工具

6.Repository

元数据 索引

数据行 → 内存Map

7.Controller

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

8.Router

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

六、课后实践

  • 支持发布帖子
  • 本地 Id 生成需要保证不重复、唯一性
  • Append 文件,更新索引,注意 Map 的并发安全问题